UriProcessor   F
last analyzed

Complexity

Total Complexity 80

Size/Duplication

Total Lines 696
Duplicated Lines 31.61 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 0
Metric Value
wmc 80
lcom 1
cbo 20
dl 220
loc 696
rs 1.2757
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B process() 0 26 2
A getRequest() 0 4 1
C execute() 0 77 16
A handleSegmentTargetsToResourceSet() 0 22 2
B _handleSegmentTargetsToRelatedResource() 0 44 4
C applyQueryOptions() 14 40 8
B performPaging() 0 27 5
B handleExpansion() 0 12 6
D _executeExpansion() 116 128 14
A _getCurrentResourceSetWrapper() 9 9 2
A _pushSegmentForRoot() 0 7 1
B _pushSegmentForNavigationProperty() 31 31 2
A _getExpandedProjectionNodes() 0 14 4
B _getCurrentExpandedProjectionNode() 24 24 4
A _pushSegment() 13 13 3
A _popSegment() 13 13 3
A assert() 0 8 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 UriProcessor 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 UriProcessor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace POData\UriProcessor;
4
5
use POData\Providers\ProvidersWrapper;
6
use POData\Providers\Metadata\ResourcePropertyKind;
7
use POData\Providers\Metadata\ResourceTypeKind;
8
use POData\Providers\Metadata\ResourceSetWrapper;
9
use POData\Providers\Metadata\ResourceProperty;
10
use POData\Providers\Query\QueryType;
11
use POData\UriProcessor\QueryProcessor\QueryProcessor;
12
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
13
use POData\UriProcessor\ResourcePathProcessor\ResourcePathProcessor;
14
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor;
15
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
16
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
17
use POData\IService;
18
use POData\Common\Url;
19
use POData\Common\Messages;
20
use POData\Common\ODataException;
21
use POData\Common\InvalidOperationException;
22
use POData\Common\ODataConstants;
23
use POData\Providers\Query\QueryResult;
24
25
/**
26
 * Class UriProcessor
27
 *
28
 * A type to process client's requets URI
29
 * The syntax of request URI is:
30
 *  Scheme Host Port ServiceRoot ResourcePath ? QueryOption
31
 * For more details refer:
32
 * http://www.odata.org/developers/protocols/uri-conventions#UriComponents
33
 *
34
 * @package POData\UriProcessor
35
 */
36
class UriProcessor
37
{
38
    /**
39
     * Description of the OData request that a client has submitted.
40
     * 
41
     * @var RequestDescription
42
     */
43
    private $request;
44
45
    /**
46
     * Holds reference to the data service instance.
47
     * 
48
     * @var IService
49
     */
50
    private $service;
51
52
    /**
53
     * Holds reference to the wrapper over IDSMP and IDSQP implementation.
54
     * 
55
     * @var ProvidersWrapper
56
     */
57
    private $providers;
58
59
    /**
60
     * Collection of segment names.
61
     *
62
     * @var string[]
63
     */
64
    private $_segmentNames;
65
66
    /**
67
     * Collection of segment ResourceSetWrapper instances.
68
     *
69
     * @var ResourceSetWrapper[]
70
     */
71
    private $_segmentResourceSetWrappers;
72
73
    /**
74
     * Constructs a new instance of UriProcessor
75
     * 
76
     * @param IService $service Reference to the data service instance.
77
     */
78
    private function __construct(IService $service)
79
    {
80
        $this->service = $service;
81
        $this->providers = $service->getProvidersWrapper();
82
        $this->_segmentNames = array();
83
        $this->_segmentResourceSetWrappers = array();
84
    }
85
86
    /**
87
     * Process the resource path and query options of client's request uri.
88
     * 
89
     * @param IService $service Reference to the data service instance.
90
     * 
91
     * @return URIProcessor
92
     * 
93
     * @throws ODataException
94
     */
95
    public static function process(IService $service)
96
    {
97
        $absoluteRequestUri = $service->getHost()->getAbsoluteRequestUri();
98
        $absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri();
99
        
100
        if (!$absoluteServiceUri->isBaseOf($absoluteRequestUri)) {
101
			throw ODataException::createInternalServerError(
102
                Messages::uriProcessorRequestUriDoesNotHaveTheRightBaseUri(
103
                    $absoluteRequestUri->getUrlAsString(), 
104
                    $absoluteServiceUri->getUrlAsString()
105
                )
106
            );
107
        }
108
109
        $uriProcessor = new UriProcessor($service);
110
        //Parse the resource path part of the request Uri.
111
		$uriProcessor->request = ResourcePathProcessor::process($service);
112
113
	    $uriProcessor->request->setUriProcessor($uriProcessor);
114
115
116
        //Parse the query string options of the request Uri.
117
        QueryProcessor::process( $uriProcessor->request, $service );
118
119
        return $uriProcessor;
120
    }
121
122
    /**
123
     * Gets reference to the request submitted by client.
124
     * 
125
     * @return RequestDescription
126
     */
127
    public function getRequest()
128
    {
129
        return $this->request;
130
    }
131
132
    /**
133
     * Execute the client submitted request against the data source.
134
     */
135
    public function execute()
136
    {
137
        $segments = $this->request->getSegments();
138
139
        foreach ($segments as $segment) {
140
141
            $requestTargetKind = $segment->getTargetKind();
142
143
	        if ($segment->getTargetSource() == TargetSource::ENTITY_SET) {
144
                $this->handleSegmentTargetsToResourceSet($segment);
145
            } else if ($requestTargetKind == TargetKind::RESOURCE()) {
146
                if (is_null($segment->getPrevious()->getResult())) {
147
					throw ODataException::createResourceNotFoundError(
148
                        $segment->getPrevious()->getIdentifier()
149
                    );
150
                }
151
                $this->_handleSegmentTargetsToRelatedResource($segment);
152
            } else if ($requestTargetKind == TargetKind::LINK()) {
153
                $segment->setResult($segment->getPrevious()->getResult());
154
            } else if ($segment->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) {
155
                // we are done, $count will the last segment and 
156
                // taken care by _applyQueryOptions method
157
                $segment->setResult($this->request->getCountValue());
158
                break;
159
            } else {
160
                if ($requestTargetKind == TargetKind::MEDIA_RESOURCE()) {
161
                    if (is_null($segment->getPrevious()->getResult())) {
162
						throw ODataException::createResourceNotFoundError(
163
                            $segment->getPrevious()->getIdentifier()
164
                        );
165
                    }
166
                    // For MLE and Named Stream the result of last segment 
167
                    // should be that of previous segment, this is required 
168
                    // while retrieving content type or stream from IDSSP
169
                    $segment->setResult($segment->getPrevious()->getResult());
170
                    // we are done, as named stream property or $value on 
171
                    // media resource will be the last segment
172
                    break;
173
                }
174
175
	            $value = $segment->getPrevious()->getResult();
176
                while (!is_null($segment)) {
177
	                //TODO: what exactly is this doing here?  Once a null's found it seems everything will be null
178
                    if (!is_null($value)) {
179
                        $value = null;
180
                    } else {
181
                        try {
182
	                        //see #88
183
                            $property = new \ReflectionProperty($value, $segment->getIdentifier());
184
                            $value = $property->getValue($value);
185
                        } catch (\ReflectionException $reflectionException) {
186
                            //throw ODataException::createInternalServerError(Messages::orderByParserFailedToAccessOrInitializeProperty($resourceProperty->getName(), $resourceType->getName()));
0 ignored issues
show
Unused Code Comprehensibility introduced by
68% 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...
187
                        }
188
                    }
189
190
                    $segment->setResult($value);
191
                    $segment = $segment->getNext();
192
                    if (!is_null($segment) && $segment->getIdentifier() == ODataConstants::URI_VALUE_SEGMENT) {
193
                        $segment->setResult($value);
194
                        $segment = $segment->getNext();
195
                    }
196
                }
197
198
                break;
199
200
            }
201
202
            if (is_null($segment->getNext()) || $segment->getNext()->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) {
203
                    $this->applyQueryOptions($segment);
204
            }
205
        }
206
207
         // Apply $select and $expand options to result set, this function will be always applied
208
         // irrespective of return value of IDSQP2::canApplyQueryOptions which means library will
209
         // not delegate $expand/$select operation to IDSQP2 implementation
210
        $this->handleExpansion();
211
    }
212
213
    /**
214
     * Query for a resource set pointed by the given segment descriptor and update the descriptor with the result.
215
     *
216
     * @param SegmentDescriptor $segment Describes the resource set to query
217
     * @return void
218
     *
219
     */
220
    private function handleSegmentTargetsToResourceSet( SegmentDescriptor $segment ) {
221
        if ($segment->isSingleResult()) {
222
            $entityInstance = $this->providers->getResourceFromResourceSet(
223
                $segment->getTargetResourceSetWrapper(),
224
                $segment->getKeyDescriptor()
225
            );
226
227
            $segment->setResult($entityInstance);
228
            
229
        } else {
230
231
            $queryResult = $this->providers->getResourceSet(
232
	            $this->request->queryType,
233
                $segment->getTargetResourceSetWrapper(),
234
                $this->request->getFilterInfo(),
0 ignored issues
show
Bug introduced by
It seems like $this->request->getFilterInfo() can be null; however, getResourceSet() 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...
235
                $this->request->getInternalOrderByInfo(),
0 ignored issues
show
Bug introduced by
It seems like $this->request->getInternalOrderByInfo() can be null; however, getResourceSet() 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...
236
                $this->request->getTopCount(),
237
                $this->request->getSkipCount()
238
            );
239
            $segment->setResult($queryResult);
240
        }
241
    }
242
243
    /**
244
     * Query for a related resource set or resource set reference pointed by the 
245
     * given segment descriptor and update the descriptor with the result.
246
     * 
247
     * @param SegmentDescriptor &$segment Describes the related resource
248
     *                                              to query.
249
     * 
250
     * @return void
251
     */
252
    private function _handleSegmentTargetsToRelatedResource(SegmentDescriptor $segment) {
253
        $projectedProperty = $segment->getProjectedProperty();
254
        $projectedPropertyKind = $projectedProperty->getKind();
255
256
        if ($projectedPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE) {
257
            if ($segment->isSingleResult()) {
258
                $entityInstance = $this->providers->getResourceFromRelatedResourceSet(
259
                    $segment->getPrevious()->getTargetResourceSetWrapper(),
260
                    $segment->getPrevious()->getResult(),
261
                    $segment->getTargetResourceSetWrapper(),
262
                    $projectedProperty,
263
                    $segment->getKeyDescriptor()
264
                );
265
266
                $segment->setResult($entityInstance);
267
            } else {
268
                $queryResult = $this->providers->getRelatedResourceSet(
269
	                $this->request->queryType,
270
                    $segment->getPrevious()->getTargetResourceSetWrapper(),
271
                    $segment->getPrevious()->getResult(),
272
                    $segment->getTargetResourceSetWrapper(),
273
                    $segment->getProjectedProperty(),
274
                    $this->request->getFilterInfo(),
0 ignored issues
show
Bug introduced by
It seems like $this->request->getFilterInfo() can be null; however, getRelatedResourceSet() 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...
275
	                //TODO: why are these null?  see #98
276
                    null, // $orderby
277
                    null, // $top
278
                    null  // $skip
279
                );
280
281
                $segment->setResult($queryResult);
282
            }           
283
        } else if ($projectedPropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE) {
284
            $entityInstance = $this->providers->getRelatedResourceReference(
285
                $segment->getPrevious()->getTargetResourceSetWrapper(),
286
                $segment->getPrevious()->getResult(),
287
                $segment->getTargetResourceSetWrapper(),
288
                $segment->getProjectedProperty()
289
            );
290
291
            $segment->setResult($entityInstance);
292
        } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
293
            //Unexpected state
294
        }
295
    }
296
297
    /**
298
     * Applies the query options to the resource(s) retrieved from the data source.
299
     * 
300
     * @param SegmentDescriptor $segment The descriptor which holds resource(s) on which query options to be applied.
301
     *
302
     */
303
    private function applyQueryOptions(SegmentDescriptor $segment)
304
    {
305
	    //TODO: I'm not really happy with this..i think i'd rather keep the result the QueryResult
306
	    //not even bother with the setCountValue stuff (shouldn't counts be on segments?)
307
	    //and just work with the QueryResult in the object model serializer
308
	    $result = $segment->getResult();
309
310
	    if(!$result instanceof QueryResult){
311
		    //If the segment isn't a query result, then there's no paging or counting to be done
312
		    return;
313
        }
314
315
316
        // Note $inlinecount=allpages means include the total count regardless of paging..so we set the counts first
317
	    // regardless if POData does the paging or not.
318 View Code Duplication
        if ($this->request->queryType == QueryType::ENTITIES_WITH_COUNT()) {
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...
319
            if ($this->providers->handlesOrderedPaging()) {
320
                $this->request->setCountValue($result->count);
321
            } else {
322
                $this->request->setCountValue(count($result->results));
323
            }
324
        }
325
326
	    //Have POData perform paging if necessary
327
	    if(!$this->providers->handlesOrderedPaging() && !empty($result->results)){
328
			$result->results = $this->performPaging($result->results);
329
	    }
330
331
	    //a bit surprising, but $skip and $top affects $count so update it here, not above
332
	    //IE  data.svc/Collection/$count?$top=10 returns 10 even if Collection has 11+ entries
333 View Code Duplication
	    if ($this->request->queryType == QueryType::COUNT()) {
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...
334
		    if ($this->providers->handlesOrderedPaging()) {
335
			    $this->request->setCountValue($result->count);
336
		    } else {
337
			    $this->request->setCountValue(count($result->results));
338
		    }
339
	    }
340
341
        $segment->setResult($result->results);
342
    }
343
344
	/**
345
	 * If the provider does not perform the paging (ordering, top, skip) then this method does it
346
	 *
347
	 * @param array $result
348
	 * @return array
349
	 */
350
	private function performPaging(array $result)
351
	{
352
		//Apply (implicit and explicit) $orderby option
353
		$internalOrderByInfo = $this->request->getInternalOrderByInfo();
354
		if (!is_null($internalOrderByInfo)) {
355
			$orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
356
			usort($result, $orderByFunction);
357
		}
358
359
		//Apply $skiptoken option
360
		$internalSkipTokenInfo = $this->request->getInternalSkipTokenInfo();
361
		if (!is_null($internalSkipTokenInfo)) {
362
			$matchingIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($result);
363
			$result = array_slice($result, $matchingIndex);
364
		}
365
366
		//Apply $top and $skip option
367
		if (!empty($result)) {
368
			$top  = $this->request->getTopCount();
369
			$skip = $this->request->getSkipCount();
370
			if(is_null($skip)) $skip = 0;
371
372
			$result = array_slice($result, $skip, $top);
373
		}
374
375
		return $result;
376
	}
377
378
379
    /**
380
     * Perform expansion.
381
     * 
382
     * @return void
383
     */
384
    private function handleExpansion()
385
    {
386
        $node = $this->request->getRootProjectionNode();
387
        if (!is_null($node) && $node->isExpansionSpecified()) {
388
            $result = $this->request->getTargetResult();
389
            if (!is_null($result) || is_array($result) && !empty($result)) {
390
                $needPop = $this->_pushSegmentForRoot();
391
                $this->_executeExpansion($result);
392
                $this->_popSegment($needPop);
393
            }
394
        }
395
    }
396
397
    /**
398
     * Execute queries for expansion.
399
     * 
400
     * @param array(mixed)/mixed $result Resource(s) whose navigation properties needs to be expanded.
0 ignored issues
show
Documentation introduced by
The doc-type array(mixed)/mixed could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
401
     *
402
     *
403
     * @return void
404
     */
405
    private function _executeExpansion($result)
406
    {
407
        $expandedProjectionNodes = $this->_getExpandedProjectionNodes();
408
        foreach ($expandedProjectionNodes as $expandedProjectionNode) {
409
            $isCollection = $expandedProjectionNode->getResourceProperty()->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE;
410
            $expandedPropertyName = $expandedProjectionNode->getResourceProperty()->getName();
411
            if (is_array($result)) {
412
                foreach ($result as $entry) {
413
                    // Check for null entry
414 View Code Duplication
                    if ($isCollection) {
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...
415
                        $currentResourceSet = $this->_getCurrentResourceSetWrapper()->getResourceSet();
416
                        $resourceSetOfProjectedProperty = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
417
                        $projectedProperty1 = $expandedProjectionNode->getResourceProperty();
418
                        $result1 = $this->providers->getRelatedResourceSet(
419
	                        QueryType::ENTITIES(), //it's always entities for an expansion
420
                            $currentResourceSet,
421
                            $entry,
422
                            $resourceSetOfProjectedProperty,
423
                            $projectedProperty1,
424
                            null, // $filter
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<POData\UriProcess...ssionParser\FilterInfo>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
425
                            null, // $orderby
426
                            null, // $top
427
                            null  // $skip
428
                        )->results;
429
                        if (!empty($result1)) {
430
                            $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo();
431
                            if (!is_null($internalOrderByInfo)) {
432
                                $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
433
                                usort($result1, $orderByFunction);
434
                                unset($internalOrderByInfo);
435
                                $takeCount = $expandedProjectionNode->getTakeCount();
436
                                if (!is_null($takeCount)) {
437
                                    $result1 = array_slice($result1, 0, $takeCount);
438
                                }
439
                            }
440
441
                            $entry->$expandedPropertyName = $result1;
442
                            $projectedProperty = $expandedProjectionNode->getResourceProperty();
443
                            $needPop = $this->_pushSegmentForNavigationProperty(
444
                                $projectedProperty
445
                            );
446
                            $this->_executeExpansion($result1);
447
                            $this->_popSegment($needPop);
448
                        } else {
449
                            $entry->$expandedPropertyName = array();
450
                        }
451
                    } else {
452
                        $currentResourceSet1 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
453
                        $resourceSetOfProjectedProperty1 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
454
                        $projectedProperty2 = $expandedProjectionNode->getResourceProperty();
455
                        $result1 = $this->providers->getRelatedResourceReference(
456
                            $currentResourceSet1,
457
                            $entry,
458
                            $resourceSetOfProjectedProperty1,
459
                            $projectedProperty2
460
                        );
461
                        $entry->$expandedPropertyName = $result1;
462
                        if (!is_null($result1)) {
463
                            $projectedProperty3 = $expandedProjectionNode->getResourceProperty();
464
                            $needPop = $this->_pushSegmentForNavigationProperty(
465
                                $projectedProperty3
466
                            );
467
                            $this->_executeExpansion($result1);
468
                            $this->_popSegment($needPop);
469
                        }
470
                    }
471
                }
472 View Code Duplication
            } else {
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...
473
                if ($isCollection) {
474
                    $currentResourceSet2 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
475
                    $resourceSetOfProjectedProperty2 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
476
                    $projectedProperty4 = $expandedProjectionNode->getResourceProperty();
477
                    $result1 = $this->providers->getRelatedResourceSet(
478
	                    QueryType::ENTITIES(), //it's always entities for an expansion
479
                        $currentResourceSet2,
480
                        $result,
481
                        $resourceSetOfProjectedProperty2,
482
                        $projectedProperty4,
483
                        null, // $filter
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<POData\UriProcess...ssionParser\FilterInfo>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
484
                        null, // $orderby
485
                        null, // $top
486
                        null  // $skip
487
                    )->results;
488
                    if (!empty($result1)) {
489
                        $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo();
490
                        if (!is_null($internalOrderByInfo)) {
491
                            $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
492
                            usort($result1, $orderByFunction);
493
                            unset($internalOrderByInfo);
494
                            $takeCount = $expandedProjectionNode->getTakeCount();
495
                            if (!is_null($takeCount)) {
496
                                $result1 = array_slice($result1, 0, $takeCount);
497
                            }
498
                        }
499
500
                        $result->$expandedPropertyName = $result1;
501
                        $projectedProperty7 = $expandedProjectionNode->getResourceProperty();
502
                        $needPop = $this->_pushSegmentForNavigationProperty(
503
                            $projectedProperty7
504
                        );
505
                        $this->_executeExpansion($result1);
506
                        $this->_popSegment($needPop);
507
                    } else {
508
                        $result->$expandedPropertyName = array();
509
                    }
510
                } else {
511
                    $currentResourceSet3 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
512
                    $resourceSetOfProjectedProperty3 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
513
                    $projectedProperty5 = $expandedProjectionNode->getResourceProperty();
514
                    $result1 = $this->providers->getRelatedResourceReference(
515
                        $currentResourceSet3,
516
                        $result,
517
                        $resourceSetOfProjectedProperty3,
518
                        $projectedProperty5
519
                    );
520
                    $result->$expandedPropertyName = $result1;
521
                    if (!is_null($result1)) {
522
                        $projectedProperty6 = $expandedProjectionNode->getResourceProperty();
523
                        $needPop = $this->_pushSegmentForNavigationProperty(
524
                            $projectedProperty6
525
                        );
526
                        $this->_executeExpansion($result1);
527
                        $this->_popSegment($needPop);
528
                    }
529
                }
530
            }
531
        }
532
    }
533
534
    /**
535
     * Resource set wrapper for the resource being retireved.
536
     *
537
     * @return ResourceSetWrapper
538
     */
539 View Code Duplication
    private 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...
540
    {
541
        $count = count($this->_segmentResourceSetWrappers);
542
        if ($count == 0) {
543
            return $this->request->getTargetResourceSetWrapper();
544
        } else {
545
            return $this->_segmentResourceSetWrappers[$count - 1];
546
        }
547
    }
548
549
    /**
550
     * Pushes a segment for the root of the tree 
551
     * Note: Calls to this method should be balanced with calls to popSegment.
552
     *
553
     * @return bool true if the segment was pushed, false otherwise.
554
     */
555
    private function _pushSegmentForRoot()
556
    {
557
        $segmentName = $this->request->getContainerName();
558
        $segmentResourceSetWrapper 
559
            = $this->request->getTargetResourceSetWrapper();
560
        return $this->_pushSegment($segmentName, $segmentResourceSetWrapper);
0 ignored issues
show
Bug introduced by
It seems like $segmentResourceSetWrapper defined by $this->request->getTargetResourceSetWrapper() on line 559 can be null; however, POData\UriProcessor\UriProcessor::_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...
561
    }
562
563
    /**
564
     * Pushes a segment for the current navigation property being written out.
565
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
566
     * 'Segment Stack' and this method.
567
     * Note: Calls to this method should be balanced with calls to popSegment.
568
     *
569
     * @param ResourceProperty &$resourceProperty Current navigation property 
570
     *                                            being written out
571
     *
572
     * @return bool true if a segment was pushed, false otherwise
573
     *
574
     * @throws InvalidOperationException If this function invoked with non-navigation
575
     *                                   property instance.
576
     */
577 View Code Duplication
    private 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...
578
    {
579
        if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) {
580
            $this->assert(
581
                !empty($this->_segmentNames), 
582
                '!is_empty($this->_segmentNames'
583
            );
584
            $currentResourceSetWrapper = $this->_getCurrentResourceSetWrapper();
585
            $currentResourceType = $currentResourceSetWrapper->getResourceType();
586
            $currentResourceSetWrapper = $this->service
587
                ->getProvidersWrapper()
588
                ->getResourceSetWrapperForNavigationProperty(
589
                    $currentResourceSetWrapper,
0 ignored issues
show
Bug introduced by
It seems like $currentResourceSetWrapper defined by $this->service->getProvi...ype, $resourceProperty) on line 586 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...
590
                    $currentResourceType,
591
                    $resourceProperty
592
                );
593
594
            $this->assert(
595
                !is_null($currentResourceSetWrapper), 
596
                '!null($currentResourceSetWrapper)'
597
            );
598
            return $this->_pushSegment(
599
                $resourceProperty->getName(), 
600
                $currentResourceSetWrapper
0 ignored issues
show
Bug introduced by
It seems like $currentResourceSetWrapper defined by $this->service->getProvi...ype, $resourceProperty) on line 586 can be null; however, POData\UriProcessor\UriProcessor::_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...
601
            );
602
        } else {
603
            throw new InvalidOperationException(
604
                'pushSegmentForNavigationProperty should not be called with non-entity type'
605
            );
606
        }
607
    }
608
609
    /**
610
     * Gets collection of expanded projection nodes under the current node.
611
     *
612
     * @return ExpandedProjectionNode[] List of nodes describing expansions for the current segment
613
     *
614
     */
615
    private function _getExpandedProjectionNodes()
616
    {
617
        $expandedProjectionNode = $this->_getCurrentExpandedProjectionNode();
618
        $expandedProjectionNodes = array();
619
        if (!is_null($expandedProjectionNode)) {
620
            foreach ($expandedProjectionNode->getChildNodes() as $node) {
621
                if ($node instanceof ExpandedProjectionNode) {
622
                    $expandedProjectionNodes[] = $node;
623
                }
624
            }
625
        }
626
627
        return $expandedProjectionNodes;
628
    }
629
630
    /**
631
     * Find a 'ExpandedProjectionNode' instance in the projection tree 
632
     * which describes the current segment.
633
     *
634
     * @return ExpandedProjectionNode|null
635
     */
636 View Code Duplication
    private 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...
637
    {
638
        $expandedProjectionNode 
639
            = $this->request->getRootProjectionNode();
640
        if (!is_null($expandedProjectionNode)) {
641
            $depth = count($this->_segmentNames);
642
            if ($depth != 0) {
643
                for ($i = 1; $i < $depth; $i++) {
644
                    $expandedProjectionNode
645
                        = $expandedProjectionNode->findNode($this->_segmentNames[$i]);
646
                        $this->assert(
647
                            !is_null($expandedProjectionNode),
648
                            '!is_null($expandedProjectionNode)'
649
                        );
650
                        $this->assert(
651
                            $expandedProjectionNode instanceof ExpandedProjectionNode,
652
                            '$expandedProjectionNode instanceof ExpandedProjectionNode'
653
                        );
654
                }
655
            }
656
        }
657
658
        return $expandedProjectionNode;
659
    }
660
661
    /**
662
     * Pushes information about the segment whose instance is going to be
663
     * retrieved from the IDSQP implementation
664
     * Note: Calls to this method should be balanced with calls to popSegment.
665
     *
666
     * @param string             $segmentName         Name of segment to push.
667
     * @param ResourceSetWrapper &$resourceSetWrapper The resource set wrapper 
668
     *                                                to push.
669
     *
670
     * @return bool true if the segment was push, false otherwise
671
     */
672 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...
673
    {
674
        $rootProjectionNode = $this->request->getRootProjectionNode();
675
        if (!is_null($rootProjectionNode) 
676
            && $rootProjectionNode->isExpansionSpecified()
677
        ) {
678
            array_push($this->_segmentNames, $segmentName);
679
            array_push($this->_segmentResourceSetWrappers, $resourceSetWrapper);
680
            return true;
681
        }
682
683
        return false;
684
    }
685
686
    /**
687
     * Pops segment information from the 'Segment Stack'
688
     * Note: Calls to this method should be balanced with previous calls 
689
     * to _pushSegment.
690
     * 
691
     * @param boolean $needPop Is a pop required. Only true if last push 
692
     *                         was successful.
693
     * 
694
     * @return void
695
     * 
696
     * @throws InvalidOperationException If found un-balanced call 
697
     *                                   with _pushSegment
698
     */
699 View Code Duplication
    private 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...
700
    {
701
        if ($needPop) {
702
            if (!empty($this->_segmentNames)) {
703
                array_pop($this->_segmentNames);
704
                array_pop($this->_segmentResourceSetWrappers);
705
            } else {
706
                throw new InvalidOperationException(
707
                    'Found non-balanced call to _pushSegment and popSegment'
708
                );
709
            }
710
        }
711
    }
712
    
713
    /**
714
     * Assert that the given condition is true.
715
     * 
716
     * @param boolean $condition         Constion to assert.
717
     * @param string  $conditionAsString Message to show incase assertion fails.
718
     * 
719
     * @return void
720
     * 
721
     * @throws InvalidOperationException
722
     */
723
    protected function assert($condition, $conditionAsString)
724
    {
725
        if (!$condition) {
726
            throw new InvalidOperationException(
727
                "Unexpected state, expecting $conditionAsString"
728
            );
729
        }
730
    }
731
}