Completed
Push — master ( 932cd1...29fc25 )
by Christopher
01:11
created

ObjectModelSerializerBase::setRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
namespace POData\ObjectModel;
4
5
use POData\Common\ODataConstants;
6
use POData\IService;
7
use POData\Providers\Metadata\ResourceSetWrapper;
8
use POData\Providers\Metadata\ResourceProperty;
9
use POData\Providers\Metadata\ResourceTypeKind;
10
use POData\Providers\Metadata\ResourceType;
11
use POData\Providers\Metadata\Type\IType;
12
use POData\UriProcessor\RequestDescription;
13
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
14
use POData\Common\InvalidOperationException;
15
use POData\Common\ODataException;
16
use POData\Common\Messages;
17
use POData\UriProcessor\SegmentStack;
18
19
/**
20
 * Class ObjectModelSerializerBase.
21
 */
22
class ObjectModelSerializerBase
23
{
24
    /**
25
     * The service implementation.
26
     *
27
     * @var IService
28
     */
29
    protected $service;
30
31
    /**
32
     * Request description instance describes OData request the
33
     * the client has submitted and result of the request.
34
     *
35
     * @var RequestDescription
36
     */
37
    protected $request;
38
39
    /**
40
     * Collection of complex type instances used for cycle detection.
41
     *
42
     * @var array
43
     */
44
    protected $complexTypeInstanceCollection;
45
46
    /**
47
     * Absolute service Uri.
48
     *
49
     * @var string
50
     */
51
    protected $absoluteServiceUri;
52
53
    /**
54
     * Absolute service Uri with slash.
55
     *
56
     * @var string
57
     */
58
    protected $absoluteServiceUriWithSlash;
59
60
    /**
61
     * Holds reference to segment stack being processed
62
     *
63
     * @var SegmentStack
64
     */
65
    protected $stack;
66
67
    /**
68
     * @param IService           $service Reference to the data service instance
69
     * @param RequestDescription $request Type instance describing the client submitted request
70
     */
71
    protected function __construct(IService $service, RequestDescription $request = null)
72
    {
73
        $this->service = $service;
74
        $this->request = $request;
75
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
76
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
77
        $this->stack = new SegmentStack($request);
78
        $this->complexTypeInstanceCollection = array();
79
    }
80
81
    /**
82
     * Gets reference to the request submitted by client.
83
     *
84
     * @return RequestDescription
85
     */
86
    public function getRequest()
87
    {
88
        assert(null != $this->request, "Request not yet set");
89
        return $this->request;
90
    }
91
92
    /**
93
     * Sets reference to the request submitted by client.
94
     * @param RequestDescription $request
95
     *
96
     */
97
    public function setRequest(RequestDescription $request)
98
    {
99
        $this->request = $request;
100
        $this->stack->setRequest($request);
101
    }
102
103
    /**
104
     * Gets the data service instance
105
     *
106
     * @return IService
107
     */
108
    public function getService()
109
    {
110
        return $this->service;
111
    }
112
113
    /**
114
     * Gets the segment stack instance
115
     *
116
     * @return SegmentStack
117
     */
118
    public function getStack()
119
    {
120
        return $this->stack;
121
    }
122
123
    /**
124
     * Builds the key for the given entity instance.
125
     * Note: The generated key can be directly used in the uri,
126
     * this function will perform
127
     * required escaping of characters, for example:
128
     * Ships(ShipName='Antonio%20Moreno%20Taquer%C3%ADa',ShipID=123),
129
     * Note to method caller: Don't do urlencoding on
130
     * return value of this method as it already encoded.
131
     *
132
     * @param mixed        $entityInstance Entity instance for which key value needs to be prepared
133
     * @param ResourceType $resourceType   Resource type instance containing metadata about the instance
134
     * @param string       $containerName  Name of the entity set that the entity instance belongs to
135
     *
136
     * @return string Key for the given resource, with values encoded for use in a URI
137
     */
138
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
139
    {
140
        $keyProperties = $resourceType->getKeyProperties();
141
        assert(count($keyProperties) != 0, 'count($keyProperties) == 0');
142
        $keyString = $containerName . '(';
143
        $comma = null;
144
        foreach ($keyProperties as $keyName => $resourceProperty) {
145
            $keyType = $resourceProperty->getInstanceType();
146
            assert($keyType instanceof IType, '$keyType not instanceof IType');
147
            $keyValue = $this->getPropertyValue($entityInstance, $resourceType, $resourceProperty);
148
            if (is_null($keyValue)) {
149
                throw ODataException::createInternalServerError(
150
                    Messages::badQueryNullKeysAreNotSupported($resourceType->getName(), $keyName)
151
                );
152
            }
153
154
            $keyValue = $keyType->convertToOData($keyValue);
155
            $keyString .= $comma . $keyName . '=' . $keyValue;
156
            $comma = ',';
157
        }
158
159
        $keyString .= ')';
160
161
        return $keyString;
162
    }
163
164
    /**
165
     * Get the value of a given property from an instance.
166
     *
167
     * @param mixed            $entity           Instance of a type which contains this property
168
     * @param ResourceType     $resourceType     Resource type instance containing metadata about the instance
169
     * @param ResourceProperty $resourceProperty Resource property instance containing metadata about the property whose value to be retrieved
170
     *
171
     * @return mixed The value of the given property
172
     *
173
     * @throws ODataException If reflection exception occurred while trying to access the property
174
     */
175
    protected function getPropertyValue($entity, ResourceType $resourceType, ResourceProperty $resourceProperty)
176
    {
177
        try {
178
            //Is this slow?  See #88
179
                // If a magic method for properties exists (eg Eloquent), dive into it directly and return value
180
            if (method_exists($entity, '__get')) {
181
                $targProperty = $resourceProperty->getName();
182
183
                return $entity->$targProperty;
184
            }
185
            $reflectionClass = new \ReflectionClass(get_class($entity));
186
            $reflectionProperty = $reflectionClass->getProperty($resourceProperty->getName());
187
            $reflectionProperty->setAccessible(true);
188
189
            return $reflectionProperty->getValue($entity);
190
        } catch (\ReflectionException $reflectionException) {
191
            throw ODataException::createInternalServerError(
192
                Messages::objectModelSerializerFailedToAccessProperty(
193
                    $resourceProperty->getName(),
194
                    $resourceType->getName()
195
                )
196
            );
197
        }
198
    }
199
200
    /**
201
     * Resource set wrapper for the resource being serialized.
202
     *
203
     * @return ResourceSetWrapper
204
     */
205
    protected function getCurrentResourceSetWrapper()
206
    {
207
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
208
        $count = count($segmentWrappers);
209
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
210
    }
211
212
    /**
213
     * Whether the current resource set is root resource set.
214
     *
215
     * @return bool true if the current resource set root container else
216
     *              false
217
     */
218
    protected function isRootResourceSet()
219
    {
220
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
221
        return empty($segmentWrappers) || 1 == count($segmentWrappers);
222
    }
223
224
    /**
225
     * Returns the etag for the given resource.
226
     *
227
     * @param mixed        $entryObject  Resource for which etag value
228
     *                                   needs to be returned
229
     * @param ResourceType $resourceType Resource type of the $entryObject
230
     *
231
     * @return string|null ETag value for the given resource
232
     *                     (with values encoded for use in a URI)
233
     *                     if there are etag properties, NULL if there is no etag property
234
     */
235
    protected function getETagForEntry($entryObject, ResourceType $resourceType)
236
    {
237
        $eTag = null;
238
        $comma = null;
239
        foreach ($resourceType->getETagProperties() as $eTagProperty) {
240
            $type = $eTagProperty->getInstanceType();
241
            assert(!is_null($type) && $type instanceof IType, 'is_null($type) || $type not instanceof IType');
242
            $value = $this->getPropertyValue($entryObject, $resourceType, $eTagProperty);
243 View Code Duplication
            if (is_null($value)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
244
                $eTag = $eTag . $comma . 'null';
245
            } else {
246
                $eTag = $eTag . $comma . $type->convertToOData($value);
247
            }
248
249
            $comma = ',';
250
        }
251
252 View Code Duplication
        if (!is_null($eTag)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
            // If eTag is made up of datetime or string properties then the above
254
            // IType::converToOData will perform utf8 and url encode. But we don't
255
            // want this for eTag value.
256
            $eTag = urldecode(utf8_decode($eTag));
257
258
            return ODataConstants::HTTP_WEAK_ETAG_PREFIX . rtrim($eTag, ',') . '"';
259
        }
260
261
        return null;
262
    }
263
264
    /**
265
     * Pushes a segment for the root of the tree being written out
266
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
267
     * 'Segment Stack' and this method.
268
     * Note: Calls to this method should be balanced with calls to popSegment.
269
     *
270
     * @return bool true if the segment was pushed, false otherwise
271
     */
272
    protected function pushSegmentForRoot()
273
    {
274
        $segmentName = $this->getRequest()->getContainerName();
275
        $segmentResourceSetWrapper = $this->getRequest()->getTargetResourceSetWrapper();
276
        assert(null != $segmentResourceSetWrapper, "Segment resource set wrapper must not be null");
277
278
        return $this->_pushSegment($segmentName, $segmentResourceSetWrapper);
279
    }
280
281
    /**
282
     * Pushes a segment for the current navigation property being written out.
283
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
284
     * 'Segment Stack' and this method.
285
     * Note: Calls to this method should be balanced with calls to popSegment.
286
     *
287
     * @param ResourceProperty &$resourceProperty The current navigation property
288
     *                                            being written out
289
     *
290
     * @return bool true if a segment was pushed, false otherwise
291
     *
292
     * @throws InvalidOperationException If this function invoked with non-navigation
293
     *                                   property instance
294
     */
295 View Code Duplication
    protected function pushSegmentForNavigationProperty(ResourceProperty & $resourceProperty)
296
    {
297
        if (ResourceTypeKind::ENTITY == $resourceProperty->getTypeKind()) {
298
            assert(!empty($this->getStack()->getSegmentNames()), 'Segment names should not be empty');
299
            $currentResourceSetWrapper = $this->getCurrentResourceSetWrapper();
300
            $currentResourceType = $currentResourceSetWrapper->getResourceType();
301
            $currentResourceSetWrapper = $this->getService()
302
                ->getProvidersWrapper()
303
                ->getResourceSetWrapperForNavigationProperty(
304
                    $currentResourceSetWrapper,
305
                    $currentResourceType,
306
                    $resourceProperty
307
                );
308
309
            assert(!is_null($currentResourceSetWrapper), 'is_null($currentResourceSetWrapper)');
310
311
            return $this->_pushSegment($resourceProperty->getName(), $currentResourceSetWrapper);
312
        }
313
        throw new InvalidOperationException('pushSegmentForNavigationProperty should not be called with non-entity type');
314
    }
315
316
    /**
317
     * Gets collection of projection nodes under the current node.
318
     *
319
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
320
     *                                                        describing projections for the current segment, If this method returns
321
     *                                                        null it means no projections are to be applied and the entire resource
322
     *                                                        for the current segment should be serialized, If it returns non-null
323
     *                                                        only the properties described by the returned projection segments should
324
     *                                                        be serialized
325
     */
326
    protected function getProjectionNodes()
327
    {
328
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
329
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
330
            return null;
331
        }
332
333
        return $expandedProjectionNode->getChildNodes();
334
    }
335
336
    /**
337
     * Find a 'ExpandedProjectionNode' instance in the projection tree
338
     * which describes the current segment.
339
     *
340
     * @return ExpandedProjectionNode|null
341
     */
342 View Code Duplication
    protected function getCurrentExpandedProjectionNode()
343
    {
344
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
345
        if (is_null($expandedProjectionNode)) {
346
            return null;
347
        } else {
348
            $segmentNames = $this->getStack()->getSegmentNames();
349
            $depth = count($segmentNames);
350
            // $depth == 1 means serialization of root entry
351
            //(the resource identified by resource path) is going on,
352
            //so control won't get into the below for loop.
353
            //we will directly return the root node,
354
            //which is 'ExpandedProjectionNode'
355
            // for resource identified by resource path.
356
            if ($depth != 0) {
357
                for ($i = 1; $i < $depth; ++$i) {
358
                    $expandedProjectionNode
359
                        = $expandedProjectionNode->findNode($segmentNames[$i]);
360
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
361
                    assert(
362
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
363
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
364
                    );
365
                }
366
            }
367
        }
368
369
        return $expandedProjectionNode;
370
    }
371
372
    /**
373
     * Check whether to expand a navigation property or not.
374
     *
375
     * @param string $navigationPropertyName Name of naviagtion property in question
376
     *
377
     * @return bool True if the given navigation should be
378
     *              explanded otherwise false
379
     */
380
    protected function shouldExpandSegment($navigationPropertyName)
381
    {
382
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
383
        if (is_null($expandedProjectionNode)) {
384
            return false;
385
        }
386
387
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
388
389
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
390
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
391
    }
392
393
    /**
394
     * Pushes information about the segment that is going to be serialized
395
     * to the 'Segment Stack'.
396
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
397
     * 'Segment Stack' and this method.
398
     * Note: Calls to this method should be balanced with calls to popSegment.
399
     *
400
     * @param string             $segmentName         Name of segment to push
401
     * @param ResourceSetWrapper &$resourceSetWrapper The resource set
402
     *                                                wrapper to push
403
     *
404
     * @return bool true if the segment was push, false otherwise
405
     */
406
    private function _pushSegment($segmentName, ResourceSetWrapper & $resourceSetWrapper)
407
    {
408
        // Even though there is no expand in the request URI, still we need to push
409
        // the segment information if we need to count
410
        //the number of entities written.
411
        // After serializing each entity we should check the count to see whether
412
        // we serialized more entities than configured
413
        //(page size, maxResultPerCollection).
414
        // But we will not do this check since library is doing paging and never
415
        // accumulate entities more than configured.
416
417
        return $this->getStack()->pushSegment($segmentName, $resourceSetWrapper);
418
    }
419
420
    /**
421
     * Get next page link from the given entity instance.
422
     *
423
     * @param mixed  &$lastObject Last object serialized to be
424
     *                            used for generating $skiptoken
425
     * @param string $absoluteUri Absolute response URI
426
     *
427
     * @return ODataLink for the link for next page
428
     */
429
    protected function getNextLinkUri(&$lastObject, $absoluteUri)
430
    {
431
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
432
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
433
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
434
        assert(!is_null($skipToken), '!is_null($skipToken)');
435
        $queryParameterString = null;
0 ignored issues
show
Unused Code introduced by
$queryParameterString is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
436
        if ($this->isRootResourceSet()) {
437
            $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
438
        } else {
439
            $queryParameterString = $this->getNextPageLinkQueryParametersForExpandedResourceSet();
440
        }
441
442
        $queryParameterString .= '$skip=' . $skipToken;
443
        $odataLink = new ODataLink();
444
        $odataLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
445
        $odataLink->url = rtrim($absoluteUri, '/') . '?' . $queryParameterString;
446
447
        return $odataLink;
448
    }
449
450
    /**
451
     * Builds the string corresponding to query parameters for top level results
452
     * (result set identified by the resource path) to be put in next page link.
453
     *
454
     * @return string|null string representing the query parameters in the URI
455
     *                     query parameter format, NULL if there
456
     *                     is no query parameters
457
     *                     required for the next link of top level result set
458
     */
459
    protected function getNextPageLinkQueryParametersForRootResourceSet()
460
    {
461
        $queryParameterString = null;
462
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
463
            ODataConstants::HTTPQUERY_STRING_EXPAND,
464
            ODataConstants::HTTPQUERY_STRING_ORDERBY,
465
            ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
466
            ODataConstants::HTTPQUERY_STRING_SELECT] as $queryOption) {
467
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
468
            if (!is_null($value)) {
469
                if (!is_null($queryParameterString)) {
470
                    $queryParameterString = $queryParameterString . '&';
471
                }
472
473
                $queryParameterString .= $queryOption . '=' . $value;
474
            }
475
        }
476
477
        $topCountValue = $this->getRequest()->getTopOptionCount();
478
        if (!is_null($topCountValue)) {
479
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
480
            if (!is_null($queryParameterString)) {
481
                $queryParameterString .= '&';
482
            }
483
484
            $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
485
        }
486
487
        if (!is_null($queryParameterString)) {
488
            $queryParameterString .= '&';
489
        }
490
491
        return $queryParameterString;
492
    }
493
494
    /**
495
     * Builds the string corresponding to query parameters for current expanded
496
     * results to be put in next page link.
497
     *
498
     * @return string|null string representing the $select and $expand parameters
499
     *                     in the URI query parameter format, NULL if there is no
500
     *                     query parameters ($expand and/select) required for the
501
     *                     next link of expanded result set
502
     */
503
    protected function getNextPageLinkQueryParametersForExpandedResourceSet()
504
    {
505
        $queryParameterString = null;
506
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
507
        if (!is_null($expandedProjectionNode)) {
508
            $pathSegments = array();
509
            $selectionPaths = null;
510
            $expansionPaths = null;
511
            $foundSelections = false;
512
            $foundExpansions = false;
513
            $this->_buildSelectionAndExpansionPathsForNode(
514
                $pathSegments,
515
                $selectionPaths,
516
                $expansionPaths,
517
                $expandedProjectionNode,
518
                $foundSelections,
519
                $foundExpansions
520
            );
521
522
            if ($foundSelections && $expandedProjectionNode->canSelectAllProperties()) {
523
                $this->_appendSelectionOrExpandPath($selectionPaths, $pathSegments, '*');
524
            }
525
526
            if (!is_null($selectionPaths)) {
527
                $queryParameterString = '$select=' . $selectionPaths;
528
            }
529
530
            if (!is_null($expansionPaths)) {
531
                if (!is_null($queryParameterString)) {
532
                    $queryParameterString .= '&';
533
                }
534
535
                $queryParameterString = '$expand=' . $expansionPaths;
536
            }
537
538
            if (!is_null($queryParameterString)) {
539
                $queryParameterString .= '&';
540
            }
541
        }
542
543
        return $queryParameterString;
544
    }
545
546
    /**
547
     * Wheter next link is needed for the current resource set (feed)
548
     * being serialized.
549
     *
550
     * @param int $resultSetCount Number of entries in the current
551
     *                            resource set
552
     *
553
     * @return bool true if the feed must have a next page link
554
     */
555
    protected function needNextPageLink($resultSetCount)
556
    {
557
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
558
        $recursionLevel = count($this->getStack()->getSegmentNames());
559
        //$this->assert($recursionLevel != 0, '$recursionLevel != 0');
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
560
        $pageSize = $currentResourceSet->getResourceSetPageSize();
561
562
        if ($recursionLevel == 1) {
563
            //presence of $top option affect next link for root container
564
            $topValueCount = $this->getRequest()->getTopOptionCount();
565
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
566
                return false;
567
            }
568
        }
569
570
        return $resultSetCount == $pageSize;
571
    }
572
573
    /**
574
     * Pops segment information from the 'Segment Stack'
575
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
576
     * 'Segment Stack' and this method.
577
     * Note: Calls to this method should be balanced with previous
578
     * calls to _pushSegment.
579
     *
580
     * @param bool $needPop Is a pop required. Only true if last
581
     *                      push was successful
582
     *
583
     * @throws InvalidOperationException If found un-balanced call with _pushSegment
584
     */
585
    protected function popSegment($needPop)
586
    {
587
        $this->getStack()->popSegment($needPop);
588
    }
589
590
    /**
591
     * Recursive metod to build $expand and $select paths for a specified node.
592
     *
593
     * @param string[]               &$parentPathSegments     Array of path
594
     *                                                        segments which leads
595
     *                                                        up to (including)
596
     *                                                        the segment
597
     *                                                        represented by
598
     *                                                        $expandedProjectionNode
599
     * @param string[]               &$selectionPaths         The string which
600
     *                                                        holds projection
601
     *                                                        path segment
602
     *                                                        seperated by comma,
603
     *                                                        On return this argument
604
     *                                                        will be updated with
605
     *                                                        the selection path
606
     *                                                        segments under
607
     *                                                        this node
608
     * @param string[]               &$expansionPaths         The string which holds
609
     *                                                        expansion path segment
610
     *                                                        seperated by comma.
611
     *                                                        On return this argument
612
     *                                                        will be updated with
613
     *                                                        the expand path
614
     *                                                        segments under
615
     *                                                        this node
616
     * @param ExpandedProjectionNode &$expandedProjectionNode The expanded node for
617
     *                                                        which expansion
618
     *                                                        and selection path
619
     *                                                        to be build
620
     * @param bool                   &$foundSelections        On return, this
621
     *                                                        argument will hold
622
     *                                                        true if any selection
623
     *                                                        defined under this node
624
     *                                                        false otherwise
625
     * @param bool                   &$foundExpansions        On return, this
626
     *                                                        argument will hold
627
     *                                                        true if any expansion
628
     *                                                        defined under this node
629
     *                                                        false otherwise
630
     * @param bool                   $foundSelections
631
     * @param bool                   $foundExpansions
632
     */
633
    private function _buildSelectionAndExpansionPathsForNode(
634
        &$parentPathSegments,
635
        &$selectionPaths,
636
        &$expansionPaths,
637
        ExpandedProjectionNode & $expandedProjectionNode,
638
        &$foundSelections,
639
        &$foundExpansions
640
    ) {
641
        $foundSelections = false;
642
        $foundExpansions = false;
643
        $foundSelectionOnChild = false;
644
        $foundExpansionOnChild = false;
645
        $expandedChildrenNeededToBeSelected = array();
646
        foreach ($expandedProjectionNode->getChildNodes() as $childNode) {
647
            if (!($childNode instanceof ExpandedProjectionNode)) {
648
                $foundSelections = true;
649
                $this->_appendSelectionOrExpandPath(
650
                    $selectionPaths,
651
                    $parentPathSegments,
652
                    $childNode->getPropertyName()
653
                );
654
            } else {
655
                $foundExpansions = true;
656
                array_push($parentPathSegments, $childNode->getPropertyName());
657
                $this->_buildSelectionAndExpansionPathsForNode(
658
                    $parentPathSegments,
659
                    $selectionPaths,
660
                    $expansionPaths,
661
                    $childNode,
662
                    $foundSelectionOnChild,
663
                    $foundExpansionOnChild
664
                );
665
                array_pop($parentPathSegments);
666
                if ($childNode->canSelectAllProperties()) {
667
                    if ($foundSelectionOnChild) {
668
                        $this->_appendSelectionOrExpandPath(
669
                            $selectionPaths,
670
                            $parentPathSegments,
671
                            $childNode->getPropertyName() . '/*'
672
                        );
673
                    } else {
674
                        $expandedChildrenNeededToBeSelected[] = $childNode;
675
                    }
676
                }
677
            }
678
679
            $foundSelections |= $foundSelectionOnChild;
680
            if (!$foundExpansionOnChild) {
681
                $this->_appendSelectionOrExpandPath(
682
                    $expansionPaths,
683
                    $parentPathSegments,
684
                    $childNode->getPropertyName()
685
                );
686
            }
687
        }
688
689
        if (!$expandedProjectionNode->canSelectAllProperties() || $foundSelections) {
690
            foreach ($expandedChildrenNeededToBeSelected as $childToProject) {
691
                $this->_appendSelectionOrExpandPath(
692
                    $selectionPaths,
693
                    $parentPathSegments,
694
                    $childNode->getPropertyName()
0 ignored issues
show
Bug introduced by
The variable $childNode seems to be defined by a foreach iteration on line 646. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
695
                );
696
                $foundSelections = true;
697
            }
698
        }
699
    }
700
701
    /**
702
     * Append the given path to $expand or $select path list.
703
     *
704
     * @param string   &$path               The $expand or $select path list to which to append the given path
705
     * @param string[] &$parentPathSegments The list of path up to the $segmentToAppend
706
     * @param string   $segmentToAppend     The last segment of the path
707
     */
708
    private function _appendSelectionOrExpandPath(&$path, &$parentPathSegments, $segmentToAppend)
709
    {
710
        if (!is_null($path)) {
711
            $path .= ', ';
712
        }
713
714
        foreach ($parentPathSegments as $parentPathSegment) {
715
            $path .= $parentPathSegment . '/';
716
        }
717
718
        $path .= $segmentToAppend;
719
    }
720
}
721