Completed
Pull Request — master (#49)
by Alex
04:02
created

ProvidersQueryWrapper::putResource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 1
eloc 9
nc 1
nop 3
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\ResourcePathProcessor\SegmentParser\KeyDescriptor;
18
19
class ProvidersQueryWrapper
20
{
21
    /**
22
     * Holds reference to IQueryProvider implementation.
23
     *
24
     * @var IQueryProvider
25
     */
26
    private $queryProvider;
27
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
    /**
40
     * Get related resource set for a resource.
41
     *
42
     * @param QueryType        $queryType         Indicates if this is a query for a count, entities, or entities
43
     *                                            with a count
44
     * @param ResourceSet      $sourceResourceSet The entity set containing the source entity
45
     * @param object           $sourceEntity      The source entity instance
46
     * @param ResourceSet      $targetResourceSet The resource set of containing the target of the navigation property
47
     * @param ResourceProperty $targetProperty    The navigation property to retrieve
48
     * @param FilterInfo       $filterInfo        Represents the $filter parameter of the OData query.
49
     *                                            NULL if no $filter specified
50
     * @param mixed            $orderBy           sorted order if we want to get the data in some specific order
51
     * @param int              $top               number of records which  need to be skip
52
     * @param string           $skip              value indicating what records to skip
53
     *
54
     * @return QueryResult
55
     *
56
     * @throws ODataException
57
     */
58
    public function getRelatedResourceSet(
59
        QueryType $queryType,
60
        ResourceSet $sourceResourceSet,
61
        $sourceEntity,
62
        ResourceSet $targetResourceSet,
63
        ResourceProperty $targetProperty,
64
        $filterInfo,
65
        $orderBy,
66
        $top,
67
        $skip
68
    ) {
69
        $queryResult = $this->queryProvider->getRelatedResourceSet(
70
            $queryType,
71
            $sourceResourceSet,
72
            $sourceEntity,
73
            $targetResourceSet,
74
            $targetProperty,
75
            $filterInfo,
76
            $orderBy,
77
            $top,
78
            $skip
79
        );
80
81
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getRelatedResourceSet');
82
83
        return $queryResult;
84
    }
85
86
    /**
87
     * Gets collection of entities belongs to an entity set.
88
     *
89
     * @param QueryType             $queryType   Indicates if this is a query for a count, entities, or entities with a
90
     *                                           count
91
     * @param ResourceSet           $resourceSet The entity set containing the entities that need to be fetched
92
     * @param FilterInfo            $filterInfo  Represents the $filter parameter of the OData query.
93
     *                                           NULL if no $filter specified
94
     * @param InternalOrderByInfo   $orderBy     The orderBy information
95
     * @param int                   $top         The top count
96
     * @param int                   $skip        The skip count
97
     * @param InternalSkipTokenInfo $skipToken   The skip token
98
     *
99
     * @return QueryResult
100
     */
101
    public function getResourceSet(
102
        QueryType $queryType,
103
        ResourceSet $resourceSet,
104
        FilterInfo $filterInfo = null,
105
        InternalOrderByInfo $orderBy = null,
106
        $top = null,
107
        $skip = null,
108
        InternalSkipTokenInfo $skipToken = null
109
    ) {
110
        $queryResult = $this->queryProvider->getResourceSet(
111
            $queryType,
112
            $resourceSet,
113
            $filterInfo,
114
            $orderBy,
115
            $top,
116
            $skip,
117
            $skipToken
0 ignored issues
show
Unused Code introduced by
The call to IQueryProvider::getResourceSet() has too many arguments starting with $skipToken.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
118
        );
119
120
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getResourceSet');
121
122
        return $queryResult;
123
    }
124
125
126
    /**
127
     * Puts an entity instance to entity set identified by a key.
128
     *
129
     * @param ResourceSet   $resourceSet   The entity set containing the entity to update
130
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
131
     * @param $data
132
     *
133
     * @return bool|null Returns result of executiong query
134
     */
135
    public function putResource(
136
        ResourceSet $resourceSet,
137
        KeyDescriptor $keyDescriptor,
138
        $data
139
    ) {
140
        $queryResult = $this->queryProvider->putResource(
141
            $resourceSet,
142
            $keyDescriptor,
143
            $data
144
        );
145
146
        return $queryResult;
147
    }
148
149
    /**
150
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
151
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
152
     * perform the ordering and paging.
153
     *
154
     * @return bool True if the query provider can handle ordered paging, false if POData should perform the paging
155
     */
156
    public function handlesOrderedPaging()
157
    {
158
        return $this->queryProvider->handlesOrderedPaging();
159
    }
160
161
    /**
162
     * Gets the underlying custom expression provider, the end developer is
163
     * responsible for implementing IExpressionProvider if he choose for.
164
     *
165
     * @return IExpressionProvider Instance of IExpressionProvider implementation
166
     *
167
     * @throws ODataException
168
     */
169
    public function getExpressionProvider()
170
    {
171
        $expressionProvider = $this->queryProvider->getExpressionProvider();
172
        if (is_null($expressionProvider)) {
173
            throw ODataException::createInternalServerError(
174
                Messages::providersWrapperExpressionProviderMustNotBeNullOrEmpty()
175
            );
176
        }
177
178
        if (!$expressionProvider instanceof IExpressionProvider) {
179
            throw ODataException::createInternalServerError(
180
                Messages::providersWrapperInvalidExpressionProviderInstance()
181
            );
182
        }
183
184
        return $expressionProvider;
185
    }
186
187
    /**
188
     * @param ResourceSet      $resourceSet   The entity set containing the entity to fetch
189
     * @param object           $sourceEntityInstance The source entity instance
190
     * @param object           $data                 The New data for the entity instance.
191
     *
192
     * returns object|null returns the newly created model if sucessful or null if model creation failed.
193
     */
194
    public function createResourceforResourceSet(
195
        ResourceSet $resourceSet,
196
        $sourceEntityInstance,
197
        $data
198
    ) {
199
        return $this->queryProvider->createResourceforResourceSet(
200
            $resourceSet,
201
            $sourceEntityInstance,
202
            $data
203
        );
204
    }
205
206
    /**
207
     * Delete resource from a resource set.
208
     * @param ResourceSet $sourceResourceSet
209
     * @param object           $sourceEntityInstance
210
     *
211
     * return bool true if resources sucessfully deteled, otherwise false.
212
     */
213
    public function deleteResource(
214
        ResourceSet $sourceResourceSet,
215
        $sourceEntityInstance
216
    ) {
217
        return $this->queryProvider->deleteResource(
218
            $sourceResourceSet,
219
            $sourceEntityInstance
220
        );
221
    }
222
223
    /**
224
     * Updates a resource
225
     *
226
     * @param ResourceSet      $sourceResourceSet    The entity set containing the source entity
227
     * @param object           $sourceEntityInstance The source entity instance
228
     * @param KeyDescriptor    $keyDescriptor        The key identifying the entity to fetch
229
     * @param object           $data                 The New data for the entity instance.
230
     * @param bool             $shouldUpdate        Should undefined values be updated or reset to default
231
     *
232
     * @return object|null The new resource value if it is assignable or throw exception for null.
233
     */
234
    public function updateResource(
235
        ResourceSet $sourceResourceSet,
236
        $sourceEntityInstance,
237
        KeyDescriptor $keyDescriptor,
238
        $data,
239
        $shouldUpdate = false
240
    ) {
241
        return $this->queryProvider->updateResource(
242
            $sourceResourceSet,
243
            $sourceEntityInstance,
244
            $keyDescriptor,
245
            $data,
246
            $shouldUpdate
247
        );
248
    }
249
250
    /**
251
     * Get related resource for a resource.
252
     *
253
     * @param ResourceSet      $sourceResourceSet The source resource set
254
     * @param object           $sourceEntity      The source resource
255
     * @param ResourceSet      $targetResourceSet The resource set of the navigation
256
     *                                            property
257
     * @param ResourceProperty $targetProperty    The navigation property to be
258
     *                                            retrieved
259
     *
260
     * @return object|null The related resource if exists else null
261
     *
262
     * @throws ODataException
263
     */
264
    public function getRelatedResourceReference(
265
        ResourceSet $sourceResourceSet,
266
        $sourceEntity,
267
        ResourceSet $targetResourceSet,
268
        ResourceProperty $targetProperty
269
    ) {
270
        $entityInstance = $this->queryProvider->getRelatedResourceReference(
271
            $sourceResourceSet,
272
            $sourceEntity,
273
            $targetResourceSet,
274
            $targetProperty
275
        );
276
277
        // we will not throw error if the resource reference is null
278
        // e.g. Orders(1234)/Customer => Customer can be null, this is
279
        // allowed if Customer is last segment. consider the following:
280
        // Orders(1234)/Customer/Orders => here if Customer is null then
281
        // the UriProcessor will throw error.
282
        if (!is_null($entityInstance)) {
283
            $methodName = 'IQueryProvider::getRelatedResourceReference';
284
285
            $targetResourceType = $this->verifyResourceType($methodName, $entityInstance, $targetResourceSet);
286
            foreach ($targetProperty->getResourceType()->getKeyProperties() as $keyName => $resourceProperty) {
287
                try {
288
                    $keyValue = $targetResourceType->getPropertyValue($entityInstance, $keyName);
289
                    if (is_null($keyValue)) {
290
                        throw ODataException::createInternalServerError(
291
                            Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties(
292
                                'IDSQP::getRelatedResourceReference'
293
                            )
294
                        );
295
                    }
296
                } catch (\ReflectionException $reflectionException) {
297
                    // Left blank - we're simply squashing reflection exceptions
298
                }
299
            }
300
        }
301
302
        return $entityInstance;
303
    }
304
305
    /**
306
     * Gets a related entity instance from an entity set identified by a key.
307
     *
308
     * @param ResourceSet      $sourceResourceSet The entity set related to the entity to be fetched
309
     * @param object           $sourceEntity      The related entity instance
310
     * @param ResourceSet      $targetResourceSet The entity set from which entity needs to be fetched
311
     * @param ResourceProperty $targetProperty    The metadata of the target property
312
     * @param KeyDescriptor    $keyDescriptor     The key to identify the entity to be fetched
313
     *
314
     * @return object|null Returns entity instance if found else null
315
     */
316
    public function getResourceFromRelatedResourceSet(
317
        ResourceSet $sourceResourceSet,
318
        $sourceEntity,
319
        ResourceSet $targetResourceSet,
320
        ResourceProperty $targetProperty,
321
        KeyDescriptor $keyDescriptor
322
    ) {
323
        $entityInstance = $this->queryProvider->getResourceFromRelatedResourceSet(
324
            $sourceResourceSet,
325
            $sourceEntity,
326
            $targetResourceSet,
327
            $targetProperty,
328
            $keyDescriptor
329
        );
330
331
        $this->validateEntityInstance(
332
            $entityInstance,
333
            $targetResourceSet,
334
            $keyDescriptor,
335
            'IQueryProvider::getResourceFromRelatedResourceSet'
336
        );
337
338
        return $entityInstance;
339
    }
340
341
    /**
342
     * Gets an entity instance from an entity set identified by a key.
343
     *
344
     * @param ResourceSet   $resourceSet   The entity set containing the entity to fetch
345
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
346
     *
347
     * @return object|null Returns entity instance if found else null
348
     */
349
    public function getResourceFromResourceSet(ResourceSet $resourceSet, KeyDescriptor $keyDescriptor)
350
    {
351
        $entityInstance = $this->queryProvider->getResourceFromResourceSet($resourceSet, $keyDescriptor);
352
        $this->validateEntityInstance(
353
            $entityInstance,
354
            $resourceSet,
355
            $keyDescriptor,
356
            'IQueryProvider::getResourceFromResourceSet'
357
        );
358
359
        return $entityInstance;
360
    }
361
362
    /**
363
     * @param QueryResult $queryResult
364
     * @param string      $methodName
365
     *
366
     * @throws ODataException
367
     */
368
    private function validateQueryResult($queryResult, QueryType $queryType, $methodName)
369
    {
370
        if (!$queryResult instanceof QueryResult) {
371
            throw ODataException::createInternalServerError(
372
                Messages::queryProviderReturnsNonQueryResult($methodName)
373
            );
374
        }
375
376
        $isResultArray = is_array($queryResult->results);
377
378
        if (QueryType::COUNT() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType) {
379
            //and the provider is supposed to handle the ordered paging they must return a count!
380
            if ($this->handlesOrderedPaging() && !is_numeric($queryResult->count)) {
381
                throw ODataException::createInternalServerError(
382
                    Messages::queryProviderResultCountMissing($methodName, $queryType)
383
                );
384
            }
385
386
            //If POData is supposed to handle the ordered aging they must return results! (possibly empty)
387
            if (!$this->handlesOrderedPaging() && !$isResultArray) {
388
                throw ODataException::createInternalServerError(
389
                    Messages::queryProviderResultsMissing($methodName, $queryType)
390
                );
391
            }
392
        }
393
394
        if ((QueryType::ENTITIES() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType)
395
            && !$isResultArray) {
396
            throw ODataException::createInternalServerError(
397
                Messages::queryProviderResultsMissing($methodName, $queryType)
398
            );
399
        }
400
    }
401
402
    /**
403
     * Validate the given entity instance.
404
     *
405
     * @param object|null   $entityInstance Entity instance to validate
406
     * @param ResourceSet   &$resourceSet   Resource set to which the entity
407
     *                                      instance belongs to
408
     * @param KeyDescriptor &$keyDescriptor The key descriptor
409
     * @param string        $methodName     Method from which this function
410
     *                                      invoked
411
     *
412
     * @throws ODataException
413
     */
414
    private function validateEntityInstance(
415
        $entityInstance,
416
        ResourceSet & $resourceSet,
417
        KeyDescriptor & $keyDescriptor,
418
        $methodName
419
    ) {
420
        if (is_null($entityInstance)) {
421
            throw ODataException::createResourceNotFoundError($resourceSet->getName());
422
        }
423
424
        $resourceType = $this->verifyResourceType($methodName, $entityInstance, $resourceSet);
425
426
        foreach ($keyDescriptor->getValidatedNamedValues() as $keyName => $valueDescription) {
427
            try {
428
                $keyValue = $resourceType->getPropertyValue($entityInstance, $keyName);
429
                if (is_null($keyValue)) {
430
                    throw ODataException::createInternalServerError(
431
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties($methodName)
432
                    );
433
                }
434
435
                $convertedValue = $valueDescription[1]->convert($valueDescription[0]);
436
                if ($keyValue != $convertedValue) {
437
                    throw ODataException::createInternalServerError(
438
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNonMatchingKeys($methodName)
439
                    );
440
                }
441
            } catch (\ReflectionException $reflectionException) {
442
                // Left blank - we're simply squashing reflection exceptions
443
            }
444
        }
445
    }
446
447
    /**
448
     * @param $methodName
449
     * @param $entityInstance
450
     * @param ResourceSet $resourceSet
451
     * @return ResourceType
452
     *
453
     * @throws ODataException
454
     */
455
    private function verifyResourceType($methodName, $entityInstance, ResourceSet $resourceSet)
456
    {
457
        $resourceType = $resourceSet->getResourceType();
458
        $entityName = $resourceType->getInstanceType()->getName();
459
        if (!($entityInstance instanceof $entityName)) {
460
            throw ODataException::createInternalServerError(
461
                Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
462
                    $entityName,
463
                    $methodName
464
                )
465
            );
466
        }
467
        return $resourceType;
468
    }
469
}
470