Completed
Pull Request — master (#61)
by Alex
03:53
created

ObjectModelSerializerBase   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 688
Duplicated Lines 8.28 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 66
lcom 1
cbo 16
dl 57
loc 688
rs 1.4541
c 4
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getProjectionNodes() 0 9 3
C _buildSelectionAndExpansionPathsForNode() 0 67 9
A _appendSelectionOrExpandPath() 0 12 3
A __construct() 0 9 1
A isRootResourceSet() 0 5 2
A pushSegmentForNavigationProperty() 20 20 2
B getCurrentExpandedProjectionNode() 29 29 4
A shouldExpandSegment() 0 12 2
A popSegment() 0 4 1
A getCurrentResourceSetWrapper() 0 6 2
A pushSegmentForRoot() 0 8 1
A _pushSegment() 0 13 1
A getNextLinkUri() 0 20 2
C getNextPageLinkQueryParametersForRootResourceSet() 0 34 7
C getNextPageLinkQueryParametersForExpandedResourceSet() 0 42 8
A needNextPageLink() 0 17 4
A getRequest() 0 5 1
A setRequest() 0 5 1
A getService() 0 4 1
A getStack() 0 4 1
B getEntryInstanceKey() 0 25 3
A getPropertyValue() 0 13 2
B getETagForEntry() 8 28 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ObjectModelSerializerBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ObjectModelSerializerBase, and based on these observations, apply Extract Interface, too.

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
            return \POData\Common\ReflectionHandler::getProperty($entity, $resourceProperty->getName());
179
        } catch (\ReflectionException $reflectionException) {
180
            throw ODataException::createInternalServerError(
181
                Messages::objectModelSerializerFailedToAccessProperty(
182
                    $resourceProperty->getName(),
183
                    $resourceType->getName()
184
                )
185
            );
186
        }
187
    }
188
189
    /**
190
     * Resource set wrapper for the resource being serialized.
191
     *
192
     * @return ResourceSetWrapper
193
     */
194
    protected function getCurrentResourceSetWrapper()
195
    {
196
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
197
        $count = count($segmentWrappers);
198
        return 0 == $count ? $this->getRequest()->getTargetResourceSetWrapper() : $segmentWrappers[$count - 1];
199
    }
200
201
    /**
202
     * Whether the current resource set is root resource set.
203
     *
204
     * @return bool true if the current resource set root container else
205
     *              false
206
     */
207
    protected function isRootResourceSet()
208
    {
209
        $segmentWrappers = $this->getStack()->getSegmentWrappers();
210
        return empty($segmentWrappers) || 1 == count($segmentWrappers);
211
    }
212
213
    /**
214
     * Returns the etag for the given resource.
215
     *
216
     * @param mixed        $entryObject  Resource for which etag value
217
     *                                   needs to be returned
218
     * @param ResourceType $resourceType Resource type of the $entryObject
219
     *
220
     * @return string|null ETag value for the given resource
221
     *                     (with values encoded for use in a URI)
222
     *                     if there are etag properties, NULL if there is no etag property
223
     */
224
    protected function getETagForEntry($entryObject, ResourceType $resourceType)
225
    {
226
        $eTag = null;
227
        $comma = null;
228
        foreach ($resourceType->getETagProperties() as $eTagProperty) {
229
            $type = $eTagProperty->getInstanceType();
230
            assert(!is_null($type) && $type instanceof IType, 'is_null($type) || $type not instanceof IType');
231
            $value = $this->getPropertyValue($entryObject, $resourceType, $eTagProperty);
232
            if (is_null($value)) {
233
                $eTag = $eTag . $comma . 'null';
234
            } else {
235
                $eTag = $eTag . $comma . $type->convertToOData($value);
236
            }
237
238
            $comma = ',';
239
        }
240
241 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...
242
            // If eTag is made up of datetime or string properties then the above
243
            // IType::converToOData will perform utf8 and url encode. But we don't
244
            // want this for eTag value.
245
            $eTag = urldecode(utf8_decode($eTag));
246
247
            return ODataConstants::HTTP_WEAK_ETAG_PREFIX . rtrim($eTag, ',') . '"';
248
        }
249
250
        return null;
251
    }
252
253
    /**
254
     * Pushes a segment for the root of the tree being written out
255
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
256
     * 'Segment Stack' and this method.
257
     * Note: Calls to this method should be balanced with calls to popSegment.
258
     *
259
     * @return bool true if the segment was pushed, false otherwise
260
     */
261
    protected function pushSegmentForRoot()
262
    {
263
        $segmentName = $this->getRequest()->getContainerName();
264
        $segmentResourceSetWrapper = $this->getRequest()->getTargetResourceSetWrapper();
265
        assert(null != $segmentResourceSetWrapper, "Segment resource set wrapper must not be null");
266
267
        return $this->_pushSegment($segmentName, $segmentResourceSetWrapper);
268
    }
269
270
    /**
271
     * Pushes a segment for the current navigation property being written out.
272
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
273
     * 'Segment Stack' and this method.
274
     * Note: Calls to this method should be balanced with calls to popSegment.
275
     *
276
     * @param ResourceProperty &$resourceProperty The current navigation property
277
     *                                            being written out
278
     *
279
     * @return bool true if a segment was pushed, false otherwise
280
     *
281
     * @throws InvalidOperationException If this function invoked with non-navigation
282
     *                                   property instance
283
     */
284 View Code Duplication
    protected function pushSegmentForNavigationProperty(ResourceProperty & $resourceProperty)
285
    {
286
        if (ResourceTypeKind::ENTITY == $resourceProperty->getTypeKind()) {
287
            assert(!empty($this->getStack()->getSegmentNames()), 'Segment names should not be empty');
288
            $currentResourceSetWrapper = $this->getCurrentResourceSetWrapper();
289
            $currentResourceType = $currentResourceSetWrapper->getResourceType();
290
            $currentResourceSetWrapper = $this->getService()
291
                ->getProvidersWrapper()
292
                ->getResourceSetWrapperForNavigationProperty(
293
                    $currentResourceSetWrapper,
294
                    $currentResourceType,
295
                    $resourceProperty
296
                );
297
298
            assert(!is_null($currentResourceSetWrapper), 'is_null($currentResourceSetWrapper)');
299
300
            return $this->_pushSegment($resourceProperty->getName(), $currentResourceSetWrapper);
301
        }
302
        throw new InvalidOperationException('pushSegmentForNavigationProperty should not be called with non-entity type');
303
    }
304
305
    /**
306
     * Gets collection of projection nodes under the current node.
307
     *
308
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
309
     *                                                        describing projections for the current segment, If this method returns
310
     *                                                        null it means no projections are to be applied and the entire resource
311
     *                                                        for the current segment should be serialized, If it returns non-null
312
     *                                                        only the properties described by the returned projection segments should
313
     *                                                        be serialized
314
     */
315
    protected function getProjectionNodes()
316
    {
317
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
318
        if (is_null($expandedProjectionNode) || $expandedProjectionNode->canSelectAllProperties()) {
319
            return null;
320
        }
321
322
        return $expandedProjectionNode->getChildNodes();
323
    }
324
325
    /**
326
     * Find a 'ExpandedProjectionNode' instance in the projection tree
327
     * which describes the current segment.
328
     *
329
     * @return ExpandedProjectionNode|null
330
     */
331 View Code Duplication
    protected function getCurrentExpandedProjectionNode()
332
    {
333
        $expandedProjectionNode = $this->getRequest()->getRootProjectionNode();
334
        if (is_null($expandedProjectionNode)) {
335
            return null;
336
        } else {
337
            $segmentNames = $this->getStack()->getSegmentNames();
338
            $depth = count($segmentNames);
339
            // $depth == 1 means serialization of root entry
340
            //(the resource identified by resource path) is going on,
341
            //so control won't get into the below for loop.
342
            //we will directly return the root node,
343
            //which is 'ExpandedProjectionNode'
344
            // for resource identified by resource path.
345
            if ($depth != 0) {
346
                for ($i = 1; $i < $depth; ++$i) {
347
                    $expandedProjectionNode
348
                        = $expandedProjectionNode->findNode($segmentNames[$i]);
349
                    assert(!is_null($expandedProjectionNode), 'is_null($expandedProjectionNode)');
350
                    assert(
351
                        $expandedProjectionNode instanceof ExpandedProjectionNode,
352
                        '$expandedProjectionNode not instanceof ExpandedProjectionNode'
353
                    );
354
                }
355
            }
356
        }
357
358
        return $expandedProjectionNode;
359
    }
360
361
    /**
362
     * Check whether to expand a navigation property or not.
363
     *
364
     * @param string $navigationPropertyName Name of naviagtion property in question
365
     *
366
     * @return bool True if the given navigation should be
367
     *              explanded otherwise false
368
     */
369
    protected function shouldExpandSegment($navigationPropertyName)
370
    {
371
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
372
        if (is_null($expandedProjectionNode)) {
373
            return false;
374
        }
375
376
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
377
378
        // null is a valid input to an instanceof call as of PHP 5.6 - will always return false
379
        return $expandedProjectionNode instanceof ExpandedProjectionNode;
380
    }
381
382
    /**
383
     * Pushes information about the segment that is going to be serialized
384
     * to the 'Segment Stack'.
385
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
386
     * 'Segment Stack' and this method.
387
     * Note: Calls to this method should be balanced with calls to popSegment.
388
     *
389
     * @param string             $segmentName         Name of segment to push
390
     * @param ResourceSetWrapper &$resourceSetWrapper The resource set
391
     *                                                wrapper to push
392
     *
393
     * @return bool true if the segment was push, false otherwise
394
     */
395
    private function _pushSegment($segmentName, ResourceSetWrapper & $resourceSetWrapper)
396
    {
397
        // Even though there is no expand in the request URI, still we need to push
398
        // the segment information if we need to count
399
        //the number of entities written.
400
        // After serializing each entity we should check the count to see whether
401
        // we serialized more entities than configured
402
        //(page size, maxResultPerCollection).
403
        // But we will not do this check since library is doing paging and never
404
        // accumulate entities more than configured.
405
406
        return $this->getStack()->pushSegment($segmentName, $resourceSetWrapper);
407
    }
408
409
    /**
410
     * Get next page link from the given entity instance.
411
     *
412
     * @param mixed  &$lastObject Last object serialized to be
413
     *                            used for generating $skiptoken
414
     * @param string $absoluteUri Absolute response URI
415
     *
416
     * @return ODataLink for the link for next page
417
     */
418
    protected function getNextLinkUri(&$lastObject, $absoluteUri)
419
    {
420
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
421
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
422
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
423
        assert(!is_null($skipToken), '!is_null($skipToken)');
424
        $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...
425
        if ($this->isRootResourceSet()) {
426
            $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
427
        } else {
428
            $queryParameterString = $this->getNextPageLinkQueryParametersForExpandedResourceSet();
429
        }
430
431
        $queryParameterString .= '$skip=' . $skipToken;
432
        $odataLink = new ODataLink();
433
        $odataLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
434
        $odataLink->url = rtrim($absoluteUri, '/') . '?' . $queryParameterString;
435
436
        return $odataLink;
437
    }
438
439
    /**
440
     * Builds the string corresponding to query parameters for top level results
441
     * (result set identified by the resource path) to be put in next page link.
442
     *
443
     * @return string|null string representing the query parameters in the URI
444
     *                     query parameter format, NULL if there
445
     *                     is no query parameters
446
     *                     required for the next link of top level result set
447
     */
448
    protected function getNextPageLinkQueryParametersForRootResourceSet()
449
    {
450
        $queryParameterString = null;
451
        foreach ([ODataConstants::HTTPQUERY_STRING_FILTER,
452
            ODataConstants::HTTPQUERY_STRING_EXPAND,
453
            ODataConstants::HTTPQUERY_STRING_ORDERBY,
454
            ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
455
            ODataConstants::HTTPQUERY_STRING_SELECT] as $queryOption) {
456
            $value = $this->getService()->getHost()->getQueryStringItem($queryOption);
457
            if (!is_null($value)) {
458
                if (!is_null($queryParameterString)) {
459
                    $queryParameterString = $queryParameterString . '&';
460
                }
461
462
                $queryParameterString .= $queryOption . '=' . $value;
463
            }
464
        }
465
466
        $topCountValue = $this->getRequest()->getTopOptionCount();
467
        if (!is_null($topCountValue)) {
468
            $remainingCount = $topCountValue - $this->getRequest()->getTopCount();
469
            if (!is_null($queryParameterString)) {
470
                $queryParameterString .= '&';
471
            }
472
473
            $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
474
        }
475
476
        if (!is_null($queryParameterString)) {
477
            $queryParameterString .= '&';
478
        }
479
480
        return $queryParameterString;
481
    }
482
483
    /**
484
     * Builds the string corresponding to query parameters for current expanded
485
     * results to be put in next page link.
486
     *
487
     * @return string|null string representing the $select and $expand parameters
488
     *                     in the URI query parameter format, NULL if there is no
489
     *                     query parameters ($expand and/select) required for the
490
     *                     next link of expanded result set
491
     */
492
    protected function getNextPageLinkQueryParametersForExpandedResourceSet()
493
    {
494
        $queryParameterString = null;
495
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
496
        if (!is_null($expandedProjectionNode)) {
497
            $pathSegments = array();
498
            $selectionPaths = null;
499
            $expansionPaths = null;
500
            $foundSelections = false;
501
            $foundExpansions = false;
502
            $this->_buildSelectionAndExpansionPathsForNode(
503
                $pathSegments,
504
                $selectionPaths,
505
                $expansionPaths,
506
                $expandedProjectionNode,
507
                $foundSelections,
508
                $foundExpansions
509
            );
510
511
            if ($foundSelections && $expandedProjectionNode->canSelectAllProperties()) {
512
                $this->_appendSelectionOrExpandPath($selectionPaths, $pathSegments, '*');
513
            }
514
515
            if (!is_null($selectionPaths)) {
516
                $queryParameterString = '$select=' . $selectionPaths;
517
            }
518
519
            if (!is_null($expansionPaths)) {
520
                if (!is_null($queryParameterString)) {
521
                    $queryParameterString .= '&';
522
                }
523
524
                $queryParameterString = '$expand=' . $expansionPaths;
525
            }
526
527
            if (!is_null($queryParameterString)) {
528
                $queryParameterString .= '&';
529
            }
530
        }
531
532
        return $queryParameterString;
533
    }
534
535
    /**
536
     * Wheter next link is needed for the current resource set (feed)
537
     * being serialized.
538
     *
539
     * @param int $resultSetCount Number of entries in the current
540
     *                            resource set
541
     *
542
     * @return bool true if the feed must have a next page link
543
     */
544
    protected function needNextPageLink($resultSetCount)
545
    {
546
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
547
        $recursionLevel = count($this->getStack()->getSegmentNames());
548
        //$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...
549
        $pageSize = $currentResourceSet->getResourceSetPageSize();
550
551
        if ($recursionLevel == 1) {
552
            //presence of $top option affect next link for root container
553
            $topValueCount = $this->getRequest()->getTopOptionCount();
554
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
555
                return false;
556
            }
557
        }
558
559
        return $resultSetCount == $pageSize;
560
    }
561
562
    /**
563
     * Pops segment information from the 'Segment Stack'
564
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
565
     * 'Segment Stack' and this method.
566
     * Note: Calls to this method should be balanced with previous
567
     * calls to _pushSegment.
568
     *
569
     * @param bool $needPop Is a pop required. Only true if last
570
     *                      push was successful
571
     *
572
     * @throws InvalidOperationException If found un-balanced call with _pushSegment
573
     */
574
    protected function popSegment($needPop)
575
    {
576
        $this->getStack()->popSegment($needPop);
577
    }
578
579
    /**
580
     * Recursive metod to build $expand and $select paths for a specified node.
581
     *
582
     * @param string[]               &$parentPathSegments     Array of path
583
     *                                                        segments which leads
584
     *                                                        up to (including)
585
     *                                                        the segment
586
     *                                                        represented by
587
     *                                                        $expandedProjectionNode
588
     * @param string[]               &$selectionPaths         The string which
589
     *                                                        holds projection
590
     *                                                        path segment
591
     *                                                        seperated by comma,
592
     *                                                        On return this argument
593
     *                                                        will be updated with
594
     *                                                        the selection path
595
     *                                                        segments under
596
     *                                                        this node
597
     * @param string[]               &$expansionPaths         The string which holds
598
     *                                                        expansion path segment
599
     *                                                        seperated by comma.
600
     *                                                        On return this argument
601
     *                                                        will be updated with
602
     *                                                        the expand path
603
     *                                                        segments under
604
     *                                                        this node
605
     * @param ExpandedProjectionNode &$expandedProjectionNode The expanded node for
606
     *                                                        which expansion
607
     *                                                        and selection path
608
     *                                                        to be build
609
     * @param bool                   &$foundSelections        On return, this
610
     *                                                        argument will hold
611
     *                                                        true if any selection
612
     *                                                        defined under this node
613
     *                                                        false otherwise
614
     * @param bool                   &$foundExpansions        On return, this
615
     *                                                        argument will hold
616
     *                                                        true if any expansion
617
     *                                                        defined under this node
618
     *                                                        false otherwise
619
     * @param bool                   $foundSelections
620
     * @param bool                   $foundExpansions
621
     */
622
    private function _buildSelectionAndExpansionPathsForNode(
623
        &$parentPathSegments,
624
        &$selectionPaths,
625
        &$expansionPaths,
626
        ExpandedProjectionNode & $expandedProjectionNode,
627
        &$foundSelections,
628
        &$foundExpansions
629
    ) {
630
        $foundSelections = false;
631
        $foundExpansions = false;
632
        $foundSelectionOnChild = false;
633
        $foundExpansionOnChild = false;
634
        $expandedChildrenNeededToBeSelected = array();
635
        foreach ($expandedProjectionNode->getChildNodes() as $childNode) {
636
            if (!($childNode instanceof ExpandedProjectionNode)) {
637
                $foundSelections = true;
638
                $this->_appendSelectionOrExpandPath(
639
                    $selectionPaths,
640
                    $parentPathSegments,
641
                    $childNode->getPropertyName()
642
                );
643
            } else {
644
                $foundExpansions = true;
645
                array_push($parentPathSegments, $childNode->getPropertyName());
646
                $this->_buildSelectionAndExpansionPathsForNode(
647
                    $parentPathSegments,
648
                    $selectionPaths,
649
                    $expansionPaths,
650
                    $childNode,
651
                    $foundSelectionOnChild,
652
                    $foundExpansionOnChild
653
                );
654
                array_pop($parentPathSegments);
655
                if ($childNode->canSelectAllProperties()) {
656
                    if ($foundSelectionOnChild) {
657
                        $this->_appendSelectionOrExpandPath(
658
                            $selectionPaths,
659
                            $parentPathSegments,
660
                            $childNode->getPropertyName() . '/*'
661
                        );
662
                    } else {
663
                        $expandedChildrenNeededToBeSelected[] = $childNode;
664
                    }
665
                }
666
            }
667
668
            $foundSelections |= $foundSelectionOnChild;
669
            if (!$foundExpansionOnChild) {
670
                $this->_appendSelectionOrExpandPath(
671
                    $expansionPaths,
672
                    $parentPathSegments,
673
                    $childNode->getPropertyName()
674
                );
675
            }
676
        }
677
678
        if (!$expandedProjectionNode->canSelectAllProperties() || $foundSelections) {
679
            foreach ($expandedChildrenNeededToBeSelected as $childToProject) {
680
                $this->_appendSelectionOrExpandPath(
681
                    $selectionPaths,
682
                    $parentPathSegments,
683
                    $childNode->getPropertyName()
0 ignored issues
show
Bug introduced by
The variable $childNode seems to be defined by a foreach iteration on line 635. 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...
684
                );
685
                $foundSelections = true;
686
            }
687
        }
688
    }
689
690
    /**
691
     * Append the given path to $expand or $select path list.
692
     *
693
     * @param string   &$path               The $expand or $select path list to which to append the given path
694
     * @param string[] &$parentPathSegments The list of path up to the $segmentToAppend
695
     * @param string   $segmentToAppend     The last segment of the path
696
     */
697
    private function _appendSelectionOrExpandPath(&$path, &$parentPathSegments, $segmentToAppend)
698
    {
699
        if (!is_null($path)) {
700
            $path .= ', ';
701
        }
702
703
        foreach ($parentPathSegments as $parentPathSegment) {
704
            $path .= $parentPathSegment . '/';
705
        }
706
707
        $path .= $segmentToAppend;
708
    }
709
}
710