Completed
Push — master ( f1c3b7...bffd1d )
by Bálint
10:19
created

UriProcessor::applyQueryOptions()   C

Complexity

Conditions 9
Paths 20

Size

Total Lines 46
Code Lines 20

Duplication

Lines 14
Ratio 30.43 %

Importance

Changes 0
Metric Value
dl 14
loc 46
rs 5.0942
c 0
b 0
f 0
cc 9
eloc 20
nc 20
nop 2
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
use POData\OperationContext\HTTPRequestMethod;
25
26
/**
27
 * Class UriProcessor
28
 *
29
 * A type to process client's requets URI
30
 * The syntax of request URI is:
31
 *  Scheme Host Port ServiceRoot ResourcePath ? QueryOption
32
 * For more details refer:
33
 * http://www.odata.org/developers/protocols/uri-conventions#UriComponents
34
 *
35
 * @package POData\UriProcessor
36
 */
37
class UriProcessor
38
{
39
    /**
40
     * Description of the OData request that a client has submitted.
41
     *
42
     * @var RequestDescription
43
     */
44
    private $request;
45
46
    /**
47
     * Holds reference to the data service instance.
48
     *
49
     * @var IService
50
     */
51
    private $service;
52
53
    /**
54
     * Holds reference to the wrapper over IDSMP and IDSQP implementation.
55
     *
56
     * @var ProvidersWrapper
57
     */
58
    private $providers;
59
60
    /**
61
     * Collection of segment names.
62
     *
63
     * @var string[]
64
     */
65
    private $_segmentNames;
66
67
    /**
68
     * Collection of segment ResourceSetWrapper instances.
69
     *
70
     * @var ResourceSetWrapper[]
71
     */
72
    private $_segmentResourceSetWrappers;
73
74
    /**
75
     * Constructs a new instance of UriProcessor
76
     *
77
     * @param IService $service Reference to the data service instance.
78
     */
79
    private function __construct(IService $service)
80
    {
81
        $this->service = $service;
82
        $this->providers = $service->getProvidersWrapper();
83
        $this->_segmentNames = array();
84
        $this->_segmentResourceSetWrappers = array();
85
    }
86
87
    /**
88
     * Process the resource path and query options of client's request uri.
89
     *
90
     * @param IService $service Reference to the data service instance.
91
     *
92
     * @return URIProcessor
93
     *
94
     * @throws ODataException
95
     */
96
    public static function process(IService $service)
97
    {
98
        $absoluteRequestUri = $service->getHost()->getAbsoluteRequestUri();
99
        $absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri();
100
101
        if (!$absoluteServiceUri->isBaseOf($absoluteRequestUri)) {
102
			throw ODataException::createInternalServerError(
103
                Messages::uriProcessorRequestUriDoesNotHaveTheRightBaseUri(
104
                    $absoluteRequestUri->getUrlAsString(),
105
                    $absoluteServiceUri->getUrlAsString()
106
                )
107
            );
108
        }
109
110
        $uriProcessor = new UriProcessor($service);
111
        //Parse the resource path part of the request Uri.
112
		$uriProcessor->request = ResourcePathProcessor::process($service);
113
114
	    $uriProcessor->request->setUriProcessor($uriProcessor);
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
        $operationContext = $this->service->getOperationContext();
138
        if (!$operationContext) {
139
            $this->executeBase();
140
            return;
141
        }
142
143
        $requestMethod = $operationContext->incomingRequest()->getMethod();
144
        if ($requestMethod == HTTPRequestMethod::GET()) {
145
            $this->executeGet();
146
        }
147
        elseif ($requestMethod == HTTPRequestMethod::PUT()) {
148
            $this->executePut();
149
        }
150
        elseif ($requestMethod == HTTPRequestMethod::POST()) {
151
            $this->executePost();
152
        }
153
        else {
154
            throw ODataException::createNotImplementedError(Messages::unsupportedMethod($requestMethod));
155
        }
156
    }
157
158
    /**
159
     * Execute the client submitted request against the data source (GET)
160
     */
161
    protected function executeGet()
162
    {
163
        return $this->executeBase();
164
    }
165
166
    /**
167
     * Execute the client submitted request against the data source (PUT)
168
     */
169 View Code Duplication
    protected function executePut()
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...
170
    {
171
        return $this->executeBase(function ($uriProcessor, $segment) {
172
            $requestMethod = $uriProcessor->service->getOperationContext()->incomingRequest()->getMethod();
173
            $resourceSet = $segment->getTargetResourceSetWrapper();
174
            $keyDescriptor = $segment->getKeyDescriptor();
175
            $data = $uriProcessor->request->getData();
176
177
            if (!$resourceSet || !$keyDescriptor) {
178
                $url = $uriProcessor->service->getHost()->getAbsoluteRequestUri()->getUrlAsString();
179
                throw ODataException::createBadRequestError(Messages::badRequestInvalidUriForThisVerb($url, $requestMethod));
180
            }
181
182
            if (!$data) {
183
                throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
184
            }
185
186
            return $uriProcessor->providers->putResource($resourceSet, $keyDescriptor, $data);
187
        });
188
    }
189
190
    /**
191
     * Execute the client submitted request against the data source (POST)
192
     */
193 View Code Duplication
    protected function executePost()
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...
194
    {
195
        return $this->executeBase(function ($uriProcessor, $segment) {
196
            $requestMethod = $uriProcessor->service->getOperationContext()->incomingRequest()->getMethod();
197
            $resourceSet = $segment->getTargetResourceSetWrapper();
198
            $keyDescriptor = $segment->getKeyDescriptor();
0 ignored issues
show
Unused Code introduced by
$keyDescriptor 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...
199
            $data = $uriProcessor->request->getData();
200
201
            if (!$resourceSet) {
202
                $url = $uriProcessor->service->getHost()->getAbsoluteRequestUri()->getUrlAsString();
203
                throw ODataException::createBadRequestError(Messages::badRequestInvalidUriForThisVerb($url, $requestMethod));
204
            }
205
206
            if (!$data) {
207
                throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
208
            }
209
210
            return $uriProcessor->providers->postResource($resourceSet, $data);
211
        });
212
    }
213
214
    /**
215
     * Execute the client submitted request against the data source
216
     *
217
     * @param callable $callback Function, what must be called
218
     */
219
    protected function executeBase($callback = null)
220
    {
221
        $segments = $this->request->getSegments();
222
223
        foreach ($segments as $segment) {
224
225
            $requestTargetKind = $segment->getTargetKind();
226
227
	        if ($segment->getTargetSource() == TargetSource::ENTITY_SET) {
228
                $this->handleSegmentTargetsToResourceSet($segment);
229
            } else if ($requestTargetKind == TargetKind::RESOURCE()) {
230
                if (is_null($segment->getPrevious()->getResult())) {
231
					throw ODataException::createResourceNotFoundError(
232
                        $segment->getPrevious()->getIdentifier()
233
                    );
234
                }
235
                $this->_handleSegmentTargetsToRelatedResource($segment);
236
            } else if ($requestTargetKind == TargetKind::LINK()) {
237
                $segment->setResult($segment->getPrevious()->getResult());
238
            } else if ($segment->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) {
239
                // we are done, $count will the last segment and
240
                // taken care by _applyQueryOptions method
241
                $segment->setResult($this->request->getCountValue());
242
                break;
243
            } else {
244
                if ($requestTargetKind == TargetKind::MEDIA_RESOURCE()) {
245
                    if (is_null($segment->getPrevious()->getResult())) {
246
						throw ODataException::createResourceNotFoundError(
247
                            $segment->getPrevious()->getIdentifier()
248
                        );
249
                    }
250
                    // For MLE and Named Stream the result of last segment
251
                    // should be that of previous segment, this is required
252
                    // while retrieving content type or stream from IDSSP
253
                    $segment->setResult($segment->getPrevious()->getResult());
254
                    // we are done, as named stream property or $value on
255
                    // media resource will be the last segment
256
                    break;
257
                }
258
259
	            $value = $segment->getPrevious()->getResult();
260
                while (!is_null($segment)) {
261
	                //TODO: what exactly is this doing here?  Once a null's found it seems everything will be null
262
                    if (!is_null($value)) {
263
                        $value = null;
264
                    } else {
265
                        try {
266
	                        //see #88
267
                            $property = new \ReflectionProperty($value, $segment->getIdentifier());
268
                            $value = $property->getValue($value);
269
                        } catch (\ReflectionException $reflectionException) {
270
                            //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...
271
                        }
272
                    }
273
274
                    $segment->setResult($value);
275
                    $segment = $segment->getNext();
276
                    if (!is_null($segment) && $segment->getIdentifier() == ODataConstants::URI_VALUE_SEGMENT) {
277
                        $segment->setResult($value);
278
                        $segment = $segment->getNext();
279
                    }
280
                }
281
282
                break;
283
284
            }
285
286
            if (is_null($segment->getNext()) || $segment->getNext()->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) {
287
                $this->applyQueryOptions($segment, $callback);
288
            }
289
        }
290
291
         // Apply $select and $expand options to result set, this function will be always applied
292
         // irrespective of return value of IDSQP2::canApplyQueryOptions which means library will
293
         // not delegate $expand/$select operation to IDSQP2 implementation
294
        $this->handleExpansion();
295
    }
296
297
    /**
298
     * Query for a resource set pointed by the given segment descriptor and update the descriptor with the result.
299
     *
300
     * @param SegmentDescriptor $segment Describes the resource set to query
301
     * @return void
302
     *
303
     */
304
    private function handleSegmentTargetsToResourceSet( SegmentDescriptor $segment ) {
305
        if ($segment->isSingleResult()) {
306
            $entityInstance = $this->providers->getResourceFromResourceSet(
307
                $segment->getTargetResourceSetWrapper(),
308
                $segment->getKeyDescriptor()
309
            );
310
311
            $segment->setResult($entityInstance);
312
313
        } else {
314
315
            $internalskiptokentinfo = $this->request->getInternalSkipTokenInfo();
316
317
            $queryResult = $this->providers->getResourceSet(
318
	            $this->request->queryType,
319
                $segment->getTargetResourceSetWrapper(),
320
                $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...
321
                $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...
322
                $this->request->getTopCount(),
323
                $this->request->getSkipCount(),
324
                $internalskiptokentinfo ? $internalskiptokentinfo->getSkipTokenInfo() : null,
325
                $this->_getExpandedProjectionNodes()
326
            );
327
            $segment->setResult($queryResult);
328
        }
329
    }
330
331
    /**
332
     * Query for a related resource set or resource set reference pointed by the
333
     * given segment descriptor and update the descriptor with the result.
334
     *
335
     * @param SegmentDescriptor &$segment Describes the related resource
336
     *                                              to query.
337
     *
338
     * @return void
339
     */
340
    private function _handleSegmentTargetsToRelatedResource(SegmentDescriptor $segment) {
341
        $projectedProperty = $segment->getProjectedProperty();
342
        $projectedPropertyKind = $projectedProperty->getKind();
343
344
        if ($projectedPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE) {
345
            if ($segment->isSingleResult()) {
346
                $entityInstance = $this->providers->getResourceFromRelatedResourceSet(
347
                    $segment->getPrevious()->getTargetResourceSetWrapper(),
348
                    $segment->getPrevious()->getResult(),
349
                    $segment->getTargetResourceSetWrapper(),
350
                    $projectedProperty,
351
                    $segment->getKeyDescriptor()
352
                );
353
354
                $segment->setResult($entityInstance);
355
            } else {
356
                $queryResult = $this->providers->getRelatedResourceSet(
357
	                $this->request->queryType,
358
                    $segment->getPrevious()->getTargetResourceSetWrapper(),
359
                    $segment->getPrevious()->getResult(),
360
                    $segment->getTargetResourceSetWrapper(),
361
                    $segment->getProjectedProperty(),
362
                    $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...
363
	                //TODO: why are these null?  see #98
364
                    null, // $orderby
365
                    null, // $top
366
                    null  // $skip
367
                );
368
369
                $segment->setResult($queryResult);
370
            }
371
        } else if ($projectedPropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE) {
372
            $entityInstance = $this->providers->getRelatedResourceReference(
373
                $segment->getPrevious()->getTargetResourceSetWrapper(),
374
                $segment->getPrevious()->getResult(),
375
                $segment->getTargetResourceSetWrapper(),
376
                $segment->getProjectedProperty()
377
            );
378
379
            $segment->setResult($entityInstance);
380
        } 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...
381
            //Unexpected state
382
        }
383
    }
384
385
    /**
386
     * Applies the query options to the resource(s) retrieved from the data source.
387
     *
388
     * @param SegmentDescriptor $segment The descriptor which holds resource(s) on which query options to be applied.
389
     * @param callable $callback Function, what must be called
390
     *
391
     */
392
    private function applyQueryOptions(SegmentDescriptor $segment, $callback = null)
393
    {
394
        // For non-GET methods
395
        if ($callback) {
396
            $callback($this, $segment);
397
            return;
398
        }
399
400
	    //TODO: I'm not really happy with this..i think i'd rather keep the result the QueryResult
401
	    //not even bother with the setCountValue stuff (shouldn't counts be on segments?)
402
	    //and just work with the QueryResult in the object model serializer
403
	    $result = $segment->getResult();
404
405
	    if(!$result instanceof QueryResult){
406
		    //If the segment isn't a query result, then there's no paging or counting to be done
407
		    return;
408
        }
409
410
411
        // Note $inlinecount=allpages means include the total count regardless of paging..so we set the counts first
412
	    // regardless if POData does the paging or not.
413 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...
414
            if ($this->providers->handlesOrderedPaging()) {
415
                $this->request->setCountValue($result->count);
416
            } else {
417
                $this->request->setCountValue(count($result->results));
418
            }
419
        }
420
421
	    //Have POData perform paging if necessary
422
	    if(!$this->providers->handlesOrderedPaging() && !empty($result->results)){
423
			$result->results = $this->performPaging($result->results);
424
	    }
425
426
	    //a bit surprising, but $skip and $top affects $count so update it here, not above
427
	    //IE  data.svc/Collection/$count?$top=10 returns 10 even if Collection has 11+ entries
428 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...
429
		    if ($this->providers->handlesOrderedPaging()) {
430
			    $this->request->setCountValue($result->count);
431
		    } else {
432
			    $this->request->setCountValue(count($result->results));
433
		    }
434
	    }
435
436
        $segment->setResult($result->results);
437
    }
438
439
	/**
440
	 * If the provider does not perform the paging (ordering, top, skip) then this method does it
441
	 *
442
	 * @param array $result
443
	 * @return array
444
	 */
445
	private function performPaging(array $result)
446
	{
447
		//Apply (implicit and explicit) $orderby option
448
		$internalOrderByInfo = $this->request->getInternalOrderByInfo();
449
		if (!is_null($internalOrderByInfo)) {
450
			$orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
451
			usort($result, $orderByFunction);
452
		}
453
454
		//Apply $skiptoken option
455
		$internalSkipTokenInfo = $this->request->getInternalSkipTokenInfo();
456
		if (!is_null($internalSkipTokenInfo)) {
457
			$matchingIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($result);
458
			$result = array_slice($result, $matchingIndex);
459
		}
460
461
		//Apply $top and $skip option
462
		if (!empty($result)) {
463
			$top  = $this->request->getTopCount();
464
			$skip = $this->request->getSkipCount();
465
			if(is_null($skip)) $skip = 0;
466
467
			$result = array_slice($result, $skip, $top);
468
		}
469
470
		return $result;
471
	}
472
473
474
    /**
475
     * Perform expansion.
476
     *
477
     * @return void
478
     */
479
    private function handleExpansion()
480
    {
481
        $node = $this->request->getRootProjectionNode();
482
        if (!is_null($node) && $node->isExpansionSpecified()) {
483
            $result = $this->request->getTargetResult();
484
            if (!is_null($result) || is_array($result) && !empty($result)) {
485
                $needPop = $this->_pushSegmentForRoot();
486
                $this->_executeExpansion($result);
487
                $this->_popSegment($needPop);
488
            }
489
        }
490
    }
491
492
    /**
493
     * Execute queries for expansion.
494
     *
495
     * @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...
496
     *
497
     *
498
     * @return void
499
     */
500
    private function _executeExpansion($result)
501
    {
502
        $expandedProjectionNodes = $this->_getExpandedProjectionNodes();
503
        foreach ($expandedProjectionNodes as $expandedProjectionNode) {
504
            $isCollection = $expandedProjectionNode->getResourceProperty()->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE;
505
            $expandedPropertyName = $expandedProjectionNode->getResourceProperty()->getName();
506
            if (is_array($result)) {
507
                foreach ($result as $entry) {
508
                    // Check for null entry
509 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...
510
                        $currentResourceSet = $this->_getCurrentResourceSetWrapper()->getResourceSet();
511
                        $resourceSetOfProjectedProperty = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
512
                        $projectedProperty1 = $expandedProjectionNode->getResourceProperty();
513
                        $result1 = $this->providers->getRelatedResourceSet(
514
	                        QueryType::ENTITIES(), //it's always entities for an expansion
515
                            $currentResourceSet,
516
                            $entry,
517
                            $resourceSetOfProjectedProperty,
518
                            $projectedProperty1,
519
                            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...
520
                            null, // $orderby
521
                            null, // $top
522
                            null  // $skip
523
                        )->results;
524
                        if (!empty($result1)) {
525
                            $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo();
526
                            if (!is_null($internalOrderByInfo)) {
527
                                $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
528
                                usort($result1, $orderByFunction);
529
                                unset($internalOrderByInfo);
530
                                $takeCount = $expandedProjectionNode->getTakeCount();
531
                                if (!is_null($takeCount)) {
532
                                    $result1 = array_slice($result1, 0, $takeCount);
533
                                }
534
                            }
535
536
                            $entry->$expandedPropertyName = $result1;
537
                            $projectedProperty = $expandedProjectionNode->getResourceProperty();
538
                            $needPop = $this->_pushSegmentForNavigationProperty(
539
                                $projectedProperty
540
                            );
541
                            $this->_executeExpansion($result1);
542
                            $this->_popSegment($needPop);
543
                        } else {
544
                            $entry->$expandedPropertyName = array();
545
                        }
546
                    } else {
547
                        $currentResourceSet1 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
548
                        $resourceSetOfProjectedProperty1 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
549
                        $projectedProperty2 = $expandedProjectionNode->getResourceProperty();
550
                        $result1 = $this->providers->getRelatedResourceReference(
551
                            $currentResourceSet1,
552
                            $entry,
553
                            $resourceSetOfProjectedProperty1,
554
                            $projectedProperty2
555
                        );
556
                        $entry->$expandedPropertyName = $result1;
557
                        if (!is_null($result1)) {
558
                            $projectedProperty3 = $expandedProjectionNode->getResourceProperty();
559
                            $needPop = $this->_pushSegmentForNavigationProperty(
560
                                $projectedProperty3
561
                            );
562
                            $this->_executeExpansion($result1);
563
                            $this->_popSegment($needPop);
564
                        }
565
                    }
566
                }
567 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...
568
                if ($isCollection) {
569
                    $currentResourceSet2 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
570
                    $resourceSetOfProjectedProperty2 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
571
                    $projectedProperty4 = $expandedProjectionNode->getResourceProperty();
572
                    $result1 = $this->providers->getRelatedResourceSet(
573
	                    QueryType::ENTITIES(), //it's always entities for an expansion
574
                        $currentResourceSet2,
575
                        $result,
576
                        $resourceSetOfProjectedProperty2,
577
                        $projectedProperty4,
578
                        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...
579
                        null, // $orderby
580
                        null, // $top
581
                        null  // $skip
582
                    )->results;
583
                    if (!empty($result1)) {
584
                        $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo();
585
                        if (!is_null($internalOrderByInfo)) {
586
                            $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
587
                            usort($result1, $orderByFunction);
588
                            unset($internalOrderByInfo);
589
                            $takeCount = $expandedProjectionNode->getTakeCount();
590
                            if (!is_null($takeCount)) {
591
                                $result1 = array_slice($result1, 0, $takeCount);
592
                            }
593
                        }
594
595
                        $result->$expandedPropertyName = $result1;
596
                        $projectedProperty7 = $expandedProjectionNode->getResourceProperty();
597
                        $needPop = $this->_pushSegmentForNavigationProperty(
598
                            $projectedProperty7
599
                        );
600
                        $this->_executeExpansion($result1);
601
                        $this->_popSegment($needPop);
602
                    } else {
603
                        $result->$expandedPropertyName = array();
604
                    }
605
                } else {
606
                    $currentResourceSet3 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
607
                    $resourceSetOfProjectedProperty3 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
608
                    $projectedProperty5 = $expandedProjectionNode->getResourceProperty();
609
                    $result1 = $this->providers->getRelatedResourceReference(
610
                        $currentResourceSet3,
611
                        $result,
612
                        $resourceSetOfProjectedProperty3,
613
                        $projectedProperty5
614
                    );
615
                    $result->$expandedPropertyName = $result1;
616
                    if (!is_null($result1)) {
617
                        $projectedProperty6 = $expandedProjectionNode->getResourceProperty();
618
                        $needPop = $this->_pushSegmentForNavigationProperty(
619
                            $projectedProperty6
620
                        );
621
                        $this->_executeExpansion($result1);
622
                        $this->_popSegment($needPop);
623
                    }
624
                }
625
            }
626
        }
627
    }
628
629
    /**
630
     * Resource set wrapper for the resource being retireved.
631
     *
632
     * @return ResourceSetWrapper
633
     */
634 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...
635
    {
636
        $count = count($this->_segmentResourceSetWrappers);
637
        if ($count == 0) {
638
            return $this->request->getTargetResourceSetWrapper();
639
        } else {
640
            return $this->_segmentResourceSetWrappers[$count - 1];
641
        }
642
    }
643
644
    /**
645
     * Pushes a segment for the root of the tree
646
     * Note: Calls to this method should be balanced with calls to popSegment.
647
     *
648
     * @return bool true if the segment was pushed, false otherwise.
649
     */
650
    private function _pushSegmentForRoot()
651
    {
652
        $segmentName = $this->request->getContainerName();
653
        $segmentResourceSetWrapper
654
            = $this->request->getTargetResourceSetWrapper();
655
        return $this->_pushSegment($segmentName, $segmentResourceSetWrapper);
0 ignored issues
show
Bug introduced by
It seems like $segmentResourceSetWrapper defined by $this->request->getTargetResourceSetWrapper() on line 654 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...
656
    }
657
658
    /**
659
     * Pushes a segment for the current navigation property being written out.
660
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
661
     * 'Segment Stack' and this method.
662
     * Note: Calls to this method should be balanced with calls to popSegment.
663
     *
664
     * @param ResourceProperty &$resourceProperty Current navigation property
665
     *                                            being written out
666
     *
667
     * @return bool true if a segment was pushed, false otherwise
668
     *
669
     * @throws InvalidOperationException If this function invoked with non-navigation
670
     *                                   property instance.
671
     */
672 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...
673
    {
674
        if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) {
675
            $this->assert(
676
                !empty($this->_segmentNames),
677
                '!is_empty($this->_segmentNames'
678
            );
679
            $currentResourceSetWrapper = $this->_getCurrentResourceSetWrapper();
680
            $currentResourceType = $currentResourceSetWrapper->getResourceType();
681
            $currentResourceSetWrapper = $this->service
682
                ->getProvidersWrapper()
683
                ->getResourceSetWrapperForNavigationProperty(
684
                    $currentResourceSetWrapper,
0 ignored issues
show
Bug introduced by
It seems like $currentResourceSetWrapper defined by $this->service->getProvi...ype, $resourceProperty) on line 681 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...
685
                    $currentResourceType,
686
                    $resourceProperty
687
                );
688
689
            $this->assert(
690
                !is_null($currentResourceSetWrapper),
691
                '!null($currentResourceSetWrapper)'
692
            );
693
            return $this->_pushSegment(
694
                $resourceProperty->getName(),
695
                $currentResourceSetWrapper
0 ignored issues
show
Bug introduced by
It seems like $currentResourceSetWrapper defined by $this->service->getProvi...ype, $resourceProperty) on line 681 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...
696
            );
697
        } else {
698
            throw new InvalidOperationException(
699
                'pushSegmentForNavigationProperty should not be called with non-entity type'
700
            );
701
        }
702
    }
703
704
    /**
705
     * Gets collection of expanded projection nodes under the current node.
706
     *
707
     * @return ExpandedProjectionNode[] List of nodes describing expansions for the current segment
708
     *
709
     */
710
    private function _getExpandedProjectionNodes()
711
    {
712
        $expandedProjectionNode = $this->_getCurrentExpandedProjectionNode();
713
        $expandedProjectionNodes = array();
714
        if (!is_null($expandedProjectionNode)) {
715
            foreach ($expandedProjectionNode->getChildNodes() as $node) {
716
                if ($node instanceof ExpandedProjectionNode) {
717
                    $expandedProjectionNodes[] = $node;
718
                }
719
            }
720
        }
721
722
        return $expandedProjectionNodes;
723
    }
724
725
    /**
726
     * Find a 'ExpandedProjectionNode' instance in the projection tree
727
     * which describes the current segment.
728
     *
729
     * @return ExpandedProjectionNode|null
730
     */
731 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...
732
    {
733
        $expandedProjectionNode
734
            = $this->request->getRootProjectionNode();
735
        if (!is_null($expandedProjectionNode)) {
736
            $depth = count($this->_segmentNames);
737
            if ($depth != 0) {
738
                for ($i = 1; $i < $depth; $i++) {
739
                    $expandedProjectionNode
740
                        = $expandedProjectionNode->findNode($this->_segmentNames[$i]);
741
                        $this->assert(
742
                            !is_null($expandedProjectionNode),
743
                            '!is_null($expandedProjectionNode)'
744
                        );
745
                        $this->assert(
746
                            $expandedProjectionNode instanceof ExpandedProjectionNode,
747
                            '$expandedProjectionNode instanceof ExpandedProjectionNode'
748
                        );
749
                }
750
            }
751
        }
752
753
        return $expandedProjectionNode;
754
    }
755
756
    /**
757
     * Pushes information about the segment whose instance is going to be
758
     * retrieved from the IDSQP implementation
759
     * Note: Calls to this method should be balanced with calls to popSegment.
760
     *
761
     * @param string             $segmentName         Name of segment to push.
762
     * @param ResourceSetWrapper &$resourceSetWrapper The resource set wrapper
763
     *                                                to push.
764
     *
765
     * @return bool true if the segment was push, false otherwise
766
     */
767 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...
768
    {
769
        $rootProjectionNode = $this->request->getRootProjectionNode();
770
        if (!is_null($rootProjectionNode)
771
            && $rootProjectionNode->isExpansionSpecified()
772
        ) {
773
            array_push($this->_segmentNames, $segmentName);
774
            array_push($this->_segmentResourceSetWrappers, $resourceSetWrapper);
775
            return true;
776
        }
777
778
        return false;
779
    }
780
781
    /**
782
     * Pops segment information from the 'Segment Stack'
783
     * Note: Calls to this method should be balanced with previous calls
784
     * to _pushSegment.
785
     *
786
     * @param boolean $needPop Is a pop required. Only true if last push
787
     *                         was successful.
788
     *
789
     * @return void
790
     *
791
     * @throws InvalidOperationException If found un-balanced call
792
     *                                   with _pushSegment
793
     */
794 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...
795
    {
796
        if ($needPop) {
797
            if (!empty($this->_segmentNames)) {
798
                array_pop($this->_segmentNames);
799
                array_pop($this->_segmentResourceSetWrappers);
800
            } else {
801
                throw new InvalidOperationException(
802
                    'Found non-balanced call to _pushSegment and popSegment'
803
                );
804
            }
805
        }
806
    }
807
808
    /**
809
     * Assert that the given condition is true.
810
     *
811
     * @param boolean $condition         Constion to assert.
812
     * @param string  $conditionAsString Message to show incase assertion fails.
813
     *
814
     * @return void
815
     *
816
     * @throws InvalidOperationException
817
     */
818
    protected function assert($condition, $conditionAsString)
819
    {
820
        if (!$condition) {
821
            throw new InvalidOperationException(
822
                "Unexpected state, expecting $conditionAsString"
823
            );
824
        }
825
    }
826
}
827