ObjectModelSerializerBase   D
last analyzed

Complexity

Total Complexity 69

Size/Duplication

Total Lines 717
Duplicated Lines 15.62 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
wmc 69
lcom 1
cbo 13
dl 112
loc 717
rs 4.1025
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A getEntryInstanceKey() 0 23 3
A getPropertyValue() 0 15 2
A getCurrentResourceSetWrapper() 9 9 2
A isRootResourceSet() 0 5 2
B getETagForEntry() 12 30 5
A pushSegmentForRoot() 0 6 1
A pushSegmentForNavigationProperty() 20 20 2
A getProjectionNodes() 0 11 3
B getCurrentExpandedProjectionNode() 31 31 4
A shouldExpandSegment() 0 10 3
B _pushSegment() 28 28 3
A getNextLinkUri() 0 19 2
C getNextPageLinkQueryParametersForRootResourceSet() 0 35 7
C getNextPageLinkQueryParametersForExpandedResourceSet() 0 42 8
A needNextPageLink() 0 17 4
A popSegment() 12 12 3
C _buildSelectionAndExpansionPathsForNode() 0 62 9
A _appendSelectionOrExpandPath() 0 12 3
A assert() 0 6 2

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