Completed
Push — master ( bffd1d...b8dc97 )
by Bálint
11:00
created

UriProcessor::executePost()   B

Complexity

Conditions 3
Paths 1

Size

Total Lines 24
Code Lines 14

Duplication

Lines 24
Ratio 100 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 24
loc 24
rs 8.9713
cc 3
eloc 14
nc 1
nop 0
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
        elseif ($requestMethod == HTTPRequestMethod::DELETE) {
154
            $this->executeDelete();
155
        }
156
        else {
157
            throw ODataException::createNotImplementedError(Messages::unsupportedMethod($requestMethod));
158
        }
159
    }
160
161
    /**
162
     * Execute the client submitted request against the data source (GET)
163
     */
164
    protected function executeGet()
165
    {
166
        return $this->executeBase();
167
    }
168
169
    /**
170
     * Execute the client submitted request against the data source (PUT)
171
     */
172 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...
173
    {
174
        return $this->executeBase(function ($uriProcessor, $segment) {
175
            $requestMethod = $uriProcessor->service->getOperationContext()->incomingRequest()->getMethod();
176
            $resourceSet = $segment->getTargetResourceSetWrapper();
177
            $keyDescriptor = $segment->getKeyDescriptor();
178
            $data = $uriProcessor->request->getData();
179
180
            if (!$resourceSet || !$keyDescriptor) {
181
                $url = $uriProcessor->service->getHost()->getAbsoluteRequestUri()->getUrlAsString();
182
                throw ODataException::createBadRequestError(Messages::badRequestInvalidUriForThisVerb($url, $requestMethod));
183
            }
184
185
            if (!$data) {
186
                throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
187
            }
188
189
            return $uriProcessor->providers->putResource($resourceSet, $keyDescriptor, $data);
190
        });
191
    }
192
193
    /**
194
     * Execute the client submitted request against the data source (POST)
195
     */
196 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...
197
    {
198
        return $this->executeBase(function ($uriProcessor, $segment) {
199
            $requestMethod = $uriProcessor->service->getOperationContext()->incomingRequest()->getMethod();
200
            $resourceSet = $segment->getTargetResourceSetWrapper();
201
            $data = $uriProcessor->request->getData();
202
203
            if (!$resourceSet) {
204
                $url = $uriProcessor->service->getHost()->getAbsoluteRequestUri()->getUrlAsString();
205
                throw ODataException::createBadRequestError(Messages::badRequestInvalidUriForThisVerb($url, $requestMethod));
206
            }
207
208
            if (!$data) {
209
                throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
210
            }
211
212
            $entity = $uriProcessor->providers->postResource($resourceSet, $data);
213
214
            $segment->setSingleResult(true);
215
            $segment->setResult($entity);
216
217
            return $entity;
218
        });
219
    }
220
221
    /**
222
     * Execute the client submitted request against the data source (POST)
223
     */
224
    protected function executeDelete()
225
    {
226
        return $this->executeBase(function ($uriProcessor, $segment) {
227
            $requestMethod = $uriProcessor->service->getOperationContext()->incomingRequest()->getMethod();
228
            $resourceSet = $segment->getTargetResourceSetWrapper();
229
            $keyDescriptor = $segment->getKeyDescriptor();
230
           
231
            if (!$resourceSet) {
232
                $url = $uriProcessor->service->getHost()->getAbsoluteRequestUri()->getUrlAsString();
233
                throw ODataException::createBadRequestError(Messages::badRequestInvalidUriForThisVerb($url, $requestMethod));
234
            }
235
236
            if (!$data) {
0 ignored issues
show
Bug introduced by
The variable $data does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
237
                throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
238
            }
239
240
            return $uriProcessor->providers->deleteResource($resourceSet, $keyDescriptor);
241
        });
242
    }
243
244
    /**
245
     * Execute the client submitted request against the data source
246
     *
247
     * @param callable $callback Function, what must be called
248
     */
249
    protected function executeBase($callback = null)
250
    {
251
        $segments = $this->request->getSegments();
252
253
        foreach ($segments as $segment) {
254
255
            $requestTargetKind = $segment->getTargetKind();
256
257
	        if ($segment->getTargetSource() == TargetSource::ENTITY_SET) {
258
                $this->handleSegmentTargetsToResourceSet($segment);
259
            } else if ($requestTargetKind == TargetKind::RESOURCE()) {
260
                if (is_null($segment->getPrevious()->getResult())) {
261
					throw ODataException::createResourceNotFoundError(
262
                        $segment->getPrevious()->getIdentifier()
263
                    );
264
                }
265
                $this->_handleSegmentTargetsToRelatedResource($segment);
266
            } else if ($requestTargetKind == TargetKind::LINK()) {
267
                $segment->setResult($segment->getPrevious()->getResult());
268
            } else if ($segment->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) {
269
                // we are done, $count will the last segment and
270
                // taken care by _applyQueryOptions method
271
                $segment->setResult($this->request->getCountValue());
272
                break;
273
            } else {
274
                if ($requestTargetKind == TargetKind::MEDIA_RESOURCE()) {
275
                    if (is_null($segment->getPrevious()->getResult())) {
276
						throw ODataException::createResourceNotFoundError(
277
                            $segment->getPrevious()->getIdentifier()
278
                        );
279
                    }
280
                    // For MLE and Named Stream the result of last segment
281
                    // should be that of previous segment, this is required
282
                    // while retrieving content type or stream from IDSSP
283
                    $segment->setResult($segment->getPrevious()->getResult());
284
                    // we are done, as named stream property or $value on
285
                    // media resource will be the last segment
286
                    break;
287
                }
288
289
	            $value = $segment->getPrevious()->getResult();
290
                while (!is_null($segment)) {
291
	                //TODO: what exactly is this doing here?  Once a null's found it seems everything will be null
292
                    if (!is_null($value)) {
293
                        $value = null;
294
                    } else {
295
                        try {
296
	                        //see #88
297
                            $property = new \ReflectionProperty($value, $segment->getIdentifier());
298
                            $value = $property->getValue($value);
299
                        } catch (\ReflectionException $reflectionException) {
300
                            //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...
301
                        }
302
                    }
303
304
                    $segment->setResult($value);
305
                    $segment = $segment->getNext();
306
                    if (!is_null($segment) && $segment->getIdentifier() == ODataConstants::URI_VALUE_SEGMENT) {
307
                        $segment->setResult($value);
308
                        $segment = $segment->getNext();
309
                    }
310
                }
311
312
                break;
313
314
            }
315
316
            if (is_null($segment->getNext()) || $segment->getNext()->getIdentifier() == ODataConstants::URI_COUNT_SEGMENT) {
317
                $this->applyQueryOptions($segment, $callback);
318
            }
319
        }
320
321
         // Apply $select and $expand options to result set, this function will be always applied
322
         // irrespective of return value of IDSQP2::canApplyQueryOptions which means library will
323
         // not delegate $expand/$select operation to IDSQP2 implementation
324
        $this->handleExpansion();
325
    }
326
327
    /**
328
     * Query for a resource set pointed by the given segment descriptor and update the descriptor with the result.
329
     *
330
     * @param SegmentDescriptor $segment Describes the resource set to query
331
     * @return void
332
     *
333
     */
334
    private function handleSegmentTargetsToResourceSet( SegmentDescriptor $segment ) {
335
        if ($segment->isSingleResult()) {
336
            $entityInstance = $this->providers->getResourceFromResourceSet(
337
                $segment->getTargetResourceSetWrapper(),
338
                $segment->getKeyDescriptor()
339
            );
340
341
            $segment->setResult($entityInstance);
342
343
        } else {
344
345
            $internalskiptokentinfo = $this->request->getInternalSkipTokenInfo();
346
347
            $queryResult = $this->providers->getResourceSet(
348
	            $this->request->queryType,
349
                $segment->getTargetResourceSetWrapper(),
350
                $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...
351
                $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...
352
                $this->request->getTopCount(),
353
                $this->request->getSkipCount(),
354
                $internalskiptokentinfo ? $internalskiptokentinfo->getSkipTokenInfo() : null,
355
                $this->_getExpandedProjectionNodes()
356
            );
357
            $segment->setResult($queryResult);
358
        }
359
    }
360
361
    /**
362
     * Query for a related resource set or resource set reference pointed by the
363
     * given segment descriptor and update the descriptor with the result.
364
     *
365
     * @param SegmentDescriptor &$segment Describes the related resource
366
     *                                              to query.
367
     *
368
     * @return void
369
     */
370
    private function _handleSegmentTargetsToRelatedResource(SegmentDescriptor $segment) {
371
        $projectedProperty = $segment->getProjectedProperty();
372
        $projectedPropertyKind = $projectedProperty->getKind();
373
374
        if ($projectedPropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE) {
375
            if ($segment->isSingleResult()) {
376
                $entityInstance = $this->providers->getResourceFromRelatedResourceSet(
377
                    $segment->getPrevious()->getTargetResourceSetWrapper(),
378
                    $segment->getPrevious()->getResult(),
379
                    $segment->getTargetResourceSetWrapper(),
380
                    $projectedProperty,
381
                    $segment->getKeyDescriptor()
382
                );
383
384
                $segment->setResult($entityInstance);
385
            } else {
386
                $queryResult = $this->providers->getRelatedResourceSet(
387
	                $this->request->queryType,
388
                    $segment->getPrevious()->getTargetResourceSetWrapper(),
389
                    $segment->getPrevious()->getResult(),
390
                    $segment->getTargetResourceSetWrapper(),
391
                    $segment->getProjectedProperty(),
392
                    $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...
393
	                //TODO: why are these null?  see #98
394
                    null, // $orderby
395
                    null, // $top
396
                    null  // $skip
397
                );
398
399
                $segment->setResult($queryResult);
400
            }
401
        } else if ($projectedPropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE) {
402
            $entityInstance = $this->providers->getRelatedResourceReference(
403
                $segment->getPrevious()->getTargetResourceSetWrapper(),
404
                $segment->getPrevious()->getResult(),
405
                $segment->getTargetResourceSetWrapper(),
406
                $segment->getProjectedProperty()
407
            );
408
409
            $segment->setResult($entityInstance);
410
        } 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...
411
            //Unexpected state
412
        }
413
    }
414
415
    /**
416
     * Applies the query options to the resource(s) retrieved from the data source.
417
     *
418
     * @param SegmentDescriptor $segment The descriptor which holds resource(s) on which query options to be applied.
419
     * @param callable $callback Function, what must be called
420
     *
421
     */
422
    private function applyQueryOptions(SegmentDescriptor $segment, $callback = null)
423
    {
424
        // For non-GET methods
425
        if ($callback) {
426
            $callback($this, $segment);
427
            return;
428
        }
429
430
	    //TODO: I'm not really happy with this..i think i'd rather keep the result the QueryResult
431
	    //not even bother with the setCountValue stuff (shouldn't counts be on segments?)
432
	    //and just work with the QueryResult in the object model serializer
433
	    $result = $segment->getResult();
434
435
	    if(!$result instanceof QueryResult){
436
		    //If the segment isn't a query result, then there's no paging or counting to be done
437
		    return;
438
        }
439
440
441
        // Note $inlinecount=allpages means include the total count regardless of paging..so we set the counts first
442
	    // regardless if POData does the paging or not.
443 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...
444
            if ($this->providers->handlesOrderedPaging()) {
445
                $this->request->setCountValue($result->count);
446
            } else {
447
                $this->request->setCountValue(count($result->results));
448
            }
449
        }
450
451
	    //Have POData perform paging if necessary
452
	    if(!$this->providers->handlesOrderedPaging() && !empty($result->results)){
453
			$result->results = $this->performPaging($result->results);
454
	    }
455
456
	    //a bit surprising, but $skip and $top affects $count so update it here, not above
457
	    //IE  data.svc/Collection/$count?$top=10 returns 10 even if Collection has 11+ entries
458 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...
459
		    if ($this->providers->handlesOrderedPaging()) {
460
			    $this->request->setCountValue($result->count);
461
		    } else {
462
			    $this->request->setCountValue(count($result->results));
463
		    }
464
	    }
465
466
        $segment->setResult($result->results);
467
    }
468
469
	/**
470
	 * If the provider does not perform the paging (ordering, top, skip) then this method does it
471
	 *
472
	 * @param array $result
473
	 * @return array
474
	 */
475
	private function performPaging(array $result)
476
	{
477
		//Apply (implicit and explicit) $orderby option
478
		$internalOrderByInfo = $this->request->getInternalOrderByInfo();
479
		if (!is_null($internalOrderByInfo)) {
480
			$orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
481
			usort($result, $orderByFunction);
482
		}
483
484
		//Apply $skiptoken option
485
		$internalSkipTokenInfo = $this->request->getInternalSkipTokenInfo();
486
		if (!is_null($internalSkipTokenInfo)) {
487
			$matchingIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($result);
488
			$result = array_slice($result, $matchingIndex);
489
		}
490
491
		//Apply $top and $skip option
492
		if (!empty($result)) {
493
			$top  = $this->request->getTopCount();
494
			$skip = $this->request->getSkipCount();
495
			if(is_null($skip)) $skip = 0;
496
497
			$result = array_slice($result, $skip, $top);
498
		}
499
500
		return $result;
501
	}
502
503
504
    /**
505
     * Perform expansion.
506
     *
507
     * @return void
508
     */
509
    private function handleExpansion()
510
    {
511
        $node = $this->request->getRootProjectionNode();
512
        if (!is_null($node) && $node->isExpansionSpecified()) {
513
            $result = $this->request->getTargetResult();
514
            if (!is_null($result) || is_array($result) && !empty($result)) {
515
                $needPop = $this->_pushSegmentForRoot();
516
                $this->_executeExpansion($result);
517
                $this->_popSegment($needPop);
518
            }
519
        }
520
    }
521
522
    /**
523
     * Execute queries for expansion.
524
     *
525
     * @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...
526
     *
527
     *
528
     * @return void
529
     */
530
    private function _executeExpansion($result)
531
    {
532
        $expandedProjectionNodes = $this->_getExpandedProjectionNodes();
533
        foreach ($expandedProjectionNodes as $expandedProjectionNode) {
534
            $isCollection = $expandedProjectionNode->getResourceProperty()->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE;
535
            $expandedPropertyName = $expandedProjectionNode->getResourceProperty()->getName();
536
            if (is_array($result)) {
537
                foreach ($result as $entry) {
538
                    // Check for null entry
539 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...
540
                        $currentResourceSet = $this->_getCurrentResourceSetWrapper()->getResourceSet();
541
                        $resourceSetOfProjectedProperty = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
542
                        $projectedProperty1 = $expandedProjectionNode->getResourceProperty();
543
                        $result1 = $this->providers->getRelatedResourceSet(
544
	                        QueryType::ENTITIES(), //it's always entities for an expansion
545
                            $currentResourceSet,
546
                            $entry,
547
                            $resourceSetOfProjectedProperty,
548
                            $projectedProperty1,
549
                            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...
550
                            null, // $orderby
551
                            null, // $top
552
                            null  // $skip
553
                        )->results;
554
                        if (!empty($result1)) {
555
                            $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo();
556
                            if (!is_null($internalOrderByInfo)) {
557
                                $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
558
                                usort($result1, $orderByFunction);
559
                                unset($internalOrderByInfo);
560
                                $takeCount = $expandedProjectionNode->getTakeCount();
561
                                if (!is_null($takeCount)) {
562
                                    $result1 = array_slice($result1, 0, $takeCount);
563
                                }
564
                            }
565
566
                            $entry->$expandedPropertyName = $result1;
567
                            $projectedProperty = $expandedProjectionNode->getResourceProperty();
568
                            $needPop = $this->_pushSegmentForNavigationProperty(
569
                                $projectedProperty
570
                            );
571
                            $this->_executeExpansion($result1);
572
                            $this->_popSegment($needPop);
573
                        } else {
574
                            $entry->$expandedPropertyName = array();
575
                        }
576
                    } else {
577
                        $currentResourceSet1 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
578
                        $resourceSetOfProjectedProperty1 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
579
                        $projectedProperty2 = $expandedProjectionNode->getResourceProperty();
580
                        $result1 = $this->providers->getRelatedResourceReference(
581
                            $currentResourceSet1,
582
                            $entry,
583
                            $resourceSetOfProjectedProperty1,
584
                            $projectedProperty2
585
                        );
586
                        $entry->$expandedPropertyName = $result1;
587
                        if (!is_null($result1)) {
588
                            $projectedProperty3 = $expandedProjectionNode->getResourceProperty();
589
                            $needPop = $this->_pushSegmentForNavigationProperty(
590
                                $projectedProperty3
591
                            );
592
                            $this->_executeExpansion($result1);
593
                            $this->_popSegment($needPop);
594
                        }
595
                    }
596
                }
597 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...
598
                if ($isCollection) {
599
                    $currentResourceSet2 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
600
                    $resourceSetOfProjectedProperty2 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
601
                    $projectedProperty4 = $expandedProjectionNode->getResourceProperty();
602
                    $result1 = $this->providers->getRelatedResourceSet(
603
	                    QueryType::ENTITIES(), //it's always entities for an expansion
604
                        $currentResourceSet2,
605
                        $result,
606
                        $resourceSetOfProjectedProperty2,
607
                        $projectedProperty4,
608
                        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...
609
                        null, // $orderby
610
                        null, // $top
611
                        null  // $skip
612
                    )->results;
613
                    if (!empty($result1)) {
614
                        $internalOrderByInfo = $expandedProjectionNode->getInternalOrderByInfo();
615
                        if (!is_null($internalOrderByInfo)) {
616
                            $orderByFunction = $internalOrderByInfo->getSorterFunction()->getReference();
617
                            usort($result1, $orderByFunction);
618
                            unset($internalOrderByInfo);
619
                            $takeCount = $expandedProjectionNode->getTakeCount();
620
                            if (!is_null($takeCount)) {
621
                                $result1 = array_slice($result1, 0, $takeCount);
622
                            }
623
                        }
624
625
                        $result->$expandedPropertyName = $result1;
626
                        $projectedProperty7 = $expandedProjectionNode->getResourceProperty();
627
                        $needPop = $this->_pushSegmentForNavigationProperty(
628
                            $projectedProperty7
629
                        );
630
                        $this->_executeExpansion($result1);
631
                        $this->_popSegment($needPop);
632
                    } else {
633
                        $result->$expandedPropertyName = array();
634
                    }
635
                } else {
636
                    $currentResourceSet3 = $this->_getCurrentResourceSetWrapper()->getResourceSet();
637
                    $resourceSetOfProjectedProperty3 = $expandedProjectionNode->getResourceSetWrapper()->getResourceSet();
638
                    $projectedProperty5 = $expandedProjectionNode->getResourceProperty();
639
                    $result1 = $this->providers->getRelatedResourceReference(
640
                        $currentResourceSet3,
641
                        $result,
642
                        $resourceSetOfProjectedProperty3,
643
                        $projectedProperty5
644
                    );
645
                    $result->$expandedPropertyName = $result1;
646
                    if (!is_null($result1)) {
647
                        $projectedProperty6 = $expandedProjectionNode->getResourceProperty();
648
                        $needPop = $this->_pushSegmentForNavigationProperty(
649
                            $projectedProperty6
650
                        );
651
                        $this->_executeExpansion($result1);
652
                        $this->_popSegment($needPop);
653
                    }
654
                }
655
            }
656
        }
657
    }
658
659
    /**
660
     * Resource set wrapper for the resource being retireved.
661
     *
662
     * @return ResourceSetWrapper
663
     */
664 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...
665
    {
666
        $count = count($this->_segmentResourceSetWrappers);
667
        if ($count == 0) {
668
            return $this->request->getTargetResourceSetWrapper();
669
        } else {
670
            return $this->_segmentResourceSetWrappers[$count - 1];
671
        }
672
    }
673
674
    /**
675
     * Pushes a segment for the root of the tree
676
     * Note: Calls to this method should be balanced with calls to popSegment.
677
     *
678
     * @return bool true if the segment was pushed, false otherwise.
679
     */
680
    private function _pushSegmentForRoot()
681
    {
682
        $segmentName = $this->request->getContainerName();
683
        $segmentResourceSetWrapper
684
            = $this->request->getTargetResourceSetWrapper();
685
        return $this->_pushSegment($segmentName, $segmentResourceSetWrapper);
0 ignored issues
show
Bug introduced by
It seems like $segmentResourceSetWrapper defined by $this->request->getTargetResourceSetWrapper() on line 684 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...
686
    }
687
688
    /**
689
     * Pushes a segment for the current navigation property being written out.
690
     * Note: Refer 'ObjectModelSerializerNotes.txt' for more details about
691
     * 'Segment Stack' and this method.
692
     * Note: Calls to this method should be balanced with calls to popSegment.
693
     *
694
     * @param ResourceProperty &$resourceProperty Current navigation property
695
     *                                            being written out
696
     *
697
     * @return bool true if a segment was pushed, false otherwise
698
     *
699
     * @throws InvalidOperationException If this function invoked with non-navigation
700
     *                                   property instance.
701
     */
702 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...
703
    {
704
        if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY) {
705
            $this->assert(
706
                !empty($this->_segmentNames),
707
                '!is_empty($this->_segmentNames'
708
            );
709
            $currentResourceSetWrapper = $this->_getCurrentResourceSetWrapper();
710
            $currentResourceType = $currentResourceSetWrapper->getResourceType();
711
            $currentResourceSetWrapper = $this->service
712
                ->getProvidersWrapper()
713
                ->getResourceSetWrapperForNavigationProperty(
714
                    $currentResourceSetWrapper,
0 ignored issues
show
Bug introduced by
It seems like $currentResourceSetWrapper defined by $this->service->getProvi...ype, $resourceProperty) on line 711 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...
715
                    $currentResourceType,
716
                    $resourceProperty
717
                );
718
719
            $this->assert(
720
                !is_null($currentResourceSetWrapper),
721
                '!null($currentResourceSetWrapper)'
722
            );
723
            return $this->_pushSegment(
724
                $resourceProperty->getName(),
725
                $currentResourceSetWrapper
0 ignored issues
show
Bug introduced by
It seems like $currentResourceSetWrapper defined by $this->service->getProvi...ype, $resourceProperty) on line 711 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...
726
            );
727
        } else {
728
            throw new InvalidOperationException(
729
                'pushSegmentForNavigationProperty should not be called with non-entity type'
730
            );
731
        }
732
    }
733
734
    /**
735
     * Gets collection of expanded projection nodes under the current node.
736
     *
737
     * @return ExpandedProjectionNode[] List of nodes describing expansions for the current segment
738
     *
739
     */
740
    private function _getExpandedProjectionNodes()
741
    {
742
        $expandedProjectionNode = $this->_getCurrentExpandedProjectionNode();
743
        $expandedProjectionNodes = array();
744
        if (!is_null($expandedProjectionNode)) {
745
            foreach ($expandedProjectionNode->getChildNodes() as $node) {
746
                if ($node instanceof ExpandedProjectionNode) {
747
                    $expandedProjectionNodes[] = $node;
748
                }
749
            }
750
        }
751
752
        return $expandedProjectionNodes;
753
    }
754
755
    /**
756
     * Find a 'ExpandedProjectionNode' instance in the projection tree
757
     * which describes the current segment.
758
     *
759
     * @return ExpandedProjectionNode|null
760
     */
761 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...
762
    {
763
        $expandedProjectionNode
764
            = $this->request->getRootProjectionNode();
765
        if (!is_null($expandedProjectionNode)) {
766
            $depth = count($this->_segmentNames);
767
            if ($depth != 0) {
768
                for ($i = 1; $i < $depth; $i++) {
769
                    $expandedProjectionNode
770
                        = $expandedProjectionNode->findNode($this->_segmentNames[$i]);
771
                        $this->assert(
772
                            !is_null($expandedProjectionNode),
773
                            '!is_null($expandedProjectionNode)'
774
                        );
775
                        $this->assert(
776
                            $expandedProjectionNode instanceof ExpandedProjectionNode,
777
                            '$expandedProjectionNode instanceof ExpandedProjectionNode'
778
                        );
779
                }
780
            }
781
        }
782
783
        return $expandedProjectionNode;
784
    }
785
786
    /**
787
     * Pushes information about the segment whose instance is going to be
788
     * retrieved from the IDSQP implementation
789
     * Note: Calls to this method should be balanced with calls to popSegment.
790
     *
791
     * @param string             $segmentName         Name of segment to push.
792
     * @param ResourceSetWrapper &$resourceSetWrapper The resource set wrapper
793
     *                                                to push.
794
     *
795
     * @return bool true if the segment was push, false otherwise
796
     */
797 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...
798
    {
799
        $rootProjectionNode = $this->request->getRootProjectionNode();
800
        if (!is_null($rootProjectionNode)
801
            && $rootProjectionNode->isExpansionSpecified()
802
        ) {
803
            array_push($this->_segmentNames, $segmentName);
804
            array_push($this->_segmentResourceSetWrappers, $resourceSetWrapper);
805
            return true;
806
        }
807
808
        return false;
809
    }
810
811
    /**
812
     * Pops segment information from the 'Segment Stack'
813
     * Note: Calls to this method should be balanced with previous calls
814
     * to _pushSegment.
815
     *
816
     * @param boolean $needPop Is a pop required. Only true if last push
817
     *                         was successful.
818
     *
819
     * @return void
820
     *
821
     * @throws InvalidOperationException If found un-balanced call
822
     *                                   with _pushSegment
823
     */
824 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...
825
    {
826
        if ($needPop) {
827
            if (!empty($this->_segmentNames)) {
828
                array_pop($this->_segmentNames);
829
                array_pop($this->_segmentResourceSetWrappers);
830
            } else {
831
                throw new InvalidOperationException(
832
                    'Found non-balanced call to _pushSegment and popSegment'
833
                );
834
            }
835
        }
836
    }
837
838
    /**
839
     * Assert that the given condition is true.
840
     *
841
     * @param boolean $condition         Constion to assert.
842
     * @param string  $conditionAsString Message to show incase assertion fails.
843
     *
844
     * @return void
845
     *
846
     * @throws InvalidOperationException
847
     */
848
    protected function assert($condition, $conditionAsString)
849
    {
850
        if (!$condition) {
851
            throw new InvalidOperationException(
852
                "Unexpected state, expecting $conditionAsString"
853
            );
854
        }
855
    }
856
}
857