ObjectModelSerializerBase   F
last analyzed

Complexity

Total Complexity 72

Size/Duplication

Total Lines 716
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 72
eloc 238
c 3
b 0
f 0
dl 0
loc 716
ccs 0
cts 251
cp 0
rs 2.64

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getCurrentExpandedProjectionNode() 0 30 4
A pushSegmentForNavigationProperty() 0 18 2
A pushSegmentForRoot() 0 5 1
A _appendSelectionOrExpandPath() 0 11 3
A getProjectionNodes() 0 10 3
A shouldExpandSegment() 0 9 3
A assert() 0 4 2
B getNextPageLinkQueryParametersForRootResourceSet() 0 35 7
A isRootResourceSet() 0 4 2
A __construct() 0 10 1
A getCurrentResourceSetWrapper() 0 7 2
B getNextPageLinkQueryParametersForExpandedResourceSet() 0 41 8
A _pushSegment() 0 27 3
A popSegment() 0 9 3
A getEntryInstanceKey() 0 25 4
A getPropertyValue() 0 5 2
B _buildSelectionAndExpansionPathsForNode() 0 59 9
A needNextPageLink() 0 16 5
A getNextLinkUri() 0 23 3
A getETagForEntry() 0 29 5

How to fix   Complexity   

Complex Class

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.

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
4
namespace POData\ObjectModel;
5
6
use POData\Common\ODataConstants;
7
use POData\IService;
8
use POData\Providers\Metadata\ResourceSetWrapper;
9
use POData\Providers\Metadata\ResourceProperty;
10
use POData\Providers\Metadata\ResourceTypeKind;
11
use POData\Providers\Metadata\ResourceType;
12
use POData\Providers\Metadata\Type\IType;
13
use POData\UriProcessor\RequestDescription;
14
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
15
use POData\Common\InvalidOperationException;
16
use POData\Common\ODataException;
17
use POData\Common\Messages;
18
19
/**
20
 * Class ObjectModelSerializerBase
21
 * @package POData\ObjectModel
22
 */
23
class ObjectModelSerializerBase
24
{
25
    /**
26
     * The service implementation.
27
     *
28
     * @var IService
29
     */
30
    protected $service;
31
32
    /**
33
     * Request description instance describes OData request the
34
     * the client has submitted and result of the request.
35
     *
36
     * @var RequestDescription
37
     */
38
    protected $request;
39
40
    /**
41
     * Collection of segment names
42
     * Remark: Read 'ObjectModelSerializerNotes.txt' for more
43
     * details about segment.
44
     *
45
     * @var string[]
46
     */
47
    private $_segmentNames;
48
49
    /**
50
     * Collection of segment ResourceSetWrapper instances
51
     * Remark: Read 'ObjectModelSerializerNotes.txt' for more
52
     * details about segment.
53
     *
54
     * @var ResourceSetWrapper[]
55
     */
56
    private $_segmentResourceSetWrappers;
57
58
    /**
59
     * Result counts for segments
60
     * Remark: Read 'ObjectModelSerializerNotes.txt' for more
61
     * details about segment.
62
     *
63
     * @var int[]
64
     */
65
    private $_segmentResultCounts;
66
67
    /**
68
     * Collection of complex type instances used for cycle detection.
69
     *
70
     * @var array
71
     */
72
    protected $complexTypeInstanceCollection;
73
74
    /**
75
     * Absolute service Uri.
76
     *
77
     * @var string
78
     */
79
    protected $absoluteServiceUri;
80
81
    /**
82
     * Absolute service Uri with slash.
83
     *
84
     * @var string
85
     */
86
    protected $absoluteServiceUriWithSlash;
87
88
    /**
89
     * @param IService $service Reference to the data service instance.
90
     * @param RequestDescription $request Type instance describing the client submitted request.
91
     *
92
     */
93
    protected function __construct(IService $service, RequestDescription $request)
94
    {
95
        $this->service = $service;
96
        $this->request = $request;
97
        $this->absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri()->getUrlAsString();
98
        $this->absoluteServiceUriWithSlash = rtrim($this->absoluteServiceUri, '/') . '/';
99
        $this->_segmentNames = array();
100
        $this->_segmentResourceSetWrappers = array();
101
        $this->_segmentResultCounts = array();
102
        $this->complexTypeInstanceCollection = array();
103
    }
104
105
    /**
106
     * Builds the key for the given entity instance.
107
     * Note: The generated key can be directly used in the uri,
108
     * this function will perform
109
     * required escaping of characters, for example:
110
     * Ships(ShipName='Antonio%20Moreno%20Taquer%C3%ADa',ShipID=123),
111
     * Note to method caller: Don't do urlencoding on
112
     * return value of this method as it already encoded.
113
     *
114
     * @param mixed        $entityInstance Entity instance for which key value needs to be prepared.
115
     * @param ResourceType $resourceType   Resource type instance containing metadata about the instance.
116
     * @param string       $containerName   Name of the entity set that the entity instance belongs to
117
     *                                      .
118
     *
119
     * @return string      Key for the given resource, with values encoded for use in a URI
120
     * .
121
     */
122
    protected function getEntryInstanceKey($entityInstance, ResourceType $resourceType, $containerName)
123
    {
124
        $keyProperties = $resourceType->getKeyProperties();
125
        $this->assert(count($keyProperties) != 0, 'count($keyProperties) != 0');
126
        $keyString = $containerName . '(';
127
        $comma = null;
128
        foreach ($keyProperties as $keyName => $resourceProperty) {
129
            $keyType = $resourceProperty->getInstanceType();
130
            if (!$keyType instanceof IType) {
131
                continue;
132
            }
133
            // $this->assert($keyType instanceof IType, '$keyType instanceof IType');
134
135
            $keyValue = $this->getPropertyValue($entityInstance, $resourceType, $resourceProperty);
136
            if (is_null($keyValue)) {
137
                throw ODataException::createInternalServerError(Messages::badQueryNullKeysAreNotSupported($resourceType->getName(), $keyName));
138
            }
139
140
            $keyValue = $keyType->convertToOData($keyValue);
141
            $keyString .= $comma . $keyName . '=' . $keyValue;
142
            $comma = ',';
143
        }
144
145
        $keyString .= ')';
146
        return $keyString;
147
    }
148
149
    /**
150
     * Get the value of a given property from an instance.
151
     *
152
     * @param mixed $entity Instance of a type which contains this property.
153
     * @param ResourceType $resourceType Resource type instance containing metadata about the instance.
154
     * @param ResourceProperty $resourceProperty Resource property instance containing metadata about the property whose value to be retrieved.
155
     *
156
     * @return mixed The value of the given property.
157
     *
158
     * @throws ODataException If reflection exception occurred while trying to access the property.
159
     *
160
     */
161
    protected function getPropertyValue($entity, ResourceType $resourceType, ResourceProperty $resourceProperty)
0 ignored issues
show
Unused Code introduced by
The parameter $resourceType is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

161
    protected function getPropertyValue($entity, /** @scrutinizer ignore-unused */ ResourceType $resourceType, ResourceProperty $resourceProperty)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
162
    {
163
        if (is_array($entity)) return $entity[$resourceProperty->getName()];
164
165
        return $entity->{$resourceProperty->getName()};
166
    }
167
168
    /**
169
     * Resource set wrapper for the resource being serialized.
170
     *
171
     * @return ResourceSetWrapper
172
     */
173
    protected function getCurrentResourceSetWrapper()
174
    {
175
        $count = count($this->_segmentResourceSetWrappers);
176
        if ($count == 0) {
177
            return $this->request->getTargetResourceSetWrapper();
178
        } else {
179
            return $this->_segmentResourceSetWrappers[$count - 1];
180
        }
181
    }
182
183
    /**
184
     * Whether the current resource set is root resource set.
185
     *
186
     * @return boolean true if the current resource set root container else
187
     *                 false.
188
     */
189
    protected function isRootResourceSet()
190
    {
191
        return empty($this->_segmentResourceSetWrappers)
192
                || count($this->_segmentResourceSetWrappers) == 1;
193
    }
194
195
    /**
196
     * Returns the etag for the given resource.
197
     *
198
     * @param mixed        $entryObject  Resource for which etag value
199
     *                                    needs to be returned
200
     * @param ResourceType $resourceType Resource type of the $entryObject
201
     *
202
     * @return string|null ETag value for the given resource
203
     * (with values encoded for use in a URI)
204
     * if there are etag properties, NULL if there is no etag property.
205
     */
206
    protected function getETagForEntry($entryObject, ResourceType $resourceType)
207
    {
208
        $eTag = null;
209
        $comma = null;
210
        foreach ($resourceType->getETagProperties() as $eTagProperty) {
211
            $type = $eTagProperty->getInstanceType();
212
            $this->assert(
213
                !is_null($type) && $type instanceof IType,
214
                '!is_null($type) && $type instanceof IType'
215
            );
216
            $value = $this->getPropertyValue($entryObject, $resourceType, $eTagProperty);
217
            if (is_null($value)) {
218
                $eTag = $eTag . $comma . 'null';
219
            } else {
220
                $eTag = $eTag . $comma . $type->convertToOData($value);
0 ignored issues
show
Bug introduced by
The method convertToOData() does not exist on ReflectionClass. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

220
                $eTag = $eTag . $comma . $type->/** @scrutinizer ignore-call */ convertToOData($value);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

653
                    /** @scrutinizer ignore-type */ $selectionPaths,
Loading history...
654
                    $parentPathSegments,
655
                    $childNode->getPropertyName()
656
                );
657
            } else {
658
                $foundExpansions = true;
659
                array_push($parentPathSegments, $childNode->getPropertyName());
660
                $this->_buildSelectionAndExpansionPathsForNode(
661
                    $parentPathSegments,
662
                    $selectionPaths, $expansionPaths,
663
                    $childNode, $foundSelectionOnChild,
664
                    $foundExpansionOnChild
665
                );
666
                array_pop($parentPathSegments);
667
                if ($childNode->canSelectAllProperties()) {
668
                    if ($foundSelectionOnChild) {
669
                        $this->_appendSelectionOrExpandPath(
670
                            $selectionPaths,
671
                            $parentPathSegments,
672
                            $childNode->getPropertyName() . '/*'
673
                        );
674
                    } else {
675
                        $expandedChildrenNeededToBeSelected[] = $childNode;
676
                    }
677
                }
678
            }
679
680
            $foundSelections |= $foundSelectionOnChild;
681
            if (!$foundExpansionOnChild) {
682
                $this->_appendSelectionOrExpandPath(
683
                    $expansionPaths,
684
                    $parentPathSegments,
685
                    $childNode->getPropertyName()
686
                );
687
            }
688
        }
689
690
        if (!$expandedProjectionNode->canSelectAllProperties() || $foundSelections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $foundSelections of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
691
            foreach ($expandedChildrenNeededToBeSelected as $childToProject) {
692
                $this->_appendSelectionOrExpandPath(
693
                    $selectionPaths,
694
                    $parentPathSegments,
695
                    $childNode->getPropertyName()
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $childNode seems to be defined by a foreach iteration on line 649. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
696
                );
697
                $foundSelections = true;
698
            }
699
        }
700
    }
701
702
    /**
703
     * Append the given path to $expand or $select path list.
704
     *
705
     * @param string        &$path  The $expand or $select path list to which to append the given path.
706
     * @param string[] &$parentPathSegments The list of path up to the $segmentToAppend.
707
     * @param string        $segmentToAppend     The last segment of the path.
708
     *
709
     * @return void
710
     */
711
    private function _appendSelectionOrExpandPath(&$path, &$parentPathSegments, $segmentToAppend)
712
    {
713
        if (!is_null($path)) {
0 ignored issues
show
introduced by
The condition is_null($path) is always false.
Loading history...
714
            $path .= ', ';
715
        }
716
717
        foreach ($parentPathSegments as $parentPathSegment) {
718
            $path .= $parentPathSegment . '/';
719
        }
720
721
        $path .= $segmentToAppend;
722
    }
723
724
    /**
725
     * Assert that the given condition is true.
726
     *
727
     * @param boolean $condition         Condition to be asserted.
728
     * @param string  $conditionAsString String containing message incase
729
     *                                   if assertion fails.
730
     *
731
     * @throws InvalidOperationException Incase if assertion failes.
732
     *
733
     * @return void
734
     */
735
    protected function assert($condition, $conditionAsString)
736
    {
737
        if (!$condition) {
738
            throw new InvalidOperationException("Unexpected state, expecting $conditionAsString");
739
        }
740
    }
741
}
742