Passed
Branch master (950424)
by Christopher
11:06
created

createResourceforResourceSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 3
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace POData\Providers;
4
5
use POData\Common\Messages;
6
use POData\Common\ODataException;
7
use POData\Providers\Expression\IExpressionProvider;
8
use POData\Providers\Metadata\ResourceEntityType;
9
use POData\Providers\Metadata\ResourceProperty;
10
use POData\Providers\Metadata\ResourceSet;
11
use POData\Providers\Metadata\ResourceType;
12
use POData\Providers\Query\IQueryProvider;
13
use POData\Providers\Query\QueryResult;
14
use POData\Providers\Query\QueryType;
15
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
16
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
17
use POData\UriProcessor\QueryProcessor\SkipTokenParser\InternalSkipTokenInfo;
18
use POData\UriProcessor\QueryProcessor\SkipTokenParser\SkipTokenInfo;
19
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
20
21
class ProvidersQueryWrapper
22
{
23
    /**
24
     * Holds reference to IQueryProvider implementation.
25
     *
26
     * @var IQueryProvider
27
     */
28
    private $queryProvider;
29
30
    /**
31
     * Creates a new instance of ProvidersWrapper.
32
     *
33
     * @param IQueryProvider $query Reference to IQueryProvider implementation
34
     */
35
    public function __construct(IQueryProvider $query)
36
    {
37
        $this->queryProvider = $query;
38
    }
39
40
    public function getQueryProvider()
41
    {
42
        return $this->queryProvider;
43
    }
44
45
    /**
46
     * Get related resource set for a resource.
47
     *
48
     * @param QueryType          $queryType         Indicates if this is a query for a count, entities, or entities
49
     *                                              with a count
50
     * @param ResourceSet        $sourceResourceSet The entity set containing the source entity
51
     * @param object             $sourceEntity      The source entity instance
52
     * @param ResourceSet        $targetResourceSet The resource set containing the target of the navigation property
53
     * @param ResourceProperty   $targetProperty    The navigation property to retrieve
54
     * @param FilterInfo|null    $filterInfo        Represents the $filter parameter of the OData query.
55
     *                                              NULL if no $filter specified
56
     * @param mixed|null         $orderBy           sorted order if we want to get the data in some specific order
57
     * @param int|null           $top               The top count
58
     * @param int|null           $skip              The skip count
59
     * @param SkipTokenInfo|null $skipToken         The skip token
60
     *
61
     * @throws ODataException
62
     *
63
     * @return QueryResult
64
     */
65 View Code Duplication
    public function getRelatedResourceSet(
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...
66
        QueryType $queryType,
67
        ResourceSet $sourceResourceSet,
68
        $sourceEntity,
69
        ResourceSet $targetResourceSet,
70
        ResourceProperty $targetProperty,
71
        FilterInfo $filterInfo = null,
72
        $orderBy = null,
73
        $top = null,
74
        $skip = null,
75
        $skipToken = null
76
    ) {
77
        $queryResult = $this->getQueryProvider()->getRelatedResourceSet(
78
            $queryType,
79
            $sourceResourceSet,
80
            $sourceEntity,
81
            $targetResourceSet,
82
            $targetProperty,
83
            $filterInfo,
84
            $orderBy,
85
            $top,
86
            $skip,
87
            $skipToken
88
        );
89
90
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getRelatedResourceSet');
91
92
        return $queryResult;
93
    }
94
95
    /**
96
     * Gets collection of entities belongs to an entity set
97
     * IE: http://host/EntitySet
98
     *  http://host/EntitySet?$skip=10&$top=5&filter=Prop gt Value.
99
     *
100
     * @param QueryType                $queryType   Is this is a query for a count, entities, or entities-with-count
101
     * @param ResourceSet              $resourceSet The entity set containing the entities to fetch
102
     * @param FilterInfo|null          $filterInfo  The $filter parameter of the OData query.  NULL if none specified
103
     * @param null|InternalOrderByInfo $orderBy     sorted order if we want to get the data in some specific order
104
     * @param int|null                 $top         number of records which need to be retrieved
105
     * @param int|null                 $skip        number of records which need to be skipped
106
     * @param SkipTokenInfo|null       $skipToken   value indicating what records to skip
107
     * @param string[]|null            $eagerLoad   array of relations to eager load
108
     *
109
     * @return QueryResult
110
     */
111 View Code Duplication
    public function getResourceSet(
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...
112
        QueryType $queryType,
113
        ResourceSet $resourceSet,
114
        FilterInfo $filterInfo = null,
115
        InternalOrderByInfo $orderBy = null,
116
        $top = null,
117
        $skip = null,
118
        SkipTokenInfo $skipToken = null,
119
        array $eagerLoad = null
120
    ) {
121
        $queryResult = $this->getQueryProvider()->getResourceSet(
122
            $queryType,
123
            $resourceSet,
124
            $filterInfo,
125
            $orderBy,
126
            $top,
127
            $skip,
128
            $skipToken,
129
            $eagerLoad
130
        );
131
132
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getResourceSet');
133
134
        return $queryResult;
135
    }
136
137
    /**
138
     * Puts an entity instance to entity set identified by a key.
139
     *
140
     * @param ResourceSet   $resourceSet   The entity set containing the entity to update
141
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
142
     * @param $data
143
     *
144
     * @return bool|null Returns result of executing query
145
     */
146
    public function putResource(
147
        ResourceSet $resourceSet,
148
        KeyDescriptor $keyDescriptor,
149
        $data
150
    ) {
151
        $queryResult = $this->getQueryProvider()->putResource(
152
            $resourceSet,
153
            $keyDescriptor,
154
            $data
155
        );
156
157
        return $queryResult;
158
    }
159
160
    /**
161
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
162
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
163
     * perform the ordering and paging.
164
     *
165
     * @return bool True if the query provider can handle ordered paging, false if POData should perform the paging
166
     */
167
    public function handlesOrderedPaging()
168
    {
169
        return $this->getQueryProvider()->handlesOrderedPaging();
170
    }
171
172
    /**
173
     * Gets the underlying custom expression provider, the end developer is
174
     * responsible for implementing IExpressionProvider if he choose for.
175
     *
176
     * @throws ODataException
177
     *
178
     * @return IExpressionProvider Instance of IExpressionProvider implementation
179
     */
180
    public function getExpressionProvider()
181
    {
182
        $expressionProvider = $this->getQueryProvider()->getExpressionProvider();
183
        if (null === $expressionProvider) {
184
            throw ODataException::createInternalServerError(
185
                Messages::providersWrapperExpressionProviderMustNotBeNullOrEmpty()
186
            );
187
        }
188
189
        if (!$expressionProvider instanceof IExpressionProvider) {
190
            throw ODataException::createInternalServerError(
191
                Messages::providersWrapperInvalidExpressionProviderInstance()
192
            );
193
        }
194
195
        return $expressionProvider;
196
    }
197
198
    /**
199
     * @param ResourceSet $resourceSet          The entity set containing the entity to fetch
200
     * @param object|null $sourceEntityInstance The source entity instance
201
     * @param object      $data                 the New data for the entity instance
202
     *
203
     * @return object|null returns the newly created model if successful, or null if model creation failed
204
     */
205
    public function createResourceforResourceSet(
206
        ResourceSet $resourceSet,
207
        $sourceEntityInstance,
208
        $data
209
    ) {
210
        return $this->getQueryProvider()->createResourceforResourceSet(
211
            $resourceSet,
212
            $sourceEntityInstance,
213
            $data
214
        );
215
    }
216
217
    /**
218
     * Delete resource from a resource set.
219
     *
220
     * @param ResourceSet $sourceResourceSet
221
     * @param object      $sourceEntityInstance
222
     *
223
     * @return bool true if resources successfully deleted, otherwise false
224
     */
225
    public function deleteResource(
226
        ResourceSet $sourceResourceSet,
227
        $sourceEntityInstance
228
    ) {
229
        return $this->getQueryProvider()->deleteResource(
230
            $sourceResourceSet,
231
            $sourceEntityInstance
232
        );
233
    }
234
235
    /**
236
     * Updates a resource.
237
     *
238
     * @param ResourceSet   $sourceResourceSet    The entity set containing the source entity
239
     * @param object        $sourceEntityInstance The source entity instance
240
     * @param KeyDescriptor $keyDescriptor        The key identifying the entity to fetch
241
     * @param object        $data                 the New data for the entity instance
242
     * @param bool          $shouldUpdate         Should undefined values be updated or reset to default
243
     *
244
     * @return object|null the new resource value if it is assignable, or throw exception for null
245
     */
246
    public function updateResource(
247
        ResourceSet $sourceResourceSet,
248
        $sourceEntityInstance,
249
        KeyDescriptor $keyDescriptor,
250
        $data,
251
        $shouldUpdate = false
252
    ) {
253
        return $this->getQueryProvider()->updateResource(
254
            $sourceResourceSet,
255
            $sourceEntityInstance,
256
            $keyDescriptor,
257
            $data,
258
            $shouldUpdate
259
        );
260
    }
261
262
    /**
263
     * Create multiple new resources in a resource set.
264
     * @param ResourceSet $sourceResourceSet The entity set containing the entity to fetch
265
     * @param object[]    $data              The new data for the entity instance
266
     *
267
     * @return object[]|null returns the newly created model if successful, or null if model creation failed
268
     */
269
    public function createBulkResourceforResourceSet(
270
        ResourceSet $sourceResourceSet,
271
        array $data
272
    ) {
273
        return $this->getQueryProvider()->createBulkResourceforResourceSet(
274
            $sourceResourceSet,
275
            $data
276
        );
277
    }
278
279
    /**
280
     * Updates a group of resources in a resource set.
281
     *
282
     * @param ResourceSet     $sourceResourceSet    The entity set containing the source entity
283
     * @param object          $sourceEntityInstance The source entity instance
284
     * @param KeyDescriptor[] $keyDescriptor        The key identifying the entity to fetch
285
     * @param object[]        $data                 The new data for the entity instances
286
     * @param bool            $shouldUpdate         Should undefined values be updated or reset to default
287
     *
288
     * @return object[]|null the new resource value if it is assignable, or throw exception for null
289
     */
290
    public function updateBulkResource(
291
        ResourceSet $sourceResourceSet,
292
        $sourceEntityInstance,
293
        array $keyDescriptor,
294
        array $data,
295
        $shouldUpdate = false
296
    ) {
297
        return $this->getQueryProvider()->updateBulkResource(
298
            $sourceResourceSet,
299
            $sourceEntityInstance,
300
            $keyDescriptor,
301
            $data,
302
            $shouldUpdate
303
        );
304
    }
305
306
    /**
307
     * Get related resource for a resource.
308
     *
309
     * @param ResourceSet      $sourceResourceSet The source resource set
310
     * @param object           $sourceEntity      The source resource
311
     * @param ResourceSet      $targetResourceSet The resource set of the navigation
312
     *                                            property
313
     * @param ResourceProperty $targetProperty    The navigation property to be
314
     *                                            retrieved
315
     *
316
     * @throws ODataException
317
     *
318
     * @return object|null The related resource if exists, else null
319
     */
320
    public function getRelatedResourceReference(
321
        ResourceSet $sourceResourceSet,
322
        $sourceEntity,
323
        ResourceSet $targetResourceSet,
324
        ResourceProperty $targetProperty
325
    ) {
326
        $entityInstance = $this->getQueryProvider()->getRelatedResourceReference(
327
            $sourceResourceSet,
328
            $sourceEntity,
329
            $targetResourceSet,
330
            $targetProperty
331
        );
332
333
        // we will not throw error if the resource reference is null
334
        // e.g. Orders(1234)/Customer => Customer can be null, this is
335
        // allowed if Customer is last segment. consider the following:
336
        // Orders(1234)/Customer/Orders => here if Customer is null then
337
        // the UriProcessor will throw error.
338
        if (null !== $entityInstance) {
339
            $methodName = 'IQueryProvider::getRelatedResourceReference';
340
341
            $targetResourceType = $this->verifyResourceType($methodName, $entityInstance, $targetResourceSet);
342
            foreach ($targetProperty->getResourceType()->getKeyProperties() as $keyName => $resourceProperty) {
343
                try {
344
                    $keyValue = $targetResourceType->getPropertyValue($entityInstance, $keyName);
345
                    if (null === $keyValue) {
346
                        throw ODataException::createInternalServerError(
347
                            Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties(
348
                                'IDSQP::getRelatedResourceReference'
349
                            )
350
                        );
351
                    }
352
                } catch (\ReflectionException $reflectionException) {
353
                    // Left blank - we're simply squashing reflection exceptions
354
                }
355
            }
356
        }
357
358
        return $entityInstance;
359
    }
360
361
    /**
362
     * Gets a related entity instance from an entity set identified by a key.
363
     *
364
     * @param ResourceSet      $sourceResourceSet The entity set related to the entity to be fetched
365
     * @param object           $sourceEntity      The related entity instance
366
     * @param ResourceSet      $targetResourceSet The entity set from which entity needs to be fetched
367
     * @param ResourceProperty $targetProperty    The metadata of the target property
368
     * @param KeyDescriptor    $keyDescriptor     The key to identify the entity to be fetched
369
     *
370
     * @return object|null Returns entity instance if found, else null
371
     */
372
    public function getResourceFromRelatedResourceSet(
373
        ResourceSet $sourceResourceSet,
374
        $sourceEntity,
375
        ResourceSet $targetResourceSet,
376
        ResourceProperty $targetProperty,
377
        KeyDescriptor $keyDescriptor
378
    ) {
379
        $entityInstance = $this->getQueryProvider()->getResourceFromRelatedResourceSet(
380
            $sourceResourceSet,
381
            $sourceEntity,
382
            $targetResourceSet,
383
            $targetProperty,
384
            $keyDescriptor
385
        );
386
387
        $this->validateEntityInstance(
388
            $entityInstance,
389
            $targetResourceSet,
390
            $keyDescriptor,
391
            'IQueryProvider::getResourceFromRelatedResourceSet'
392
        );
393
394
        return $entityInstance;
395
    }
396
397
    /**
398
     * Gets an entity instance from an entity set identified by a key.
399
     *
400
     * @param ResourceSet   $resourceSet   The entity set containing the entity to fetch
401
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
402
     * @param string[]|null $eagerLoad     array of relations to eager load
403
     *
404
     * @return object|null Returns entity instance if found, else null
405
     */
406
    public function getResourceFromResourceSet(
407
        ResourceSet $resourceSet,
408
        KeyDescriptor $keyDescriptor,
409
        array $eagerLoad = null
410
    ) {
411
        $entityInstance = $this->getQueryProvider()
412
            ->getResourceFromResourceSet($resourceSet, $keyDescriptor, $eagerLoad);
413
        $this->validateEntityInstance(
414
            $entityInstance,
415
            $resourceSet,
416
            $keyDescriptor,
417
            'IQueryProvider::getResourceFromResourceSet'
418
        );
419
420
        return $entityInstance;
421
    }
422
423
    /**
424
     * Attaches child model to parent model.
425
     *
426
     * @param ResourceSet $sourceResourceSet
427
     * @param object      $sourceEntityInstance
428
     * @param ResourceSet $targetResourceSet
429
     * @param object      $targetEntityInstance
430
     * @param $navPropName
431
     *
432
     * @return bool
433
     */
434
    public function hookSingleModel(
435
        ResourceSet $sourceResourceSet,
436
        $sourceEntityInstance,
437
        ResourceSet $targetResourceSet,
438
        $targetEntityInstance,
439
        $navPropName
440
    ) {
441
        return $this->getQueryProvider()->hookSingleModel(
442
            $sourceResourceSet,
443
            $sourceEntityInstance,
444
            $targetResourceSet,
445
            $targetEntityInstance,
446
            $navPropName
447
        );
448
    }
449
450
    /**
451
     * Removes child model from parent model.
452
     *
453
     * @param ResourceSet $sourceResourceSet
454
     * @param object      $sourceEntityInstance
455
     * @param ResourceSet $targetResourceSet
456
     * @param object      $targetEntityInstance
457
     * @param $navPropName
458
     *
459
     * @return bool
460
     */
461
    public function unhookSingleModel(
462
        ResourceSet $sourceResourceSet,
463
        $sourceEntityInstance,
464
        ResourceSet $targetResourceSet,
465
        $targetEntityInstance,
466
        $navPropName
467
    ) {
468
        return $this->getQueryProvider()->unhookSingleModel(
469
            $sourceResourceSet,
470
            $sourceEntityInstance,
471
            $targetResourceSet,
472
            $targetEntityInstance,
473
            $navPropName
474
        );
475
    }
476
477
478
    /**
479
     * @param QueryResult $queryResult
480
     * @param QueryType   $queryType
481
     * @param string      $methodName
482
     *
483
     * @throws ODataException
484
     */
485
    protected function validateQueryResult($queryResult, QueryType $queryType, $methodName)
486
    {
487
        if (!$queryResult instanceof QueryResult) {
488
            throw ODataException::createInternalServerError(
489
                Messages::queryProviderReturnsNonQueryResult($methodName)
490
            );
491
        }
492
493
        $isResultArray = is_array($queryResult->results);
494
495
        if (QueryType::COUNT() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType) {
496
            //and the provider is supposed to handle the ordered paging they must return a count!
497
            if ($this->handlesOrderedPaging() && !is_numeric($queryResult->count)) {
498
                throw ODataException::createInternalServerError(
499
                    Messages::queryProviderResultCountMissing($methodName, $queryType)
500
                );
501
            }
502
503
            //If POData is supposed to handle the ordered aging they must return results! (possibly empty)
504
            if (!$this->handlesOrderedPaging() && !$isResultArray) {
505
                throw ODataException::createInternalServerError(
506
                    Messages::queryProviderResultsMissing($methodName, $queryType)
507
                );
508
            }
509
        }
510
511
        if ((QueryType::ENTITIES() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType)
512
            && !$isResultArray) {
513
            throw ODataException::createInternalServerError(
514
                Messages::queryProviderResultsMissing($methodName, $queryType)
515
            );
516
        }
517
    }
518
519
    /**
520
     * Validate the given entity instance.
521
     *
522
     * @param object|null   $entityInstance Entity instance to validate
523
     * @param ResourceSet   &$resourceSet   Resource set to which the entity
524
     *                                      instance belongs to
525
     * @param KeyDescriptor &$keyDescriptor The key descriptor
526
     * @param string        $methodName     Method from which this function
527
     *                                      invoked
528
     *
529
     * @throws ODataException
530
     */
531
    protected function validateEntityInstance(
532
        $entityInstance,
533
        ResourceSet &$resourceSet,
534
        KeyDescriptor &$keyDescriptor,
535
        $methodName
536
    ) {
537
        if (null === $entityInstance) {
538
            throw ODataException::createResourceNotFoundError($resourceSet->getName());
539
        }
540
541
        $resourceType = $this->verifyResourceType($methodName, $entityInstance, $resourceSet);
542
543
        foreach ($keyDescriptor->getValidatedNamedValues() as $keyName => $valueDescription) {
544
            try {
545
                $keyValue = $resourceType->getPropertyValue($entityInstance, $keyName);
546
                if (null === $keyValue) {
547
                    throw ODataException::createInternalServerError(
548
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties($methodName)
549
                    );
550
                }
551
552
                $convertedValue = $valueDescription[1]->convert($valueDescription[0]);
553
                if ($keyValue != $convertedValue) {
554
                    throw ODataException::createInternalServerError(
555
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNonMatchingKeys($methodName)
556
                    );
557
                }
558
            } catch (\ReflectionException $reflectionException) {
559
                // Left blank - we're simply squashing reflection exceptions
560
            }
561
        }
562
    }
563
564
    /**
565
     * @param string $methodName
566
     * @param $entityInstance
567
     * @param ResourceSet $resourceSet
568
     *
569
     * @throws ODataException
570
     *
571
     * @return ResourceEntityType
572
     */
573
    private function verifyResourceType($methodName, $entityInstance, ResourceSet $resourceSet)
574
    {
575
        $resourceType = $resourceSet->getResourceType();
576
        $entityName = $resourceType->getInstanceType()->getName();
577
        if (!($entityInstance instanceof $entityName)) {
578
            throw ODataException::createInternalServerError(
579
                Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
580
                    $entityName,
581
                    $methodName
582
                )
583
            );
584
        }
585
586
        return $resourceType;
587
    }
588
589
    /**
590
     * Start database transaction.
591
     *
592
     * @return void
593
     */
594
    public function startTransaction()
595
    {
596
        $this->getQueryProvider()->startTransaction();
597
    }
598
599
    /**
600
     * Commit database transaction.
601
     *
602
     * @return void
603
     */
604
    public function commitTransaction()
605
    {
606
        $this->getQueryProvider()->commitTransaction();
607
    }
608
609
    /**
610
     * Abort database transaction.
611
     *
612
     * @return void
613
     */
614
    public function rollBackTransaction()
615
    {
616
        $this->getQueryProvider()->rollBackTransaction();
617
    }
618
}
619