Test Setup Failed
Pull Request — master (#102)
by Alex
03:26
created

getRelatedResourceReference()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 8.439
c 0
b 0
f 0
cc 5
eloc 22
nc 2
nop 4
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\ResourceProperty;
9
use POData\Providers\Metadata\ResourceSet;
10
use POData\Providers\Metadata\ResourceType;
11
use POData\Providers\Query\IQueryProvider;
12
use POData\Providers\Query\QueryResult;
13
use POData\Providers\Query\QueryType;
14
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
15
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
16
use POData\UriProcessor\QueryProcessor\SkipTokenParser\InternalSkipTokenInfo;
17
use POData\UriProcessor\QueryProcessor\SkipTokenParser\SkipTokenInfo;
18
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
19
20
class ProvidersQueryWrapper
21
{
22
    /**
23
     * Holds reference to IQueryProvider implementation.
24
     *
25
     * @var IQueryProvider
26
     */
27
    private $queryProvider;
28
29
    /**
30
     * Creates a new instance of ProvidersWrapper.
31
     *
32
     * @param IQueryProvider $query Reference to IQueryProvider implementation
33
     */
34
    public function __construct(IQueryProvider $query)
35
    {
36
        $this->queryProvider = $query;
37
    }
38
39
    public function getQueryProvider()
40
    {
41
        return $this->queryProvider;
42
    }
43
44
    /**
45
     * Get related resource set for a resource.
46
     *
47
     * @param QueryType             $queryType         Indicates if this is a query for a count, entities, or entities
48
     *                                                  with a count
49
     * @param ResourceSet           $sourceResourceSet The entity set containing the source entity
50
     * @param object                $sourceEntity      The source entity instance
51
     * @param ResourceSet           $targetResourceSet The resource set containing the target of the navigation property
52
     * @param ResourceProperty      $targetProperty    The navigation property to retrieve
53
     * @param FilterInfo            $filterInfo        Represents the $filter parameter of the OData query.
54
     *                                                 NULL if no $filter specified
55
     * @param mixed                 $orderBy           sorted order if we want to get the data in some specific order
56
     * @param int                   $top               The top count
57
     * @param int                   $skip              The skip count
58
     * @param SkipTokenInfo|null    $skipToken         The skip token
59
     *
60
     * @throws ODataException
61
     *
62
     * @return QueryResult
63
     */
64
    public function getRelatedResourceSet(
65
        QueryType $queryType,
66
        ResourceSet $sourceResourceSet,
67
        $sourceEntity,
68
        ResourceSet $targetResourceSet,
69
        ResourceProperty $targetProperty,
70
        $filterInfo,
71
        $orderBy,
72
        $top,
73
        $skip,
74
        $skipToken = null
75
    ) {
76
        $queryResult = $this->getQueryProvider()->getRelatedResourceSet(
77
            $queryType,
78
            $sourceResourceSet,
79
            $sourceEntity,
80
            $targetResourceSet,
81
            $targetProperty,
82
            $filterInfo,
83
            $orderBy,
84
            $top,
85
            $skip,
86
            $skipToken
87
        );
88
89
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getRelatedResourceSet');
90
91
        return $queryResult;
92
    }
93
94
    /**
95
     * Gets collection of entities belongs to an entity set.
96
     *
97
     * @param QueryType             $queryType   Indicates if this is a query for a count, entities, or entities with a
98
     *                                           count
99
     * @param ResourceSet           $resourceSet The entity set containing the entities that need to be fetched
100
     * @param FilterInfo            $filterInfo  Represents the $filter parameter of the OData query.
101
     *                                           NULL if no $filter specified
102
     * @param InternalOrderByInfo   $orderBy     The orderBy information
103
     * @param int                   $top         The top count
104
     * @param int                   $skip        The skip count
105
     * @param SkipTokenInfo|null    $skipToken   The skip token
106
     *
107
     * @return QueryResult
108
     */
109
    public function getResourceSet(
110
        QueryType $queryType,
111
        ResourceSet $resourceSet,
112
        FilterInfo $filterInfo = null,
113
        InternalOrderByInfo $orderBy = null,
114
        $top = null,
115
        $skip = null,
116
        SkipTokenInfo $skipToken = null
117
    ) {
118
        $queryResult = $this->getQueryProvider()->getResourceSet(
119
            $queryType,
120
            $resourceSet,
121
            $filterInfo,
122
            $orderBy,
123
            $top,
124
            $skip,
125
            $skipToken
126
        );
127
128
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getResourceSet');
129
130
        return $queryResult;
131
    }
132
133
    /**
134
     * Puts an entity instance to entity set identified by a key.
135
     *
136
     * @param ResourceSet   $resourceSet   The entity set containing the entity to update
137
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
138
     * @param $data
139
     *
140
     * @return bool|null Returns result of executiong query
141
     */
142
    public function putResource(
143
        ResourceSet $resourceSet,
144
        KeyDescriptor $keyDescriptor,
145
        $data
146
    ) {
147
        $queryResult = $this->getQueryProvider()->putResource(
148
            $resourceSet,
149
            $keyDescriptor,
150
            $data
151
        );
152
153
        return $queryResult;
154
    }
155
156
    /**
157
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
158
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
159
     * perform the ordering and paging.
160
     *
161
     * @return bool True if the query provider can handle ordered paging, false if POData should perform the paging
162
     */
163
    public function handlesOrderedPaging()
164
    {
165
        return $this->getQueryProvider()->handlesOrderedPaging();
166
    }
167
168
    /**
169
     * Gets the underlying custom expression provider, the end developer is
170
     * responsible for implementing IExpressionProvider if he choose for.
171
     *
172
     * @throws ODataException
173
     *
174
     * @return IExpressionProvider Instance of IExpressionProvider implementation
175
     */
176
    public function getExpressionProvider()
177
    {
178
        $expressionProvider = $this->getQueryProvider()->getExpressionProvider();
179
        if (is_null($expressionProvider)) {
180
            throw ODataException::createInternalServerError(
181
                Messages::providersWrapperExpressionProviderMustNotBeNullOrEmpty()
182
            );
183
        }
184
185
        if (!$expressionProvider instanceof IExpressionProvider) {
186
            throw ODataException::createInternalServerError(
187
                Messages::providersWrapperInvalidExpressionProviderInstance()
188
            );
189
        }
190
191
        return $expressionProvider;
192
    }
193
194
    /**
195
     * @param ResourceSet $resourceSet          The entity set containing the entity to fetch
196
     * @param object      $sourceEntityInstance The source entity instance
197
     * @param object      $data                 The New data for the entity instance.
198
     *
199
     * returns object|null returns the newly created model if sucessful or null if model creation failed.
200
     */
201
    public function createResourceforResourceSet(
202
        ResourceSet $resourceSet,
203
        $sourceEntityInstance,
204
        $data
205
    ) {
206
        return $this->getQueryProvider()->createResourceforResourceSet(
207
            $resourceSet,
208
            $sourceEntityInstance,
209
            $data
210
        );
211
    }
212
213
    /**
214
     * Delete resource from a resource set.
215
     *
216
     * @param ResourceSet $sourceResourceSet
217
     * @param object      $sourceEntityInstance
218
     *
219
     * return bool true if resources sucessfully deteled, otherwise false.
220
     */
221
    public function deleteResource(
222
        ResourceSet $sourceResourceSet,
223
        $sourceEntityInstance
224
    ) {
225
        return $this->getQueryProvider()->deleteResource(
226
            $sourceResourceSet,
227
            $sourceEntityInstance
228
        );
229
    }
230
231
    /**
232
     * Updates a resource.
233
     *
234
     * @param ResourceSet   $sourceResourceSet    The entity set containing the source entity
235
     * @param object        $sourceEntityInstance The source entity instance
236
     * @param KeyDescriptor $keyDescriptor        The key identifying the entity to fetch
237
     * @param object        $data                 The New data for the entity instance.
238
     * @param bool          $shouldUpdate         Should undefined values be updated or reset to default
239
     *
240
     * @return object|null The new resource value if it is assignable or throw exception for null.
241
     */
242
    public function updateResource(
243
        ResourceSet $sourceResourceSet,
244
        $sourceEntityInstance,
245
        KeyDescriptor $keyDescriptor,
246
        $data,
247
        $shouldUpdate = false
248
    ) {
249
        return $this->getQueryProvider()->updateResource(
250
            $sourceResourceSet,
251
            $sourceEntityInstance,
252
            $keyDescriptor,
253
            $data,
254
            $shouldUpdate
255
        );
256
    }
257
258
    /**
259
     * Get related resource for a resource.
260
     *
261
     * @param ResourceSet      $sourceResourceSet The source resource set
262
     * @param object           $sourceEntity      The source resource
263
     * @param ResourceSet      $targetResourceSet The resource set of the navigation
264
     *                                            property
265
     * @param ResourceProperty $targetProperty    The navigation property to be
266
     *                                            retrieved
267
     *
268
     * @throws ODataException
269
     *
270
     * @return object|null The related resource if exists else null
271
     */
272
    public function getRelatedResourceReference(
273
        ResourceSet $sourceResourceSet,
274
        $sourceEntity,
275
        ResourceSet $targetResourceSet,
276
        ResourceProperty $targetProperty
277
    ) {
278
        $entityInstance = $this->getQueryProvider()->getRelatedResourceReference(
279
            $sourceResourceSet,
280
            $sourceEntity,
281
            $targetResourceSet,
282
            $targetProperty
283
        );
284
285
        // we will not throw error if the resource reference is null
286
        // e.g. Orders(1234)/Customer => Customer can be null, this is
287
        // allowed if Customer is last segment. consider the following:
288
        // Orders(1234)/Customer/Orders => here if Customer is null then
289
        // the UriProcessor will throw error.
290
        if (!is_null($entityInstance)) {
291
            $methodName = 'IQueryProvider::getRelatedResourceReference';
292
293
            $targetResourceType = $this->verifyResourceType($methodName, $entityInstance, $targetResourceSet);
294
            foreach ($targetProperty->getResourceType()->getKeyProperties() as $keyName => $resourceProperty) {
295
                try {
296
                    $keyValue = $targetResourceType->getPropertyValue($entityInstance, $keyName);
297
                    if (is_null($keyValue)) {
298
                        throw ODataException::createInternalServerError(
299
                            Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties(
300
                                'IDSQP::getRelatedResourceReference'
301
                            )
302
                        );
303
                    }
304
                } catch (\ReflectionException $reflectionException) {
305
                    // Left blank - we're simply squashing reflection exceptions
306
                }
307
            }
308
        }
309
310
        return $entityInstance;
311
    }
312
313
    /**
314
     * Gets a related entity instance from an entity set identified by a key.
315
     *
316
     * @param ResourceSet      $sourceResourceSet The entity set related to the entity to be fetched
317
     * @param object           $sourceEntity      The related entity instance
318
     * @param ResourceSet      $targetResourceSet The entity set from which entity needs to be fetched
319
     * @param ResourceProperty $targetProperty    The metadata of the target property
320
     * @param KeyDescriptor    $keyDescriptor     The key to identify the entity to be fetched
321
     *
322
     * @return object|null Returns entity instance if found else null
323
     */
324
    public function getResourceFromRelatedResourceSet(
325
        ResourceSet $sourceResourceSet,
326
        $sourceEntity,
327
        ResourceSet $targetResourceSet,
328
        ResourceProperty $targetProperty,
329
        KeyDescriptor $keyDescriptor
330
    ) {
331
        $entityInstance = $this->getQueryProvider()->getResourceFromRelatedResourceSet(
332
            $sourceResourceSet,
333
            $sourceEntity,
334
            $targetResourceSet,
335
            $targetProperty,
336
            $keyDescriptor
337
        );
338
339
        $this->validateEntityInstance(
340
            $entityInstance,
341
            $targetResourceSet,
342
            $keyDescriptor,
343
            'IQueryProvider::getResourceFromRelatedResourceSet'
344
        );
345
346
        return $entityInstance;
347
    }
348
349
    /**
350
     * Gets an entity instance from an entity set identified by a key.
351
     *
352
     * @param ResourceSet   $resourceSet   The entity set containing the entity to fetch
353
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
354
     *
355
     * @return object|null Returns entity instance if found else null
356
     */
357
    public function getResourceFromResourceSet(ResourceSet $resourceSet, KeyDescriptor $keyDescriptor)
358
    {
359
        $entityInstance = $this->getQueryProvider()->getResourceFromResourceSet($resourceSet, $keyDescriptor);
360
        $this->validateEntityInstance(
361
            $entityInstance,
362
            $resourceSet,
363
            $keyDescriptor,
364
            'IQueryProvider::getResourceFromResourceSet'
365
        );
366
367
        return $entityInstance;
368
    }
369
370
    /**
371
     * @param QueryResult $queryResult
372
     * @param string      $methodName
373
     *
374
     * @throws ODataException
375
     */
376
    private function validateQueryResult($queryResult, QueryType $queryType, $methodName)
377
    {
378
        if (!$queryResult instanceof QueryResult) {
379
            throw ODataException::createInternalServerError(
380
                Messages::queryProviderReturnsNonQueryResult($methodName)
381
            );
382
        }
383
384
        $isResultArray = is_array($queryResult->results);
385
386
        if (QueryType::COUNT() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType) {
387
            //and the provider is supposed to handle the ordered paging they must return a count!
388
            if ($this->handlesOrderedPaging() && !is_numeric($queryResult->count)) {
389
                throw ODataException::createInternalServerError(
390
                    Messages::queryProviderResultCountMissing($methodName, $queryType)
391
                );
392
            }
393
394
            //If POData is supposed to handle the ordered aging they must return results! (possibly empty)
395
            if (!$this->handlesOrderedPaging() && !$isResultArray) {
396
                throw ODataException::createInternalServerError(
397
                    Messages::queryProviderResultsMissing($methodName, $queryType)
398
                );
399
            }
400
        }
401
402
        if ((QueryType::ENTITIES() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType)
403
            && !$isResultArray) {
404
            throw ODataException::createInternalServerError(
405
                Messages::queryProviderResultsMissing($methodName, $queryType)
406
            );
407
        }
408
    }
409
410
    /**
411
     * Validate the given entity instance.
412
     *
413
     * @param object|null   $entityInstance Entity instance to validate
414
     * @param ResourceSet   &$resourceSet   Resource set to which the entity
415
     *                                      instance belongs to
416
     * @param KeyDescriptor &$keyDescriptor The key descriptor
417
     * @param string        $methodName     Method from which this function
418
     *                                      invoked
419
     *
420
     * @throws ODataException
421
     */
422
    private function validateEntityInstance(
423
        $entityInstance,
424
        ResourceSet & $resourceSet,
425
        KeyDescriptor & $keyDescriptor,
426
        $methodName
427
    ) {
428
        if (is_null($entityInstance)) {
429
            throw ODataException::createResourceNotFoundError($resourceSet->getName());
430
        }
431
432
        $resourceType = $this->verifyResourceType($methodName, $entityInstance, $resourceSet);
433
434
        foreach ($keyDescriptor->getValidatedNamedValues() as $keyName => $valueDescription) {
435
            try {
436
                $keyValue = $resourceType->getPropertyValue($entityInstance, $keyName);
437
                if (is_null($keyValue)) {
438
                    throw ODataException::createInternalServerError(
439
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties($methodName)
440
                    );
441
                }
442
443
                $convertedValue = $valueDescription[1]->convert($valueDescription[0]);
444
                if ($keyValue != $convertedValue) {
445
                    throw ODataException::createInternalServerError(
446
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNonMatchingKeys($methodName)
447
                    );
448
                }
449
            } catch (\ReflectionException $reflectionException) {
450
                // Left blank - we're simply squashing reflection exceptions
451
            }
452
        }
453
    }
454
455
    /**
456
     * @param string $methodName
457
     * @param $entityInstance
458
     * @param ResourceSet $resourceSet
459
     *
460
     * @throws ODataException
461
     *
462
     * @return ResourceType
463
     */
464
    private function verifyResourceType($methodName, $entityInstance, ResourceSet $resourceSet)
465
    {
466
        $resourceType = $resourceSet->getResourceType();
467
        $entityName = $resourceType->getInstanceType()->getName();
468
        if (!($entityInstance instanceof $entityName)) {
469
            throw ODataException::createInternalServerError(
470
                Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
471
                    $entityName,
472
                    $methodName
473
                )
474
            );
475
        }
476
477
        return $resourceType;
478
    }
479
}
480