Test Setup Failed
Push — master ( 3debc5...181c40 )
by Alex
03:18
created

ObjectModelSerializerBase   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 701
Duplicated Lines 9.13 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
dl 64
loc 701
c 0
b 0
f 0
wmc 67
lcom 1
cbo 16
rs 1.0434

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A setRequest() 0 5 1
A getRequest() 0 6 1
A getService() 0 4 1
A setService() 0 6 1
A getStack() 0 4 1
B getEntryInstanceKey() 0 25 3
A getPropertyValue() 0 13 2
A getCurrentResourceSetWrapper() 7 7 2
A isRootResourceSet() 0 6 2
B getETagForEntry() 8 26 5
A pushSegmentForRoot() 0 8 1
A pushSegmentForNavigationProperty() 20 20 2
A getProjectionNodes() 0 9 3
B getCurrentExpandedProjectionNode() 29 29 4
A shouldExpandSegment() 0 12 2
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 popSegment() 0 4 1
C _buildSelectionAndExpansionPathsForNode() 0 67 9
A _appendSelectionOrExpandPath() 0 12 3

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