Completed
Push — master ( 34b7ec...a579ea )
by Alex
13s
created

UriProcessorNew::executeGet()   C

Complexity

Conditions 17
Paths 48

Size

Total Lines 49
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 49
rs 5.1117
c 0
b 0
f 0
cc 17
eloc 37
nc 48
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace POData\UriProcessor;
4
5
use POData\Common\HttpStatus;
6
use POData\Common\Messages;
7
use POData\Common\ODataConstants;
8
use POData\Common\ODataException;
9
use POData\IService;
10
use POData\ObjectModel\CynicDeserialiser;
11
use POData\ObjectModel\ModelDeserialiser;
12
use POData\ObjectModel\ODataEntry;
13
use POData\OperationContext\HTTPRequestMethod;
14
use POData\Providers\Metadata\ResourcePropertyKind;
15
use POData\Providers\Metadata\ResourceSet;
16
use POData\Providers\Metadata\ResourceSetWrapper;
17
use POData\Providers\ProvidersWrapper;
18
use POData\Providers\Query\QueryResult;
19
use POData\Providers\Query\QueryType;
20
use POData\UriProcessor\Interfaces\IUriProcessor;
21
use POData\UriProcessor\QueryProcessor\QueryProcessor;
22
use POData\UriProcessor\ResourcePathProcessor\ResourcePathProcessor;
23
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
24
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor;
25
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
26
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
27
28
/**
29
 * Class UriProcessorNew.
30
 *
31
 * A type to process client's requested URI
32
 * The syntax of request URI is:
33
 *  Scheme Host Port ServiceRoot ResourcePath ? QueryOption
34
 * For more details refer:
35
 * http://www.odata.org/developers/protocols/uri-conventions#UriComponents
36
 */
37
class UriProcessorNew implements IUriProcessor
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
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
     * Holds reference to request expander.
62
     *
63
     * @var RequestExpander
64
     */
65
    private $expander;
66
67
    /**
68
     * @var ModelDeserialiser
69
     */
70
    private $cereal;
71
72
    /**
73
     * @var CynicDeserialiser
74
     */
75
    private $cynicDeserialiser;
76
77
    /**
78
     * Constructs a new instance of UriProcessor.
79
     *
80
     * @param IService $service Reference to the data service instance
81
     */
82
    private function __construct(IService $service)
83
    {
84
        $this->service = $service;
85
        $this->providers = $service->getProvidersWrapper();
86
        $this->request = ResourcePathProcessor::process($service);
87
        $this->expander = new RequestExpander(
88
            $this->getRequest(),
89
            $this->getService(),
90
            $this->getProviders()
91
        );
92
        $this->getRequest()->setUriProcessor($this);
93
        $this->cereal = new ModelDeserialiser();
94
        $this->cynicDeserialiser = new CynicDeserialiser(
95
            $service->getMetadataProvider(),
96
            $service->getProvidersWrapper()
97
        );
98
    }
99
100
    /**
101
     * Process the resource path and query options of client's request uri.
102
     *
103
     * @param IService $service Reference to the data service instance
104
     *
105
     * @throws ODataException
106
     *
107
     * @return IUriProcessor
108
     */
109
    public static function process(IService $service)
110
    {
111
        $absRequestUri = $service->getHost()->getAbsoluteRequestUri();
112
        $absServiceUri = $service->getHost()->getAbsoluteServiceUri();
113
114
        if (!$absServiceUri->isBaseOf($absRequestUri)) {
115
            throw ODataException::createInternalServerError(
116
                Messages::uriProcessorRequestUriDoesNotHaveTheRightBaseUri(
117
                    $absRequestUri->getUrlAsString(),
118
                    $absServiceUri->getUrlAsString()
119
                )
120
            );
121
        }
122
123
        $processor = new self($service);
124
125
        //Parse the query string options of the request Uri.
126
        QueryProcessor::process($processor->request, $service);
127
128
        return $processor;
129
    }
130
131
    /**
132
     * Gets reference to the request submitted by client.
133
     *
134
     * @return RequestDescription
135
     */
136
    public function getRequest()
137
    {
138
        return $this->request;
139
    }
140
141
    /**
142
     * Gets reference to the request submitted by client.
143
     *
144
     * @return ProvidersWrapper
145
     */
146
    public function getProviders()
147
    {
148
        return $this->providers;
149
    }
150
151
    /**
152
     * Gets the data service instance.
153
     *
154
     * @return IService
155
     */
156
    public function getService()
157
    {
158
        return $this->service;
159
    }
160
161
    /**
162
     * Gets the request expander instance.
163
     *
164
     * @return RequestExpander
165
     */
166
    public function getExpander()
167
    {
168
        return $this->expander;
169
    }
170
171
    /**
172
     * @return ModelDeserialiser
173
     */
174
    public function getModelDeserialiser()
175
    {
176
        return $this->cereal;
177
    }
178
179
    public function getCynicDeserialiser()
180
    {
181
        return $this->cynicDeserialiser;
182
    }
183
184
    /**
185
     * Execute the client submitted request against the data source.
186
     */
187
    public function execute()
188
    {
189
        $service = $this->getService();
190
        assert($service instanceof IService, '!($service instanceof IService)');
191
        $context = $service->getOperationContext();
192
        $method = $context->incomingRequest()->getMethod();
193
194
        switch ($method) {
195
            case HTTPRequestMethod::GET():
196
                $this->executeGet();
197
                break;
198
            case HTTPRequestMethod::DELETE():
199
                $this->executeGet();
200
                $this->executeDelete();
201
                break;
202
            case HTTPRequestMethod::PUT():
203
                $this->executeGet();
204
                $this->executePut();
205
                break;
206
            case HTTPRequestMethod::POST():
207
                $this->executePost();
208
                break;
209
            default:
210
                throw ODataException::createNotImplementedError(Messages::onlyReadSupport($method));
211
        }
212
213
        // Apply $select and $expand options to result set, this function will be always applied
214
        // irrespective of return value of IDSQP2::canApplyQueryOptions which means library will
215
        // not delegate $expand/$select operation to IDSQP2 implementation
216
        $this->getExpander()->handleExpansion();
217
    }
218
219
    /**
220
     * Execute the client submitted request against the data source (GET).
221
     */
222
    protected function executeGet()
223
    {
224
        $segments = $this->getRequest()->getSegments();
225
        $root = $this->getRequest()->getRootProjectionNode();
226
        $eagerLoad = (null === $root) ? [] : $root->getEagerLoadList();
227
228
        foreach ($segments as $segment) {
229
            $requestTargetKind = $segment->getTargetKind();
230
231
            switch ($requestTargetKind) {
232
                case TargetKind::SINGLETON():
233
                    $this->executeGetSingleton($segment);
234
                    break;
235
                case TargetKind::RESOURCE():
236
                    $this->executeGetResource($segment, $eagerLoad);
237
                    break;
238
                case TargetKind::MEDIA_RESOURCE():
239
                    $this->checkResourceExistsByIdentifier($segment);
240
                    $segment->setResult($segment->getPrevious()->getResult());
241
                    // a media resource means we're done - bail out of segment processing
242
                    break 2;
243
                case TargetKind::LINK():
244
                    $this->executeGetLink($segment);
245
                    break;
246
                case TargetKind::PRIMITIVE_VALUE():
247
                    $previous = $segment->getPrevious();
248
                    if (null !== $previous && TargetKind::RESOURCE() == $previous->getTargetKind()) {
249
                        $result = $previous->getResult();
250
                        if ($result instanceof QueryResult) {
251
                            $raw = null !== $result->count ? $result->count : count($result->results);
252
                            $segment->setResult($raw);
253
                        }
254
                    }
255
                    break;
256
                case TargetKind::PRIMITIVE():
257
                case TargetKind::COMPLEX_OBJECT():
258
                case TargetKind::BAG():
259
                    break;
260
                default:
261
                    assert(false, 'Not implemented yet');
262
            }
263
264
            if (null === $segment->getNext()
265
                || ODataConstants::URI_COUNT_SEGMENT == $segment->getNext()->getIdentifier()
266
            ) {
267
                $this->applyQueryOptions($segment);
268
            }
269
        }
270
    }
271
272
    /**
273
     * Execute the client submitted request against the data source (DELETE).
274
     */
275
    protected function executeDelete()
276
    {
277
        $segment = $this->getFinalEffectiveSegment();
278
        $requestMethod = $this->getService()->getOperationContext()->incomingRequest()->getMethod();
279
        $resourceSet = $segment->getTargetResourceSetWrapper();
280
        $keyDescriptor = $segment->getKeyDescriptor();
281
282
        $this->checkUriValidForSuppliedVerb($resourceSet, $keyDescriptor, $requestMethod);
283
        assert($resourceSet instanceof ResourceSet);
284
        $this->getProviders()->deleteResource($resourceSet, $segment->getResult());
285
        $this->getService()->getHost()->setResponseStatusCode(HttpStatus::CODE_NOCONTENT);
286
    }
287
288
    /**
289
     * Execute the client submitted request against the data source (PUT).
290
     */
291
    protected function executePut()
292
    {
293
        $segment = $this->getFinalEffectiveSegment();
294
        $requestMethod = $this->getService()->getOperationContext()->incomingRequest()->getMethod();
295
        $resourceSet = null !== $segment ? $segment->getTargetResourceSetWrapper() : null;
296
        $keyDescriptor = null !== $segment ? $segment->getKeyDescriptor() : null;
297
298
        $this->checkUriValidForSuppliedVerb($resourceSet, $keyDescriptor, $requestMethod);
299
        assert($resourceSet instanceof ResourceSet);
300
        assert($keyDescriptor instanceof KeyDescriptor);
301
302
        $payload = $this->getRequest()->getData();
303
        assert($payload instanceof ODataEntry, get_class($payload));
304
        assert(!empty($payload->id), 'Payload ID must not be empty for PUT request');
305
        $data = $this->getModelDeserialiser()->bulkDeserialise($resourceSet->getResourceType(), $payload);
306
307
        if (empty($data)) {
308
            throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
309
        }
310
311
        $queryResult = $this->getCynicDeserialiser()->processPayload($payload);
312
        $segment->setResult($queryResult);
313
    }
314
315
    /**
316
     * Execute the client submitted request against the data source (POST).
317
     */
318
    protected function executePost()
319
    {
320
        $segments = $this->getRequest()->getSegments();
321
        $requestMethod = $this->getService()->getOperationContext()->incomingRequest()->getMethod();
322
323
        foreach ($segments as $segment) {
324
            $requestTargetKind = $segment->getTargetKind();
325
            if ($requestTargetKind == TargetKind::RESOURCE()) {
326
                $resourceSet = $segment->getTargetResourceSetWrapper();
327 View Code Duplication
                if (!$resourceSet) {
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...
328
                    $url = $this->getService()->getHost()->getAbsoluteRequestUri()->getUrlAsString();
329
                    $msg = Messages::badRequestInvalidUriForThisVerb($url, $requestMethod);
330
                    throw ODataException::createBadRequestError($msg);
331
                }
332
333
                $payload = $this->getRequest()->getData();
334
                assert($payload instanceof ODataEntry, get_class($payload));
335
                assert(empty($payload->id), 'Payload ID must be empty for POST request');
336
                $data = $this->getModelDeserialiser()->bulkDeserialise($resourceSet->getResourceType(), $payload);
337
338
                if (empty($data)) {
339
                    throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
340
                }
341
                $this->getService()->getHost()->setResponseStatusCode(HttpStatus::CODE_CREATED);
342
                $queryResult = $this->getCynicDeserialiser()->processPayload($payload);
343
                $keyID = $payload->id;
344
                assert($keyID instanceof KeyDescriptor, get_class($keyID));
345
                $locationUrl = $keyID->generateRelativeUri($resourceSet->getResourceSet());
346
                $absoluteServiceUri = $this->getService()->getHost()->getAbsoluteServiceUri()->getUrlAsString();
347
                $location = rtrim($absoluteServiceUri, '/') . '/' . $locationUrl;
348
                $this->getService()->getHost()->setResponseLocation($location);
349
                $segment->setResult($queryResult);
350
            }
351
        }
352
    }
353
354
    /**
355
     * @return null|SegmentDescriptor
356
     */
357
    protected function getFinalEffectiveSegment()
358
    {
359
        $segment = $this->getRequest()->getLastSegment();
360
        // if last segment is $count, back up one
361
        if (null !== $segment && ODataConstants::URI_COUNT_SEGMENT == $segment->getIdentifier()) {
362
            $segment = $segment->getPrevious();
363
            return $segment;
364
        }
365
        return $segment;
366
    }
367
368
    /**
369
     * @param $resourceSet
370
     * @param $keyDescriptor
371
     * @param $requestMethod
372
     * @throws ODataException
373
     */
374
    protected function checkUriValidForSuppliedVerb($resourceSet, $keyDescriptor, $requestMethod)
375
    {
376 View Code Duplication
        if (!$resourceSet || !$keyDescriptor) {
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...
377
            $url = $this->getService()->getHost()->getAbsoluteRequestUri()->getUrlAsString();
378
            throw ODataException::createBadRequestError(
379
                Messages::badRequestInvalidUriForThisVerb($url, $requestMethod)
380
            );
381
        }
382
    }
383
384
    /**
385
     * @param $segment
386
     */
387
    private function executeGetSingleton($segment)
388
    {
389
        $segmentId = $segment->getIdentifier();
390
        $singleton = $this->getService()->getProvidersWrapper()->resolveSingleton($segmentId);
391
        $segment->setResult($singleton->get());
392
    }
393
394
    /**
395
     * @param $segment
396
     */
397
    private function executeGetResource($segment, array $eagerList = [])
398
    {
399
        foreach ($eagerList as $eager) {
400
            assert(is_string($eager) && 0 < strlen($eager), 'Eager-load list elements must be non-empty strings');
401
        }
402
        $isRelated = $segment->getTargetSource() != TargetSource::ENTITY_SET;
403
        if (!$isRelated) {
404
            $queryResult = $this->executeGetResourceDirect($segment, $eagerList);
405
        } else {
406
            $queryResult = $this->executeGetResourceRelated($segment);
407
        }
408
        $segment->setResult($queryResult);
409
    }
410
411
    /**
412
     * @param $segment
413
     */
414
    private function executeGetLink($segment)
415
    {
416
        $previous = $segment->getPrevious();
417
        assert(isset($previous));
418
        $segment->setResult($previous->getResult());
419
    }
420
421
    /**
422
     * @param $segment
423
     * @return null|object|QueryResult
424
     */
425
    private function executeGetResourceDirect($segment, array $eagerList)
426
    {
427
        if ($segment->isSingleResult()) {
428
            $queryResult = $this->getProviders()->getResourceFromResourceSet(
429
                $segment->getTargetResourceSetWrapper(),
430
                $segment->getKeyDescriptor(),
431
                $eagerList
432
            );
433
        } else {
434
            $skip = $this->getRequest()->getSkipCount();
435
            $skip = (null === $skip) ? 0 : $skip;
436
            $skipToken = $this->getRequest()->getInternalSkipTokenInfo();
437
            $skipToken = (null != $skipToken) ? $skipToken->getSkipTokenInfo() : null;
438
            $queryResult = $this->getProviders()->getResourceSet(
439
                $this->getRequest()->queryType,
440
                $segment->getTargetResourceSetWrapper(),
441
                $this->getRequest()->getFilterInfo(),
442
                $this->getRequest()->getInternalOrderByInfo(),
443
                $this->getRequest()->getTopCount(),
444
                $skip,
445
                $skipToken,
446
                $eagerList
447
            );
448
        }
449
        return $queryResult;
450
    }
451
452
    /**
453
     * @param $segment
454
     * @return null|object|QueryResult
455
     */
456
    private function executeGetResourceRelated($segment)
457
    {
458
        $projectedProperty = $segment->getProjectedProperty();
459
        $projectedPropertyKind = null !== $projectedProperty ? $projectedProperty->getKind() : 0;
460
        $queryResult = null;
461
        switch ($projectedPropertyKind) {
462
            case ResourcePropertyKind::RESOURCE_REFERENCE:
463
                $queryResult = $this->getProviders()->getRelatedResourceReference(
464
                    $segment->getPrevious()->getTargetResourceSetWrapper(),
465
                    $segment->getPrevious()->getResult(),
466
                    $segment->getTargetResourceSetWrapper(),
467
                    $projectedProperty
468
                );
469
                break;
470
            case ResourcePropertyKind::RESOURCESET_REFERENCE:
471
                if ($segment->isSingleResult()) {
472
                    $queryResult = $this->getProviders()->getResourceFromRelatedResourceSet(
473
                        $segment->getPrevious()->getTargetResourceSetWrapper(),
474
                        $segment->getPrevious()->getResult(),
475
                        $segment->getTargetResourceSetWrapper(),
476
                        $projectedProperty,
477
                        $segment->getKeyDescriptor()
478
                    );
479
                } else {
480
                    $skipToken = $this->getRequest()->getInternalSkipTokenInfo();
481
                    $skipToken = (null !== $skipToken) ? $skipToken->getSkipTokenInfo() : null;
482
                    $queryResult = $this->getProviders()->getRelatedResourceSet(
483
                        $this->getRequest()->queryType,
484
                        $segment->getPrevious()->getTargetResourceSetWrapper(),
485
                        $segment->getPrevious()->getResult(),
486
                        $segment->getTargetResourceSetWrapper(),
487
                        $projectedProperty,
488
                        $this->getRequest()->getFilterInfo(),
489
                        null, // $orderby
490
                        null, // $top
491
                        null, // $skip
492
                        $skipToken
493
                    );
494
                }
495
                break;
496
            default:
497
                $this->checkResourceExistsByIdentifier($segment);
498
                assert(false, 'Invalid property kind type for resource retrieval');
499
        }
500
        return $queryResult;
501
    }
502
503
    /**
504
     * @param $segment
505
     * @throws ODataException
506
     */
507
    private function checkResourceExistsByIdentifier($segment)
508
    {
509
        if (null === $segment->getPrevious()->getResult()) {
510
            throw ODataException::createResourceNotFoundError(
511
                $segment->getPrevious()->getIdentifier()
512
            );
513
        }
514
    }
515
516
    /**
517
     * Applies the query options to the resource(s) retrieved from the data source.
518
     *
519
     * @param SegmentDescriptor $segment The descriptor which holds resource(s) on which query options to be applied
520
     */
521
    private function applyQueryOptions(SegmentDescriptor $segment)
522
    {
523
        $result = $segment->getResult();
524
        if (!$result instanceof QueryResult) {
525
            //If the segment isn't a query result, then there's no paging or counting to be done
526
            return;
527
        }
528
        // Note $inlinecount=allpages means include the total count regardless of paging..so we set the counts first
529
        // regardless if POData does the paging or not.
530 View Code Duplication
        if ($this->getRequest()->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...
531
            if ($this->getProviders()->handlesOrderedPaging()) {
532
                $this->getRequest()->setCountValue($result->count);
533
            } else {
534
                $this->getRequest()->setCountValue(count($result->results));
535
            }
536
        }
537
        //Have POData perform paging if necessary
538
        if (!$this->getProviders()->handlesOrderedPaging() && !empty($result->results)) {
539
            $result->results = $this->performPaging($result->results);
540
        }
541
        //a bit surprising, but $skip and $top affects $count so update it here, not above
542
        //IE  data.svc/Collection/$count?$top=10 returns 10 even if Collection has 11+ entries
543 View Code Duplication
        if ($this->getRequest()->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...
544
            if ($this->getProviders()->handlesOrderedPaging()) {
545
                $this->getRequest()->setCountValue($result->count);
546
            } else {
547
                $this->getRequest()->setCountValue(count($result->results));
548
            }
549
        }
550
        $segment->setResult($result);
551
    }
552
553
    /**
554
     * If the provider does not perform the paging (ordering, top, skip) then this method does it.
555
     *
556
     * @param array $result
557
     *
558
     * @return array
559
     */
560
    private function performPaging(array $result)
561
    {
562
        //Apply (implicit and explicit) $orderby option
563
        $internalOrderByInfo = $this->getRequest()->getInternalOrderByInfo();
564
        if (null !== $internalOrderByInfo) {
565
            $orderByFunction = $internalOrderByInfo->getSorterFunction();
566
            usort($result, $orderByFunction);
567
        }
568
        //Apply $skiptoken option
569
        $internalSkipTokenInfo = $this->getRequest()->getInternalSkipTokenInfo();
570
        if (null !== $internalSkipTokenInfo) {
571
            $matchingIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($result);
572
            $result = array_slice($result, $matchingIndex);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $result. This often makes code more readable.
Loading history...
573
        }
574
        //Apply $top and $skip option
575
        if (!empty($result)) {
576
            $top = $this->getRequest()->getTopCount();
577
            $skip = $this->getRequest()->getSkipCount();
578
            if (null === $skip) {
579
                $skip = 0;
580
            }
581
            $result = array_slice($result, $skip, $top);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $result. This often makes code more readable.
Loading history...
582
        }
583
        return $result;
584
    }
585
}
586