Completed
Push — master ( 9dec22...41f187 )
by Bálint
02:44
created

ObjectModelSerializerBase::getNextLinkUri()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
228
                $eTag = $eTag . $comma. 'null';
229
            } else {
230
                $eTag = $eTag . $comma . $type->convertToOData($value);
0 ignored issues
show
Bug introduced by
The method convertToOData does only exist in POData\Providers\Metadata\Type\IType, but not in ReflectionClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
231
            }
232
233
            $comma = ',';
234
        }
235
236 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...
237
            // If eTag is made up of datetime or string properties then the above
238
            // IType::converToOData will perform utf8 and url encode. But we don't
239
            // want this for eTag value.
240
            $eTag = urldecode(utf8_decode($eTag));
241
            return ODataConstants::HTTP_WEAK_ETAG_PREFIX . rtrim($eTag, ',') . '"';
242
        }
243
244
        return null;
245
    }
246
247
    /**
248
     * Pushes a segment for the root of the tree being written out
249
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
250
     * 'Segment Stack' and this method.
251
     * Note: Calls to this method should be balanced with calls to popSegment.
252
     *
253
     * @return bool true if the segment was pushed, false otherwise.
254
     */
255
    protected function pushSegmentForRoot()
256
    {
257
        $segmentName = $this->request->getContainerName();
258
        $segmentResourceSetWrapper = $this->request->getTargetResourceSetWrapper();
259
        return $this->_pushSegment($segmentName, $segmentResourceSetWrapper);
0 ignored issues
show
Bug introduced by
It seems like $segmentResourceSetWrapper defined by $this->request->getTargetResourceSetWrapper() on line 258 can be null; however, POData\ObjectModel\Objec...zerBase::_pushSegment() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
260
    }
261
262
    /**
263
     * Pushes a segment for the current navigation property being written out.
264
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
265
     * 'Segment Stack' and this method.
266
     * Note: Calls to this method should be balanced with calls to popSegment.
267
     *
268
     * @param ResourceProperty &$resourceProperty The current navigation property
269
     * being written out.
270
     *
271
     * @return bool true if a segment was pushed, false otherwise
272
     *
273
     * @throws InvalidOperationException If this function invoked with non-navigation
274
     *                                   property instance.
275
     */
276 View Code Duplication
    protected function pushSegmentForNavigationProperty(ResourceProperty &$resourceProperty)
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...
277
    {
278
        if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) {
279
            $this->assert(!empty($this->_segmentNames), '!is_empty($this->_segmentNames');
280
            $currentResourceSetWrapper = $this->getCurrentResourceSetWrapper();
281
            $currentResourceType = $currentResourceSetWrapper->getResourceType();
282
            $currentResourceSetWrapper = $this->service
283
                ->getProvidersWrapper()
284
                ->getResourceSetWrapperForNavigationProperty(
285
                    $currentResourceSetWrapper,
0 ignored issues
show
Bug introduced by
It seems like $currentResourceSetWrapper defined by $this->service->getProvi...ype, $resourceProperty) on line 282 can be null; however, POData\Providers\Provide...ForNavigationProperty() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
286
                    $currentResourceType,
287
                    $resourceProperty
288
                );
289
290
            $this->assert(!is_null($currentResourceSetWrapper), '!null($currentResourceSetWrapper)');
291
            return $this->_pushSegment($resourceProperty->getName(), $currentResourceSetWrapper);
0 ignored issues
show
Bug introduced by
It seems like $currentResourceSetWrapper defined by $this->service->getProvi...ype, $resourceProperty) on line 282 can be null; however, POData\ObjectModel\Objec...zerBase::_pushSegment() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
292
        } else {
293
            throw new InvalidOperationException('pushSegmentForNavigationProperty should not be called with non-entity type');
294
        }
295
    }
296
297
    /**
298
     * Gets collection of projection nodes under the current node.
299
     *
300
     * @return ProjectionNode[]|ExpandedProjectionNode[]|null List of nodes
301
     * describing projections for the current segment, If this method returns
302
     * null it means no projections are to be applied and the entire resource
303
     * for the current segment should be serialized, If it returns non-null
304
     * only the properties described by the returned projection segments should
305
     * be serialized.
306
     */
307
    protected function getProjectionNodes()
308
    {
309
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
310
        if (is_null($expandedProjectionNode)
311
            || $expandedProjectionNode->canSelectAllProperties()
312
        ) {
313
            return null;
314
        }
315
316
        return $expandedProjectionNode->getChildNodes();
317
    }
318
319
    /**
320
     * Find a 'ExpandedProjectionNode' instance in the projection tree
321
     * which describes the current segment.
322
     *
323
     * @return ExpandedProjectionNode|null
324
     */
325 View Code Duplication
    protected function getCurrentExpandedProjectionNode()
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...
326
    {
327
        $expandedProjectionNode = $this->request->getRootProjectionNode();
328
        if (is_null($expandedProjectionNode)) {
329
            return null;
330
        } else {
331
            $depth = count($this->_segmentNames);
332
            // $depth == 1 means serialization of root entry
333
            //(the resource identified by resource path) is going on,
334
            //so control won't get into the below for loop.
335
            //we will directly return the root node,
336
            //which is 'ExpandedProjectionNode'
337
            // for resource identified by resource path.
338
            if ($depth != 0) {
339
                for ($i = 1; $i < $depth; $i++) {
340
                    $expandedProjectionNode
341
                        = $expandedProjectionNode->findNode($this->_segmentNames[$i]);
342
                        $this->assert(
343
                            !is_null($expandedProjectionNode),
344
                            '!is_null($expandedProjectionNode)'
345
                        );
346
                        $this->assert(
347
                            $expandedProjectionNode instanceof ExpandedProjectionNode,
348
                            '$expandedProjectionNode instanceof ExpandedProjectionNode'
349
                        );
350
                }
351
            }
352
        }
353
354
        return $expandedProjectionNode;
355
    }
356
357
    /**
358
     * Check whether to expand a navigation property or not.
359
     *
360
     * @param string $navigationPropertyName Name of naviagtion property in question.
361
     *
362
     * @return boolean True if the given navigation should be
363
     * explanded otherwise false.
364
     */
365
    protected function shouldExpandSegment($navigationPropertyName)
366
    {
367
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
368
        if (is_null($expandedProjectionNode)) {
369
            return false;
370
        }
371
372
        $expandedProjectionNode = $expandedProjectionNode->findNode($navigationPropertyName);
373
        return !is_null($expandedProjectionNode) && ($expandedProjectionNode instanceof ExpandedProjectionNode);
374
    }
375
376
    /**
377
     * Pushes information about the segment that is going to be serialized
378
     * to the 'Segment Stack'.
379
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
380
     * 'Segment Stack' and this method.
381
     * Note: Calls to this method should be balanced with calls to popSegment.
382
     *
383
     * @param string             $segmentName         Name of segment to push.
384
     * @param ResourceSetWrapper &$resourceSetWrapper The resource set
385
     *                                                wrapper to push.
386
     *
387
     * @return bool true if the segment was push, false otherwise
388
     */
389 View Code Duplication
    private function _pushSegment($segmentName, ResourceSetWrapper &$resourceSetWrapper)
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...
390
    {
391
        $rootProjectionNode = $this->request->getRootProjectionNode();
392
        // Even though there is no expand in the request URI, still we need to push
393
        // the segment information if we need to count
394
        //the number of entities written.
395
        // After serializing each entity we should check the count to see whether
396
        // we serialized more entities than configured
397
        //(page size, maxResultPerCollection).
398
        // But we will not do this check since library is doing paging and never
399
        // accumulate entities more than configured.
400
        //
401
        // if ((!is_null($rootProjectionNode) && $rootProjectionNode->isExpansionSpecified())
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% 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...
402
        //    || ($resourceSetWrapper->getResourceSetPageSize() != 0)
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% 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...
403
        //    || ($this->service->getServiceConfiguration()->getMaxResultsPerCollection() != PHP_INT_MAX)
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
404
        //) {}
405
406
        if (!is_null($rootProjectionNode)
407
            && $rootProjectionNode->isExpansionSpecified()
408
        ) {
409
            array_push($this->_segmentNames, $segmentName);
410
            array_push($this->_segmentResourceSetWrappers, $resourceSetWrapper);
411
            array_push($this->_segmentResultCounts, 0);
412
            return true;
413
        }
414
415
        return false;
416
    }
417
418
    /**
419
     * Get next page link from the given entity instance.
420
     *
421
     * @param mixed  &$lastObject Last object serialized to be
422
     *                            used for generating $skiptoken.
423
     * @param string $absoluteUri Absolute response URI.
424
     *
425
     * @return URI for the link for next page.
426
     */
427
    protected function getNextLinkUri(&$lastObject, $absoluteUri)
428
    {
429
        $currentExpandedProjectionNode = $this->getCurrentExpandedProjectionNode();
430
        $internalOrderByInfo = $currentExpandedProjectionNode->getInternalOrderByInfo();
431
        $skipToken = $internalOrderByInfo->buildSkipTokenValue($lastObject);
432
        $this->assert(!is_null($skipToken), '!is_null($skipToken)');
433
        $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...
434
        if ($this->isRootResourceSet()) {
435
            $queryParameterString = $this->getNextPageLinkQueryParametersForRootResourceSet();
436
        } else {
437
            $queryParameterString = $this->getNextPageLinkQueryParametersForExpandedResourceSet();
438
        }
439
440
        $queryParameterString .= '$skiptoken=' . $skipToken;
441
        $odalaLink = new ODataLink();
442
        $odalaLink->name = ODataConstants::ATOM_LINK_NEXT_ATTRIBUTE_STRING;
443
        $odalaLink->url = rtrim($absoluteUri, '/') . '?' . $queryParameterString;
444
        return $odalaLink;
445
    }
446
447
    /**
448
     * Builds the string corresponding to query parameters for top level results
449
     * (result set identified by the resource path) to be put in next page link.
450
     *
451
     * @return string|null string representing the query parameters in the URI
452
     *                     query parameter format, NULL if there
453
     *                     is no query parameters
454
     *                     required for the next link of top level result set.
455
     */
456
    protected function getNextPageLinkQueryParametersForRootResourceSet()
457
    {
458
        $queryParameterString = null;
459
        foreach (array(ODataConstants::HTTPQUERY_STRING_FILTER,
460
            ODataConstants::HTTPQUERY_STRING_EXPAND,
461
            ODataConstants::HTTPQUERY_STRING_ORDERBY,
462
            ODataConstants::HTTPQUERY_STRING_INLINECOUNT,
463
            ODataConstants::HTTPQUERY_STRING_SELECT) as $queryOption
464
        ) {
465
            $value = $this->service->getHost()->getQueryStringItem($queryOption);
466
            if (!is_null($value)) {
467
                if (!is_null($queryParameterString)) {
468
                    $queryParameterString = $queryParameterString . '&';
469
                }
470
471
                $queryParameterString .= $queryOption . '=' . $value;
472
            }
473
        }
474
475
        $topCountValue = $this->request->getTopOptionCount();
476
        if (!is_null($topCountValue)) {
477
            $remainingCount  = $topCountValue - $this->request->getTopCount();
478
            if (!is_null($queryParameterString)) {
479
                $queryParameterString .= '&';
480
            }
481
482
            $queryParameterString .= ODataConstants::HTTPQUERY_STRING_TOP . '=' . $remainingCount;
483
        }
484
485
        if (!is_null($queryParameterString)) {
486
            $queryParameterString .= '&';
487
        }
488
489
        return $queryParameterString;
490
    }
491
492
    /**
493
     * Builds the string corresponding to query parameters for current expanded
494
     * results to be put in next page link.
495
     *
496
     * @return string|null string representing the $select and $expand parameters
497
     *                     in the URI query parameter format, NULL if there is no
498
     *                     query parameters ($expand and/select) required for the
499
     *                     next link of expanded result set.
500
     */
501
    protected function getNextPageLinkQueryParametersForExpandedResourceSet()
502
    {
503
        $queryParameterString = null;
504
        $expandedProjectionNode = $this->getCurrentExpandedProjectionNode();
505
        if (!is_null($expandedProjectionNode)) {
506
            $pathSegments = array();
507
            $selectionPaths = null;
508
            $expansionPaths = null;
509
            $foundSelections = false;
510
            $foundExpansions = false;
511
            $this->_buildSelectionAndExpansionPathsForNode(
512
                $pathSegments,
513
                $selectionPaths,
514
                $expansionPaths,
515
                $expandedProjectionNode,
516
                $foundSelections,
517
                $foundExpansions
518
            );
519
520
            if ($foundSelections && $expandedProjectionNode->canSelectAllProperties()) {
521
                $this->_appendSelectionOrExpandPath($selectionPaths, $pathSegments, '*');
522
            }
523
524
            if (!is_null($selectionPaths)) {
525
                $queryParameterString = '$select=' . $selectionPaths;
526
            }
527
528
            if (!is_null($expansionPaths)) {
529
                if (!is_null($queryParameterString)) {
530
                    $queryParameterString .= '&';
531
                }
532
533
                $queryParameterString = '$expand=' . $expansionPaths;
534
            }
535
536
            if (!is_null($queryParameterString)) {
537
                    $queryParameterString .= '&';
538
            }
539
        }
540
541
        return $queryParameterString;
542
    }
543
544
    /**
545
     * Wheter next link is needed for the current resource set (feed)
546
     * being serialized.
547
     *
548
     * @param int $resultSetCount Number of entries in the current
549
     *                            resource set.
550
     *
551
     * @return boolean true if the feed must have a next page link
552
     */
553
    protected function needNextPageLink($resultSetCount)
554
    {
555
        $currentResourceSet = $this->getCurrentResourceSetWrapper();
556
        $recursionLevel = count($this->_segmentNames);
557
        //$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...
558
        $pageSize = $currentResourceSet->getResourceSetPageSize();
559
560
        if ($recursionLevel == 1) {
561
            //presence of $top option affect next link for root container
562
            $topValueCount = $this->request->getTopOptionCount();
563
            if (!is_null($topValueCount) && ($topValueCount <= $pageSize)) {
564
                 return false;
565
            }
566
        }
567
568
        return $resultSetCount == $pageSize;
569
    }
570
571
    /**
572
     * Pops segment information from the 'Segment Stack'
573
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
574
     * 'Segment Stack' and this method.
575
     * Note: Calls to this method should be balanced with previous
576
     * calls to _pushSegment.
577
     *
578
     * @param boolean $needPop Is a pop required. Only true if last
579
     *                         push was successful.
580
     *
581
     * @return void
582
     *
583
     * @throws InvalidOperationException If found un-balanced call with _pushSegment
584
     */
585 View Code Duplication
    protected function popSegment($needPop)
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...
586
    {
587
        if ($needPop) {
588
            if (!empty($this->_segmentNames)) {
589
                array_pop($this->_segmentNames);
590
                array_pop($this->_segmentResourceSetWrappers);
591
                array_pop($this->_segmentResultCounts);
592
            } else {
593
                throw new InvalidOperationException('Found non-balanced call to _pushSegment and popSegment');
594
            }
595
        }
596
    }
597
598
    /**
599
     * Recursive metod to build $expand and $select paths for a specified node.
600
     *
601
     * @param string[]          &$parentPathSegments     Array of path
602
     *                                                        segments which leads
603
     *                                                        up to (including)
604
     *                                                        the segment
605
     *                                                        represented by
606
     *                                                        $expandedProjectionNode.
607
     * @param string[]          &$selectionPaths         The string which
608
     *                                                        holds projection
609
     *                                                        path segment
610
     *                                                        seperated by comma,
611
     *                                                        On return this argument
612
     *                                                        will be updated with
613
     *                                                        the selection path
614
     *                                                        segments under
615
     *                                                        this node.
616
     * @param string[]          &$expansionPaths         The string which holds
617
     *                                                        expansion path segment
618
     *                                                        seperated by comma.
619
     *                                                        On return this argument
620
     *                                                        will be updated with
621
     *                                                        the expand path
622
     *                                                        segments under
623
     *                                                        this node.
624
     * @param ExpandedProjectionNode &$expandedProjectionNode The expanded node for
625
     *                                                        which expansion
626
     *                                                        and selection path
627
     *                                                        to be build.
628
     * @param boolean                &$foundSelections        On return, this
629
     *                                                        argument will hold
630
     *                                                        true if any selection
631
     *                                                        defined under this node
632
     *                                                        false otherwise.
633
     * @param boolean                &$foundExpansions        On return, this
634
     *                                                        argument will hold
635
     *                                                        true if any expansion
636
     *                                                        defined under this node
637
     *                                                        false otherwise.
638
     *
639
     * @return void
640
     */
641
    private function _buildSelectionAndExpansionPathsForNode(&$parentPathSegments,
642
        &$selectionPaths, &$expansionPaths,
643
        ExpandedProjectionNode &$expandedProjectionNode,
644
        &$foundSelections, &$foundExpansions
645
    ) {
646
        $foundSelections = false;
647
        $foundExpansions = false;
648
        $foundSelectionOnChild = false;
649
        $foundExpansionOnChild = false;
650
        $expandedChildrenNeededToBeSelected = array();
651
        foreach ($expandedProjectionNode->getChildNodes() as $childNode) {
652
            if (!($childNode instanceof ExpandedProjectionNode)) {
653
                $foundSelections = true;
654
                $this->_appendSelectionOrExpandPath(
655
                    $selectionPaths,
656
                    $parentPathSegments,
657
                    $childNode->getPropertyName()
658
                );
659
            } else {
660
                $foundExpansions = true;
661
                array_push($parentPathSegments, $childNode->getPropertyName());
662
                $this->_buildSelectionAndExpansionPathsForNode(
663
                    $parentPathSegments,
664
                    $selectionPaths, $expansionPaths,
665
                    $childNode, $foundSelectionOnChild,
666
                    $foundExpansionOnChild
667
                );
668
                array_pop($parentPathSegments);
669
                if ($childNode->canSelectAllProperties()) {
670
                    if ($foundSelectionOnChild) {
671
                        $this->_appendSelectionOrExpandPath(
672
                            $selectionPaths,
673
                            $parentPathSegments,
674
                            $childNode->getPropertyName() . '/*'
675
                        );
676
                    } else {
677
                        $expandedChildrenNeededToBeSelected[] = $childNode;
678
                    }
679
                }
680
            }
681
682
            $foundSelections |= $foundSelectionOnChild;
683
            if (!$foundExpansionOnChild) {
684
                $this->_appendSelectionOrExpandPath(
685
                    $expansionPaths,
686
                    $parentPathSegments,
687
                    $childNode->getPropertyName()
688
                );
689
            }
690
        }
691
692
        if (!$expandedProjectionNode->canSelectAllProperties() || $foundSelections) {
693
            foreach ($expandedChildrenNeededToBeSelected as $childToProject) {
694
                $this->_appendSelectionOrExpandPath(
695
                    $selectionPaths,
696
                    $parentPathSegments,
697
                    $childNode->getPropertyName()
0 ignored issues
show
Bug introduced by
The variable $childNode seems to be defined by a foreach iteration on line 651. 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...
698
                );
699
                $foundSelections = true;
700
            }
701
        }
702
    }
703
704
    /**
705
     * Append the given path to $expand or $select path list.
706
     *
707
     * @param string        &$path  The $expand or $select path list to which to append the given path.
708
     * @param string[] &$parentPathSegments The list of path up to the $segmentToAppend.
709
     * @param string        $segmentToAppend     The last segment of the path.
710
     *
711
     * @return void
712
     */
713
    private function _appendSelectionOrExpandPath(&$path, &$parentPathSegments, $segmentToAppend)
714
    {
715
        if (!is_null($path)) {
716
            $path .= ', ';
717
        }
718
719
        foreach ($parentPathSegments as $parentPathSegment) {
720
            $path .= $parentPathSegment . '/';
721
        }
722
723
        $path .= $segmentToAppend;
724
    }
725
726
    /**
727
     * Assert that the given condition is true.
728
     *
729
     * @param boolean $condition         Condition to be asserted.
730
     * @param string  $conditionAsString String containing message incase
731
     *                                   if assertion fails.
732
     *
733
     * @throws InvalidOperationException Incase if assertion failes.
734
     *
735
     * @return void
736
     */
737
    protected function assert($condition, $conditionAsString)
738
    {
739
        if (!$condition) {
740
            throw new InvalidOperationException("Unexpected state, expecting $conditionAsString");
741
        }
742
    }
743
}
744