Completed
Push — master ( 561959...44ef42 )
by Alex
02:58
created

ProvidersQueryWrapper::handlesOrderedPaging()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 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
     *
108
     * @return QueryResult
109
     */
110 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...
111
        QueryType $queryType,
112
        ResourceSet $resourceSet,
113
        FilterInfo $filterInfo = null,
114
        InternalOrderByInfo $orderBy = null,
115
        $top = null,
116
        $skip = null,
117
        SkipTokenInfo $skipToken = null
118
    ) {
119
        $queryResult = $this->getQueryProvider()->getResourceSet(
120
            $queryType,
121
            $resourceSet,
122
            $filterInfo,
123
            $orderBy,
124
            $top,
125
            $skip,
126
            $skipToken
127
        );
128
129
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getResourceSet');
130
131
        return $queryResult;
132
    }
133
134
    /**
135
     * Puts an entity instance to entity set identified by a key.
136
     *
137
     * @param ResourceSet   $resourceSet   The entity set containing the entity to update
138
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
139
     * @param $data
140
     *
141
     * @return bool|null Returns result of executing query
142
     */
143
    public function putResource(
144
        ResourceSet $resourceSet,
145
        KeyDescriptor $keyDescriptor,
146
        $data
147
    ) {
148
        $queryResult = $this->getQueryProvider()->putResource(
149
            $resourceSet,
150
            $keyDescriptor,
151
            $data
152
        );
153
154
        return $queryResult;
155
    }
156
157
    /**
158
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
159
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
160
     * perform the ordering and paging.
161
     *
162
     * @return bool True if the query provider can handle ordered paging, false if POData should perform the paging
163
     */
164
    public function handlesOrderedPaging()
0 ignored issues
show
Coding Style introduced by
function handlesOrderedPaging() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
165
    {
166
        return $this->getQueryProvider()->handlesOrderedPaging();
167
    }
168
169
    /**
170
     * Gets the underlying custom expression provider, the end developer is
171
     * responsible for implementing IExpressionProvider if he choose for.
172
     *
173
     * @throws ODataException
174
     *
175
     * @return IExpressionProvider Instance of IExpressionProvider implementation
176
     */
177
    public function getExpressionProvider()
178
    {
179
        $expressionProvider = $this->getQueryProvider()->getExpressionProvider();
180
        if (null === $expressionProvider) {
181
            throw ODataException::createInternalServerError(
182
                Messages::providersWrapperExpressionProviderMustNotBeNullOrEmpty()
183
            );
184
        }
185
186
        if (!$expressionProvider instanceof IExpressionProvider) {
187
            throw ODataException::createInternalServerError(
188
                Messages::providersWrapperInvalidExpressionProviderInstance()
189
            );
190
        }
191
192
        return $expressionProvider;
193
    }
194
195
    /**
196
     * @param ResourceSet $resourceSet          The entity set containing the entity to fetch
197
     * @param object|null $sourceEntityInstance The source entity instance
198
     * @param object      $data                 the New data for the entity instance
199
     *
200
     * @return object|null returns the newly created model if successful, or null if model creation failed
201
     */
202
    public function createResourceforResourceSet(
203
        ResourceSet $resourceSet,
204
        $sourceEntityInstance,
205
        $data
206
    ) {
207
        return $this->getQueryProvider()->createResourceforResourceSet(
208
            $resourceSet,
209
            $sourceEntityInstance,
210
            $data
211
        );
212
    }
213
214
    /**
215
     * Delete resource from a resource set.
216
     *
217
     * @param ResourceSet $sourceResourceSet
218
     * @param object      $sourceEntityInstance
219
     *
220
     * @return bool true if resources successfully deleted, otherwise false
221
     */
222
    public function deleteResource(
0 ignored issues
show
Coding Style introduced by
function deleteResource() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
223
        ResourceSet $sourceResourceSet,
224
        $sourceEntityInstance
225
    ) {
226
        return $this->getQueryProvider()->deleteResource(
227
            $sourceResourceSet,
228
            $sourceEntityInstance
229
        );
230
    }
231
232
    /**
233
     * Updates a resource.
234
     *
235
     * @param ResourceSet   $sourceResourceSet    The entity set containing the source entity
236
     * @param object        $sourceEntityInstance The source entity instance
237
     * @param KeyDescriptor $keyDescriptor        The key identifying the entity to fetch
238
     * @param object        $data                 the New data for the entity instance
239
     * @param bool          $shouldUpdate         Should undefined values be updated or reset to default
240
     *
241
     * @return object|null the new resource value if it is assignable, or throw exception for null
242
     */
243
    public function updateResource(
244
        ResourceSet $sourceResourceSet,
245
        $sourceEntityInstance,
246
        KeyDescriptor $keyDescriptor,
247
        $data,
248
        $shouldUpdate = false
249
    ) {
250
        return $this->getQueryProvider()->updateResource(
251
            $sourceResourceSet,
252
            $sourceEntityInstance,
253
            $keyDescriptor,
254
            $data,
255
            $shouldUpdate
256
        );
257
    }
258
259
    /**
260
     * Create multiple new resources in a resource set.
261
     * @param ResourceSet   $sourceResourceSet  The entity set containing the entity to fetch
262
     * @param object[]      $data               The new data for the entity instance
263
     *
264
     * @return object[]|null returns the newly created model if successful, or null if model creation failed
265
     */
266
    public function createBulkResourceforResourceSet(
267
        ResourceSet $sourceResourceSet,
268
        array $data
269
    ) {
270
        return $this->getQueryProvider()->createBulkResourceforResourceSet(
271
            $sourceResourceSet,
272
            $data
273
        );
274
    }
275
276
    /**
277
     * Updates a group of resources in a resource set.
278
     *
279
     * @param ResourceSet       $sourceResourceSet    The entity set containing the source entity
280
     * @param object            $sourceEntityInstance The source entity instance
281
     * @param KeyDescriptor[]   $keyDescriptor        The key identifying the entity to fetch
282
     * @param object[]          $data                 The new data for the entity instances
283
     * @param bool              $shouldUpdate         Should undefined values be updated or reset to default
284
     *
285
     * @return object[]|null the new resource value if it is assignable, or throw exception for null
286
     */
287
    public function updateBulkResource(
288
        ResourceSet $sourceResourceSet,
289
        $sourceEntityInstance,
290
        array $keyDescriptor,
291
        array $data,
292
        $shouldUpdate = false
293
    ) {
294
        return $this->getQueryProvider()->updateBulkResource(
295
            $sourceResourceSet,
296
            $sourceEntityInstance,
297
            $keyDescriptor,
298
            $data,
299
            $shouldUpdate
300
        );
301
    }
302
303
    /**
304
     * Get related resource for a resource.
305
     *
306
     * @param ResourceSet      $sourceResourceSet The source resource set
307
     * @param object           $sourceEntity      The source resource
308
     * @param ResourceSet      $targetResourceSet The resource set of the navigation
309
     *                                            property
310
     * @param ResourceProperty $targetProperty    The navigation property to be
311
     *                                            retrieved
312
     *
313
     * @throws ODataException
314
     *
315
     * @return object|null The related resource if exists, else null
316
     */
317
    public function getRelatedResourceReference(
318
        ResourceSet $sourceResourceSet,
319
        $sourceEntity,
320
        ResourceSet $targetResourceSet,
321
        ResourceProperty $targetProperty
322
    ) {
323
        $entityInstance = $this->getQueryProvider()->getRelatedResourceReference(
324
            $sourceResourceSet,
325
            $sourceEntity,
326
            $targetResourceSet,
327
            $targetProperty
328
        );
329
330
        // we will not throw error if the resource reference is null
331
        // e.g. Orders(1234)/Customer => Customer can be null, this is
332
        // allowed if Customer is last segment. consider the following:
333
        // Orders(1234)/Customer/Orders => here if Customer is null then
334
        // the UriProcessor will throw error.
335
        if (null !== $entityInstance) {
336
            $methodName = 'IQueryProvider::getRelatedResourceReference';
337
338
            $targetResourceType = $this->verifyResourceType($methodName, $entityInstance, $targetResourceSet);
339
            foreach ($targetProperty->getResourceType()->getKeyProperties() as $keyName => $resourceProperty) {
340
                try {
341
                    $keyValue = $targetResourceType->getPropertyValue($entityInstance, $keyName);
342
                    if (null === $keyValue) {
343
                        throw ODataException::createInternalServerError(
344
                            Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties(
345
                                'IDSQP::getRelatedResourceReference'
346
                            )
347
                        );
348
                    }
349
                } catch (\ReflectionException $reflectionException) {
350
                    // Left blank - we're simply squashing reflection exceptions
351
                }
352
            }
353
        }
354
355
        return $entityInstance;
356
    }
357
358
    /**
359
     * Gets a related entity instance from an entity set identified by a key.
360
     *
361
     * @param ResourceSet      $sourceResourceSet The entity set related to the entity to be fetched
362
     * @param object           $sourceEntity      The related entity instance
363
     * @param ResourceSet      $targetResourceSet The entity set from which entity needs to be fetched
364
     * @param ResourceProperty $targetProperty    The metadata of the target property
365
     * @param KeyDescriptor    $keyDescriptor     The key to identify the entity to be fetched
366
     *
367
     * @return object|null Returns entity instance if found, else null
368
     */
369
    public function getResourceFromRelatedResourceSet(
370
        ResourceSet $sourceResourceSet,
371
        $sourceEntity,
372
        ResourceSet $targetResourceSet,
373
        ResourceProperty $targetProperty,
374
        KeyDescriptor $keyDescriptor
375
    ) {
376
        $entityInstance = $this->getQueryProvider()->getResourceFromRelatedResourceSet(
377
            $sourceResourceSet,
378
            $sourceEntity,
379
            $targetResourceSet,
380
            $targetProperty,
381
            $keyDescriptor
382
        );
383
384
        $this->validateEntityInstance(
385
            $entityInstance,
386
            $targetResourceSet,
387
            $keyDescriptor,
388
            'IQueryProvider::getResourceFromRelatedResourceSet'
389
        );
390
391
        return $entityInstance;
392
    }
393
394
    /**
395
     * Gets an entity instance from an entity set identified by a key.
396
     *
397
     * @param ResourceSet   $resourceSet   The entity set containing the entity to fetch
398
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
399
     *
400
     * @return object|null Returns entity instance if found, else null
401
     */
402
    public function getResourceFromResourceSet(ResourceSet $resourceSet, KeyDescriptor $keyDescriptor)
403
    {
404
        $entityInstance = $this->getQueryProvider()->getResourceFromResourceSet($resourceSet, $keyDescriptor);
405
        $this->validateEntityInstance(
406
            $entityInstance,
407
            $resourceSet,
408
            $keyDescriptor,
409
            'IQueryProvider::getResourceFromResourceSet'
410
        );
411
412
        return $entityInstance;
413
    }
414
415
    /**
416
     * Attaches child model to parent model
417
     *
418
     * @param ResourceSet $sourceResourceSet
419
     * @param object $sourceEntityInstance
420
     * @param ResourceSet $targetResourceSet
421
     * @param object $targetEntityInstance
422
     * @param $navPropName
423
     *
424
     * @return bool
425
     */
426
    public function hookSingleModel(
427
        ResourceSet $sourceResourceSet,
428
        $sourceEntityInstance,
429
        ResourceSet $targetResourceSet,
430
        $targetEntityInstance,
431
        $navPropName
432
    ) {
433
        return $this->getQueryProvider()->hookSingleModel(
434
            $sourceResourceSet,
435
            $sourceEntityInstance,
436
            $targetResourceSet,
437
            $targetEntityInstance,
438
            $navPropName
439
        );
440
    }
441
442
    /**
443
     * Removes child model from parent model
444
     *
445
     * @param ResourceSet $sourceResourceSet
446
     * @param object $sourceEntityInstance
447
     * @param ResourceSet $targetResourceSet
448
     * @param object $targetEntityInstance
449
     * @param $navPropName
450
     *
451
     * @return bool
452
     */
453
    public function unhookSingleModel(
454
        ResourceSet $sourceResourceSet,
455
        $sourceEntityInstance,
456
        ResourceSet $targetResourceSet,
457
        $targetEntityInstance,
458
        $navPropName
459
    ) {
460
        return $this->getQueryProvider()->unhookSingleModel(
461
            $sourceResourceSet,
462
            $sourceEntityInstance,
463
            $targetResourceSet,
464
            $targetEntityInstance,
465
            $navPropName
466
        );
467
    }
468
469
470
    /**
471
     * @param QueryResult $queryResult
472
     * @param QueryType   $queryType
473
     * @param string      $methodName
474
     *
475
     * @throws ODataException
476
     */
477
    protected function validateQueryResult($queryResult, QueryType $queryType, $methodName)
478
    {
479
        if (!$queryResult instanceof QueryResult) {
480
            throw ODataException::createInternalServerError(
481
                Messages::queryProviderReturnsNonQueryResult($methodName)
482
            );
483
        }
484
485
        $isResultArray = is_array($queryResult->results);
486
487
        if (QueryType::COUNT() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType) {
488
            //and the provider is supposed to handle the ordered paging they must return a count!
489
            if ($this->handlesOrderedPaging() && !is_numeric($queryResult->count)) {
490
                throw ODataException::createInternalServerError(
491
                    Messages::queryProviderResultCountMissing($methodName, $queryType)
492
                );
493
            }
494
495
            //If POData is supposed to handle the ordered aging they must return results! (possibly empty)
496
            if (!$this->handlesOrderedPaging() && !$isResultArray) {
497
                throw ODataException::createInternalServerError(
498
                    Messages::queryProviderResultsMissing($methodName, $queryType)
499
                );
500
            }
501
        }
502
503
        if ((QueryType::ENTITIES() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType)
504
            && !$isResultArray) {
505
            throw ODataException::createInternalServerError(
506
                Messages::queryProviderResultsMissing($methodName, $queryType)
507
            );
508
        }
509
    }
510
511
    /**
512
     * Validate the given entity instance.
513
     *
514
     * @param object|null   $entityInstance Entity instance to validate
515
     * @param ResourceSet   &$resourceSet   Resource set to which the entity
516
     *                                      instance belongs to
517
     * @param KeyDescriptor &$keyDescriptor The key descriptor
518
     * @param string        $methodName     Method from which this function
519
     *                                      invoked
520
     *
521
     * @throws ODataException
522
     */
523
    protected function validateEntityInstance(
524
        $entityInstance,
525
        ResourceSet &$resourceSet,
526
        KeyDescriptor &$keyDescriptor,
527
        $methodName
528
    ) {
529
        if (null === $entityInstance) {
530
            throw ODataException::createResourceNotFoundError($resourceSet->getName());
531
        }
532
533
        $resourceType = $this->verifyResourceType($methodName, $entityInstance, $resourceSet);
534
535
        foreach ($keyDescriptor->getValidatedNamedValues() as $keyName => $valueDescription) {
536
            try {
537
                $keyValue = $resourceType->getPropertyValue($entityInstance, $keyName);
538
                if (null === $keyValue) {
539
                    throw ODataException::createInternalServerError(
540
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties($methodName)
541
                    );
542
                }
543
544
                $convertedValue = $valueDescription[1]->convert($valueDescription[0]);
545
                if ($keyValue != $convertedValue) {
546
                    throw ODataException::createInternalServerError(
547
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNonMatchingKeys($methodName)
548
                    );
549
                }
550
            } catch (\ReflectionException $reflectionException) {
551
                // Left blank - we're simply squashing reflection exceptions
552
            }
553
        }
554
    }
555
556
    /**
557
     * @param string $methodName
558
     * @param $entityInstance
559
     * @param ResourceSet $resourceSet
560
     *
561
     * @throws ODataException
562
     *
563
     * @return ResourceEntityType
564
     */
565
    private function verifyResourceType($methodName, $entityInstance, ResourceSet $resourceSet)
566
    {
567
        $resourceType = $resourceSet->getResourceType();
568
        $entityName = $resourceType->getInstanceType()->getName();
569
        if (!($entityInstance instanceof $entityName)) {
570
            throw ODataException::createInternalServerError(
571
                Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
572
                    $entityName,
573
                    $methodName
574
                )
575
            );
576
        }
577
578
        return $resourceType;
579
    }
580
581
    /**
582
     * Start database transaction
583
     *
584
     * @return void
585
     */
586
    public function startTransaction()
587
    {
588
        $this->getQueryProvider()->startTransaction();
589
    }
590
591
    /**
592
     * Commit database transaction
593
     *
594
     * @return void
595
     */
596
    public function commitTransaction()
597
    {
598
        $this->getQueryProvider()->commitTransaction();
599
    }
600
601
    /**
602
     * Abort database transaction
603
     *
604
     * @return void
605
     */
606
    public function rollBackTransaction()
607
    {
608
        $this->getQueryProvider()->rollBackTransaction();
609
    }
610
}
611