Completed
Push — master ( f0e0ca...79ebae )
by Christopher
04:17
created

UriProcessor::execute()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
c 0
b 0
f 0
rs 6.7272
cc 7
eloc 17
nc 12
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\Messages;
19
use POData\Common\ODataException;
20
use POData\Common\InvalidOperationException;
21
use POData\Common\ODataConstants;
22
use POData\Providers\Query\QueryResult;
23
use POData\OperationContext\HTTPRequestMethod;
24
25
/**
26
 * Class UriProcessor.
27
 *
28
 * A type to process client's requets URI
29
 * The syntax of request URI is:
30
 *  Scheme Host Port ServiceRoot ResourcePath ? QueryOption
31
 * For more details refer:
32
 * http://www.odata.org/developers/protocols/uri-conventions#UriComponents
33
 */
34
class UriProcessor
35
{
36
    /**
37
     * Description of the OData request that a client has submitted.
38
     *
39
     * @var RequestDescription
40
     */
41
    private $request;
42
43
    /**
44
     * Holds reference to the data service instance.
45
     *
46
     * @var IService
47
     */
48
    private $service;
49
50
    /**
51
     * Holds reference to the wrapper over IDSMP and IDSQP implementation.
52
     *
53
     * @var ProvidersWrapper
54
     */
55
    private $providers;
56
57
    /**
58
     * Collection of segment names.
59
     *
60
     * @var string[]
61
     */
62
    private $_segmentNames;
63
64
    /**
65
     * Collection of segment ResourceSetWrapper instances.
66
     *
67
     * @var ResourceSetWrapper[]
68
     */
69
    private $_segmentResourceSetWrappers;
70
71
    /**
72
     * Constructs a new instance of UriProcessor.
73
     *
74
     * @param IService $service Reference to the data service instance
75
     */
76
    private function __construct(IService $service)
77
    {
78
        $this->service = $service;
79
        $this->providers = $service->getProvidersWrapper();
80
        $this->_segmentNames = array();
81
        $this->_segmentResourceSetWrappers = array();
82
    }
83
84
    /**
85
     * Process the resource path and query options of client's request uri.
86
     *
87
     * @param IService $service Reference to the data service instance
88
     *
89
     * @return URIProcessor
90
     *
91
     * @throws ODataException
92
     */
93
    public static function process(IService $service)
94
    {
95
        $absoluteRequestUri = $service->getHost()->getAbsoluteRequestUri();
96
        $absoluteServiceUri = $service->getHost()->getAbsoluteServiceUri();
97
98
        if (!$absoluteServiceUri->isBaseOf($absoluteRequestUri)) {
99
            throw ODataException::createInternalServerError(
100
                Messages::uriProcessorRequestUriDoesNotHaveTheRightBaseUri(
101
                    $absoluteRequestUri->getUrlAsString(),
102
                    $absoluteServiceUri->getUrlAsString()
103
                )
104
            );
105
        }
106
107
        $uriProcessor = new self($service);
108
        //Parse the resource path part of the request Uri.
109
        $uriProcessor->request = ResourcePathProcessor::process($service);
110
111
        $uriProcessor->request->setUriProcessor($uriProcessor);
112
113
        //Parse the query string options of the request Uri.
114
        QueryProcessor::process($uriProcessor->request, $service);
115
116
        return $uriProcessor;
117
    }
118
119
    /**
120
     * Gets reference to the request submitted by client.
121
     *
122
     * @return RequestDescription
123
     */
124
    public function getRequest()
125
    {
126
        return $this->request;
127
    }
128
129
    /**
130
     * Gets the data service instance
131
     *
132
     * @return IService
133
     */
134
    public function getService()
135
    {
136
        return $this->service;
137
    }
138
139
    /**
140
     * Execute the client submitted request against the data source.
141
     */
142
    public function execute()
143
    {
144
        $service = $this->getService();
145
        $operationContext = !isset($service) ? null : $service->getOperationContext();
146
        if (!$operationContext) {
147
            $this->executeBase();
148
149
            return;
150
        }
151
152
        $requestMethod = $operationContext->incomingRequest()->getMethod();
153
        if ($requestMethod == HTTPRequestMethod::GET()) {
154
            $this->executeGet();
155
        } elseif ($requestMethod == HTTPRequestMethod::POST()) {
156
            $this->executePost();
157
        } elseif ($requestMethod == HTTPRequestMethod::PUT()) {
158
            $this->executePut();
159
        } elseif ($requestMethod == HTTPRequestMethod::DELETE()) {
160
            $this->executeDelete();
161
            //TODO: we probably need these verbes eventually.
162
        /*} elseif ($requestMethod == HTTPRequestMethod::PATCH()) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
163
            $this->executePatch();
164
        } elseif ($requestMethod == HTTPRequestMethod::MERGE()) {
165
            $this->executeMerge();*/
166
        } else {
167
            throw ODataException::createNotImplementedError(Messages::onlyReadSupport($requestMethod));
168
        }
169
    }
170
171
 
172
173
    /**
174
     * Execute the client submitted request against the data source (GET).
175
     */
176
    protected function executeGet()
177
    {
178
        return $this->executeBase();
179
    }
180
           /**
181
     * Execute the client submitted request against the data source (POST).
182
     */
183
    protected function executePost()
184
    {
185
        $segments = $this->request->getSegments();
186
187
        foreach ($segments as $segment) {
188
            if ($requestTargetKind == TargetKind::RESOURCE()) {
0 ignored issues
show
Bug introduced by
The variable $requestTargetKind 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...
189
                $requestMethod = $this->getService()->getOperationContext()->incomingRequest()->getMethod();
190
                $resourceSet = $segment->getTargetResourceSetWrapper();
191
                $keyDescriptor = $segment->getKeyDescriptor();
192
                $data = $this->request->getData();
193
                if (!$resourceSet || !$keyDescriptor) {
194
                    $url = $this->getService()->getHost()->getAbsoluteRequestUri()->getUrlAsString();
195
                    throw ODataException::createBadRequestError(Messages::badRequestInvalidUriForThisVerb($url, $requestMethod));
196
                }
197
                if (!$data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

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