UriProcessorNew::executeGetSingleton()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace POData\UriProcessor;
6
7
use Exception;
8
use POData\Common\HttpStatus;
9
use POData\Common\InvalidOperationException;
10
use POData\Common\Messages;
11
use POData\Common\NotImplementedException;
12
use POData\Common\ODataConstants;
13
use POData\Common\ODataException;
14
use POData\Common\Url;
15
use POData\Common\UrlFormatException;
16
use POData\IService;
17
use POData\ObjectModel\CynicDeserialiser;
18
use POData\ObjectModel\ModelDeserialiser;
19
use POData\ObjectModel\ODataEntry;
20
use POData\ObjectModel\ODataURL;
21
use POData\OperationContext\HTTPRequestMethod;
22
use POData\Providers\Metadata\ResourcePropertyKind;
23
use POData\Providers\Metadata\ResourceSet;
24
use POData\Providers\ProvidersWrapper;
25
use POData\Providers\Query\QueryResult;
26
use POData\Providers\Query\QueryType;
27
use POData\UriProcessor\Interfaces\IUriProcessor;
28
use POData\UriProcessor\QueryProcessor\QueryProcessor;
29
use POData\UriProcessor\ResourcePathProcessor\ResourcePathProcessor;
30
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
31
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor;
32
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentParser;
33
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
34
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
35
use ReflectionException;
36
37
/**
38
 * Class UriProcessorNew.
39
 *
40
 * A type to process client's requested URI
41
 * The syntax of request URI is:
42
 *  Scheme Host Port ServiceRoot ResourcePath ? QueryOption
43
 * For more details refer:
44
 * http://www.odata.org/developers/protocols/uri-conventions#UriComponents
45
 */
46
class UriProcessorNew implements IUriProcessor
47
{
48
    /**
49
     * Description of the OData request that a client has submitted.
50
     *
51
     * @var RequestDescription
52
     */
53
    private $request;
54
55
    /**
56
     * Holds reference to the data service instance.
57
     *
58
     * @var IService
59
     */
60
    private $service;
61
62
    /**
63
     * Holds reference to the wrapper over IDSMP and IDSQP implementation.
64
     *
65
     * @var ProvidersWrapper
66
     */
67
    private $providers;
68
69
    /**
70
     * Holds reference to request expander.
71
     *
72
     * @var RequestExpander
73
     */
74
    private $expander;
75
76
    /**
77
     * @var ModelDeserialiser
78
     */
79
    private $cereal;
80
81
    /**
82
     * @var CynicDeserialiser
83
     */
84
    private $cynicDeserialiser;
85
86
    /**
87
     * Constructs a new instance of UriProcessor.
88
     *
89
     * @param  IService                  $service Reference to the data service instance
90
     * @throws InvalidOperationException
91
     * @throws ODataException
92
     * @throws UrlFormatException
93
     * @throws ReflectionException
94
     */
95
    private function __construct(IService $service)
96
    {
97
        $this->service   = $service;
98
        $this->providers = $service->getProvidersWrapper();
99
        $this->request   = ResourcePathProcessor::process($service);
100
        $this->expander  = new RequestExpander(
101
            $this->getRequest(),
102
            $this->getService(),
103
            $this->getProviders()
104
        );
105
        $this->getRequest()->setUriProcessor($this);
106
        $this->cereal            = new ModelDeserialiser();
107
        $this->cynicDeserialiser = new CynicDeserialiser(
108
            $service->getMetadataProvider(),
109
            $service->getProvidersWrapper()
110
        );
111
    }
112
113
    /**
114
     * Gets reference to the request submitted by client.
115
     *
116
     * @return RequestDescription
117
     */
118
    public function getRequest()
119
    {
120
        return $this->request;
121
    }
122
123
    /**
124
     * Gets the data service instance.
125
     *
126
     * @return IService
127
     */
128
    public function getService()
129
    {
130
        return $this->service;
131
    }
132
133
    /**
134
     * Gets reference to the request submitted by client.
135
     *
136
     * @return ProvidersWrapper
137
     */
138
    public function getProviders()
139
    {
140
        return $this->providers;
141
    }
142
143
    /**
144
     * Process the resource path and query options of client's request uri.
145
     *
146
     * @param IService $service Reference to the data service instance
147
     *
148
     * @throws ODataException
149
     * @throws NotImplementedException
150
     * @throws ReflectionException
151
     * @throws UrlFormatException
152
     * @throws InvalidOperationException
153
     * @return IUriProcessor
154
     */
155
    public static function process(IService $service)
156
    {
157
        $absRequestUri = $service->getHost()->getAbsoluteRequestUri();
158
        $absServiceUri = $service->getHost()->getAbsoluteServiceUri();
159
160
        if (!$absServiceUri->isBaseOf($absRequestUri)) {
161
            throw ODataException::createInternalServerError(
162
                Messages::uriProcessorRequestUriDoesNotHaveTheRightBaseUri(
163
                    $absRequestUri->getUrlAsString(),
164
                    $absServiceUri->getUrlAsString()
165
                )
166
            );
167
        }
168
169
        $processor = new self($service);
170
        //Parse the query string options of the request Uri.
171
        QueryProcessor::process($processor->request, $service);
172
        return $processor;
173
    }
174
175
    /**
176
     * Execute the client submitted request against the data source.
177
     * @throws InvalidOperationException
178
     * @throws ODataException
179
     * @throws UrlFormatException
180
     * @throws ReflectionException
181
     */
182
    public function execute()
183
    {
184
        $service = $this->getService();
185
        if (!$service instanceof IService) {
0 ignored issues
show
introduced by
$service is always a sub-type of POData\IService.
Loading history...
186
            throw new InvalidOperationException('!($service instanceof IService)');
187
        }
188
        $context = $service->getOperationContext();
189
        $method  = $context->incomingRequest()->getMethod();
190
191
        switch ($method) {
192
            case HTTPRequestMethod::GET():
193
                $this->executeGet();
194
                break;
195
            case HTTPRequestMethod::DELETE():
196
                $this->executeGet();
197
                $this->executeDelete();
198
                break;
199
            case HTTPRequestMethod::PUT():
200
                $this->executeGet();
201
                $this->executePut();
202
                break;
203
            case HTTPRequestMethod::POST():
204
                $this->executePost();
205
                break;
206
            default:
207
                throw ODataException::createNotImplementedError(Messages::onlyReadSupport($method));
208
        }
209
210
        // Apply $select and $expand options to result set, this function will be always applied
211
        // irrespective of return value of IDSQP2::canApplyQueryOptions which means library will
212
        // not delegate $expand/$select operation to IDSQP2 implementation
213
        $this->getExpander()->handleExpansion();
214
    }
215
216
    /**
217
     * Execute the client submitted request against the data source (GET).
218
     * @throws ODataException
219
     * @throws InvalidOperationException
220
     * @throws ReflectionException
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
                    if (TargetSource::ENTITY_SET() == $segment->getTargetSource()) {
237
                        $this->handleSegmentTargetsToResourceSet($segment, $eagerLoad);
238
                    } else {
239
                        $this->executeGetResource($segment, $eagerLoad);
240
                    }
241
                    break;
242
                case TargetKind::MEDIA_RESOURCE():
243
                    $this->checkResourceExistsByIdentifier($segment);
244
                    $segment->setResult($segment->getPrevious()->getResult());
245
                    // a media resource means we're done - bail out of segment processing
246
                    break 2;
247
                case TargetKind::LINK():
248
                    $this->executeGetLink($segment);
249
                    break;
250
                case TargetKind::PRIMITIVE_VALUE():
251
                    $previous = $segment->getPrevious();
252
                    if (null !== $previous && TargetKind::RESOURCE() == $previous->getTargetKind()) {
253
                        $result = $previous->getResult();
254
                        if ($result instanceof QueryResult) {
255
                            $raw = null !== $result->count ? $result->count : count($result->results);
0 ignored issues
show
Bug introduced by
It seems like $result->results can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

255
                            $raw = null !== $result->count ? $result->count : count(/** @scrutinizer ignore-type */ $result->results);
Loading history...
256
                            $segment->setResult($raw);
257
                        }
258
                    }
259
                    break;
260
                case TargetKind::PRIMITIVE():
261
                case TargetKind::COMPLEX_OBJECT():
262
                case TargetKind::BAG():
263
                    break;
264
                default:
265
                    throw new InvalidOperationException('Not implemented yet');
266
            }
267
268
            if (null === $segment->getNext()
269
                || ODataConstants::URI_COUNT_SEGMENT == $segment->getNext()->getIdentifier()
270
            ) {
271
                $this->applyQueryOptions($segment);
272
            }
273
        }
274
    }
275
276
    /**
277
     * @param SegmentDescriptor $segment
278
     */
279
    private function executeGetSingleton(SegmentDescriptor $segment)
280
    {
281
        $segmentId = $segment->getIdentifier();
282
        $singleton = $this->getService()->getProvidersWrapper()->resolveSingleton($segmentId);
283
        $segment->setResult($singleton->get());
284
    }
285
286
    /**
287
     * Query for a resource set pointed by the given segment descriptor and update the descriptor with the result.
288
     *
289
     * @param  SegmentDescriptor         $segment   Describes the resource set to query
290
     * @param  array|null                $eagerLoad
291
     * @throws InvalidOperationException
292
     * @throws ODataException
293
     * @throws ReflectionException
294
     */
295
    private function handleSegmentTargetsToResourceSet(SegmentDescriptor $segment, $eagerLoad)
296
    {
297
        if ($segment->isSingleResult()) {
298
            $entityInstance = $this->getProviders()->getResourceFromResourceSet(
299
                $segment->getTargetResourceSetWrapper(),
300
                $segment->getKeyDescriptor()
301
            );
302
303
            $segment->setResult($entityInstance);
304
        } else {
305
            $eagerLoad   = (null !== $eagerLoad) ? $eagerLoad : [];
306
            $skip        = (null == $this->getRequest()) ? 0 : $this->getRequest()->getSkipCount();
307
            $skip        = (null === $skip) ? 0 : $skip;
308
            $skipToken   = $this->getRequest()->getInternalSkipTokenInfo();
309
            $skipToken   = (null != $skipToken) ? $skipToken->getSkipTokenInfo() : null;
310
            $queryResult = $this->getProviders()->getResourceSet(
311
                $this->getRequest()->queryType,
312
                $segment->getTargetResourceSetWrapper(),
313
                $this->getRequest()->getFilterInfo(),
314
                $this->getRequest()->getInternalOrderByInfo(),
315
                $this->getRequest()->getTopCount(),
316
                $skip,
317
                $skipToken,
318
                $eagerLoad
319
            );
320
            $segment->setResult($queryResult);
321
        }
322
    }
323
324
    /**
325
     * @param  SegmentDescriptor         $segment
326
     * @param  array                     $eagerList
327
     * @throws InvalidOperationException
328
     * @throws ODataException
329
     * @throws ReflectionException
330
     */
331
    private function executeGetResource(SegmentDescriptor $segment, array $eagerList = [])
332
    {
333
        foreach ($eagerList as $eager) {
334
            $nonEmpty = is_string($eager) && 0 < strlen($eager);
335
            if (!$nonEmpty) {
336
                throw new InvalidOperationException('Eager-load list elements must be non-empty strings');
337
            }
338
        }
339
        $isRelated = TargetSource::ENTITY_SET() == $segment->getTargetSource();
340
        if ($isRelated) {
341
            $queryResult = $this->executeGetResourceDirect($segment, $eagerList);
342
        } else {
343
            $queryResult = $this->executeGetResourceRelated($segment, $eagerList);
344
        }
345
        $segment->setResult($queryResult);
346
    }
347
348
    /**
349
     * @param  SegmentDescriptor         $segment
350
     * @param  array                     $eagerList
351
     * @throws ODataException
352
     * @throws ReflectionException
353
     * @throws InvalidOperationException
354
     * @return null|object|QueryResult
355
     */
356
    private function executeGetResourceDirect(SegmentDescriptor $segment, array $eagerList)
357
    {
358
        if ($segment->isSingleResult()) {
359
            $queryResult = $this->getProviders()->getResourceFromResourceSet(
360
                $segment->getTargetResourceSetWrapper(),
361
                $segment->getKeyDescriptor(),
362
                $eagerList
363
            );
364
        } else {
365
            $skip        = $this->getRequest()->getSkipCount();
366
            $skip        = (null === $skip) ? 0 : $skip;
367
            $skipToken   = $this->getRequest()->getInternalSkipTokenInfo();
368
            $skipToken   = (null != $skipToken) ? $skipToken->getSkipTokenInfo() : null;
369
            $queryResult = $this->getProviders()->getResourceSet(
370
                $this->getRequest()->queryType,
371
                $segment->getTargetResourceSetWrapper(),
372
                $this->getRequest()->getFilterInfo(),
373
                $this->getRequest()->getInternalOrderByInfo(),
374
                $this->getRequest()->getTopCount(),
375
                $skip,
376
                $skipToken,
377
                $eagerList
378
            );
379
        }
380
        return $queryResult;
381
    }
382
383
    /**
384
     * @param SegmentDescriptor $segment
385
     * @param $eagerList
386
     * @throws ODataException
387
     * @throws ReflectionException
388
     * @throws InvalidOperationException
389
     * @return null|object|QueryResult
390
     */
391
    private function executeGetResourceRelated(SegmentDescriptor $segment, $eagerList)
0 ignored issues
show
Unused Code introduced by
The parameter $eagerList is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

391
    private function executeGetResourceRelated(SegmentDescriptor $segment, /** @scrutinizer ignore-unused */ $eagerList)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
392
    {
393
        $projectedProperty     = $segment->getProjectedProperty();
394
        $projectedPropertyKind = null !== $projectedProperty ? $projectedProperty->getKind() :
395
            new ResourcePropertyKind(0);
396
        $queryResult = null;
397
        switch ($projectedPropertyKind) {
398
            case ResourcePropertyKind::RESOURCE_REFERENCE():
399
                $queryResult = $this->getProviders()->getRelatedResourceReference(
400
                    $segment->getPrevious()->getTargetResourceSetWrapper(),
401
                    $segment->getPrevious()->getResult(),
402
                    $segment->getTargetResourceSetWrapper(),
403
                    $projectedProperty
404
                );
405
                break;
406
            case ResourcePropertyKind::RESOURCESET_REFERENCE():
407
                if ($segment->isSingleResult()) {
408
                    $queryResult = $this->getProviders()->getResourceFromRelatedResourceSet(
409
                        $segment->getPrevious()->getTargetResourceSetWrapper(),
410
                        $segment->getPrevious()->getResult(),
411
                        $segment->getTargetResourceSetWrapper(),
412
                        $projectedProperty,
413
                        $segment->getKeyDescriptor()
414
                    );
415
                } else {
416
                    $skipToken   = $this->getRequest()->getInternalSkipTokenInfo();
417
                    $skipToken   = (null !== $skipToken) ? $skipToken->getSkipTokenInfo() : null;
418
                    $queryResult = $this->getProviders()->getRelatedResourceSet(
419
                        $this->getRequest()->queryType,
420
                        $segment->getPrevious()->getTargetResourceSetWrapper(),
421
                        $segment->getPrevious()->getResult(),
422
                        $segment->getTargetResourceSetWrapper(),
423
                        $projectedProperty,
424
                        $this->getRequest()->getFilterInfo(),
425
                        null, // $orderby
426
                        null, // $top
427
                        null, // $skip
428
                        $skipToken
429
                    );
430
                }
431
                break;
432
            default:
433
                $this->checkResourceExistsByIdentifier($segment);
434
                throw new InvalidOperationException('Invalid property kind type for resource retrieval');
435
        }
436
        return $queryResult;
437
    }
438
439
    /**
440
     * @param  SegmentDescriptor $segment
441
     * @throws ODataException
442
     */
443
    private function checkResourceExistsByIdentifier(SegmentDescriptor $segment)
444
    {
445
        if (null === $segment->getPrevious()->getResult()) {
446
            throw ODataException::createResourceNotFoundError(
447
                $segment->getPrevious()->getIdentifier()
448
            );
449
        }
450
    }
451
452
    /**
453
     * @param SegmentDescriptor $segment
454
     */
455
    private function executeGetLink(SegmentDescriptor $segment)
456
    {
457
        $previous = $segment->getPrevious();
458
        assert(isset($previous));
459
        $segment->setResult($previous->getResult());
460
    }
461
462
    /**
463
     * Applies the query options to the resource(s) retrieved from the data source.
464
     *
465
     * @param  SegmentDescriptor $segment The descriptor which holds resource(s) on which query options to be applied
466
     * @throws ODataException
467
     */
468
    private function applyQueryOptions(SegmentDescriptor $segment)
469
    {
470
        $result = $segment->getResult();
471
        if (!$result instanceof QueryResult) {
472
            //If the segment isn't a query result, then there's no paging or counting to be done
473
            return;
474
        }
475
        // Note $inlinecount=allpages means include the total count regardless of paging..so we set the counts first
476
        // regardless if POData does the paging or not.
477
        if ($this->getRequest()->queryType == QueryType::ENTITIES_WITH_COUNT()) {
478
            if ($this->getProviders()->handlesOrderedPaging()) {
479
                $this->getRequest()->setCountValue($result->count);
480
            } else {
481
                $this->getRequest()->setCountValue(count($result->results));
0 ignored issues
show
Bug introduced by
It seems like $result->results can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

481
                $this->getRequest()->setCountValue(count(/** @scrutinizer ignore-type */ $result->results));
Loading history...
482
            }
483
        }
484
        //Have POData perform paging if necessary
485
        if (!$this->getProviders()->handlesOrderedPaging() && !empty($result->results)) {
486
            $result->results = $this->performPaging($result->results);
0 ignored issues
show
Bug introduced by
It seems like $result->results can also be of type object; however, parameter $result of POData\UriProcessor\UriP...sorNew::performPaging() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

486
            $result->results = $this->performPaging(/** @scrutinizer ignore-type */ $result->results);
Loading history...
487
        }
488
        //a bit surprising, but $skip and $top affects $count so update it here, not above
489
        //IE  data.svc/Collection/$count?$top=10 returns 10 even if Collection has 11+ entries
490
        if ($this->getRequest()->queryType == QueryType::COUNT()) {
491
            if ($this->getProviders()->handlesOrderedPaging()) {
492
                $this->getRequest()->setCountValue($result->count);
493
            } else {
494
                $this->getRequest()->setCountValue(count($result->results));
495
            }
496
        }
497
        $segment->setResult($result);
498
    }
499
500
    /**
501
     * If the provider does not perform the paging (ordering, top, skip) then this method does it.
502
     *
503
     * @param array $result
504
     *
505
     * @throws ODataException
506
     * @return array
507
     */
508
    private function performPaging(array $result)
509
    {
510
        //Apply (implicit and explicit) $orderby option
511
        $internalOrderByInfo = $this->getRequest()->getInternalOrderByInfo();
512
        if (null !== $internalOrderByInfo) {
513
            $orderByFunction = $internalOrderByInfo->getSorterFunction();
514
            usort($result, $orderByFunction);
515
        }
516
        //Apply $skiptoken option
517
        $internalSkipTokenInfo = $this->getRequest()->getInternalSkipTokenInfo();
518
        if (null !== $internalSkipTokenInfo) {
519
            $matchingIndex = $internalSkipTokenInfo->getIndexOfFirstEntryInTheNextPage($result);
520
            $result        = array_slice($result, $matchingIndex);
521
        }
522
        //Apply $top and $skip option
523
        if (!empty($result)) {
524
            $top  = $this->getRequest()->getTopCount();
525
            $skip = $this->getRequest()->getSkipCount();
526
            if (null === $skip) {
527
                $skip = 0;
528
            }
529
            $result = array_slice($result, $skip, $top);
530
        }
531
        return $result;
532
    }
533
534
    /**
535
     * Execute the client submitted request against the data source (DELETE).
536
     * @throws ODataException
537
     * @throws UrlFormatException
538
     */
539
    protected function executeDelete()
540
    {
541
        $segment       = $this->getFinalEffectiveSegment();
542
        $requestMethod = $this->getService()->getOperationContext()->incomingRequest()->getMethod();
543
        $resourceSet   = $segment->getTargetResourceSetWrapper();
544
        $keyDescriptor = $segment->getKeyDescriptor();
545
546
        $this->checkUriValidForSuppliedVerb($resourceSet, $keyDescriptor, $requestMethod);
547
        assert($resourceSet instanceof ResourceSet);
548
        $this->getProviders()->deleteResource($resourceSet, $segment->getResult());
549
        $this->getService()->getHost()->setResponseStatusCode(HttpStatus::CODE_NOCONTENT);
550
    }
551
552
    /**
553
     * @return null|SegmentDescriptor
554
     */
555
    protected function getFinalEffectiveSegment()
556
    {
557
        $segment = $this->getRequest()->getLastSegment();
558
        // if last segment is $count, back up one
559
        if (null !== $segment && ODataConstants::URI_COUNT_SEGMENT == $segment->getIdentifier()) {
560
            $segment = $segment->getPrevious();
561
            return $segment;
562
        }
563
        return $segment;
564
    }
565
566
    /**
567
     * @param $resourceSet
568
     * @param $keyDescriptor
569
     * @param $requestMethod
570
     * @throws ODataException
571
     * @throws UrlFormatException
572
     */
573
    protected function checkUriValidForSuppliedVerb($resourceSet, $keyDescriptor, $requestMethod)
574
    {
575
        if (!$resourceSet || !$keyDescriptor) {
576
            $url = $this->getService()->getHost()->getAbsoluteRequestUri()->getUrlAsString();
577
            throw ODataException::createBadRequestError(
578
                Messages::badRequestInvalidUriForThisVerb($url, $requestMethod)
579
            );
580
        }
581
    }
582
583
    /**
584
     * Execute the client submitted request against the data source (PUT).
585
     * @throws InvalidOperationException
586
     * @throws ODataException
587
     * @throws ReflectionException
588
     * @throws Exception
589
     */
590
    protected function executePut()
591
    {
592
        $segment       = $this->getFinalEffectiveSegment();
593
        $requestMethod = $this->getService()->getOperationContext()->incomingRequest()->getMethod();
594
        $resourceSet   = null !== $segment ? $segment->getTargetResourceSetWrapper() : null;
595
        $keyDescriptor = null !== $segment ? $segment->getKeyDescriptor() : null;
596
597
        $this->checkUriValidForSuppliedVerb($resourceSet, $keyDescriptor, $requestMethod);
598
        assert($resourceSet instanceof ResourceSet);
599
        assert($keyDescriptor instanceof KeyDescriptor);
600
601
        $payload = $this->getRequest()->getData();
602
        if (!$payload instanceof ODataEntry) {
603
            throw new InvalidOperationException(get_class($payload));
604
        }
605
        if (empty($payload->id)) {
606
            throw new InvalidOperationException('Payload ID must not be empty for PUT request');
607
        }
608
        $data = $this->getModelDeserialiser()->bulkDeserialise($resourceSet->getResourceType(), $payload);
609
610
        if (empty($data)) {
611
            throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
612
        }
613
614
        $queryResult = $this->getCynicDeserialiser()->processPayload($payload);
615
        $segment->setResult($queryResult);
616
    }
617
618
    /**
619
     * @return ModelDeserialiser
620
     */
621
    public function getModelDeserialiser()
622
    {
623
        return $this->cereal;
624
    }
625
626
    /**
627
     * @return CynicDeserialiser
628
     */
629
    public function getCynicDeserialiser()
630
    {
631
        return $this->cynicDeserialiser;
632
    }
633
634
    /**
635
     * Execute the client submitted request against the data source (POST).
636
     * @throws InvalidOperationException
637
     * @throws ODataException
638
     * @throws UrlFormatException
639
     * @throws ReflectionException
640
     * @throws Exception
641
     */
642
    protected function executePost()
643
    {
644
        $segments      = $this->getRequest()->getSegments();
645
        $requestMethod = $this->getService()->getOperationContext()->incomingRequest()->getMethod();
646
647
        foreach ($segments as $segment) {
648
            $requestTargetKind = $segment->getTargetKind();
649
            if ($requestTargetKind == TargetKind::RESOURCE()) {
650
                $resourceSet = $segment->getTargetResourceSetWrapper();
651
                if (!$resourceSet) {
652
                    $url = $this->getService()->getHost()->getAbsoluteRequestUri()->getUrlAsString();
653
                    $msg = Messages::badRequestInvalidUriForThisVerb($url, $requestMethod);
654
                    throw ODataException::createBadRequestError($msg);
655
                }
656
657
                $payload = $this->getRequest()->getData();
658
                if ($payload instanceof ODataURL) {
659
                    $this->executeGet();
660
                    $masterModel        = $this->getRequest()->getSegments()[0]->getResult();
661
                    $masterResourceSet  = $this->getRequest()->getSegments()[0]->getTargetResourceSetWrapper();
662
                    $masterNavProperty  = $this->getRequest()->getLastSegment()->getIdentifier();
663
                    $slaveModelUri      = new Url($payload->getUrl());
664
                    $host               = $this->getService()->getHost();
665
                    $absoluteServiceUri = $host->getAbsoluteServiceUri();
666
                    $requestUriSegments = array_slice(
667
                        $slaveModelUri->getSegments(),
668
                        $absoluteServiceUri->getSegmentCount()
669
                    );
670
                    $newSegments = SegmentParser::parseRequestUriSegments(
671
                        $requestUriSegments,
672
                        $this->getService()->getProvidersWrapper(),
673
                        true
674
                    );
675
                    $this->executeGetResource($newSegments[0]);
676
                    $slaveModel       = $newSegments[0]->getResult();
677
                    $slaveResourceSet = $newSegments[0]->getTargetResourceSetWrapper();
678
                    $linkAdded        = $this->getProviders()
679
                        ->hookSingleModel(
680
                            $masterResourceSet,
681
                            $masterModel,
682
                            $slaveResourceSet,
683
                            $slaveModel,
684
                            $masterNavProperty
685
                        );
686
                    if ($linkAdded) {
687
                        $this->getService()->getHost()->setResponseStatusCode(HttpStatus::CODE_NOCONTENT);
688
                    } else {
689
                        throw ODataException::createInternalServerError('AdapterIndicatedLinkNotAttached');
690
                    }
691
                    foreach ($segments as $innerSegment) {
692
                        $innerSegment->setResult(null);
693
                    }
694
                    return;
695
                }
696
                if (!$payload instanceof ODataEntry) {
697
                    throw new InvalidOperationException(get_class($payload));
698
                }
699
                if (!empty($payload->id)) {
700
                    throw new InvalidOperationException('Payload ID must be empty for POST request');
701
                }
702
703
                $data = $this->getModelDeserialiser()->bulkDeserialise($resourceSet->getResourceType(), $payload);
704
705
                if (empty($data)) {
706
                    throw ODataException::createBadRequestError(Messages::noDataForThisVerb($requestMethod));
707
                }
708
                $this->getService()->getHost()->setResponseStatusCode(HttpStatus::CODE_CREATED);
709
                $queryResult = $this->getCynicDeserialiser()->processPayload($payload);
710
                $keyID       = $payload->id;
711
                if (!$keyID instanceof KeyDescriptor) {
712
                    throw new InvalidOperationException(get_class($keyID));
0 ignored issues
show
Bug introduced by
$keyID of type null is incompatible with the type object expected by parameter $object of get_class(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

712
                    throw new InvalidOperationException(get_class(/** @scrutinizer ignore-type */ $keyID));
Loading history...
713
                }
714
715
                $locationUrl        = $keyID->generateRelativeUri($resourceSet->getResourceSet());
716
                $absoluteServiceUri = $this->getService()->getHost()->getAbsoluteServiceUri()->getUrlAsString();
717
                $location           = rtrim($absoluteServiceUri, '/') . '/' . $locationUrl;
718
                $this->getService()->getHost()->setResponseLocation($location);
719
                $segment->setResult($queryResult);
720
            }
721
        }
722
    }
723
724
    /**
725
     * Gets the request expander instance.
726
     *
727
     * @return RequestExpander
728
     */
729
    public function getExpander()
730
    {
731
        return $this->expander;
732
    }
733
}
734