ProvidersWrapper::getResourceSet()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 27
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 27
ccs 14
cts 14
cp 1
rs 9.8666
cc 1
nc 1
nop 9
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace POData\Providers;
4
5
use POData\Providers\Metadata\ResourceTypeKind;
6
use POData\Providers\Metadata\ResourceSetWrapper;
7
use POData\Providers\Metadata\ResourceType;
8
use POData\Providers\Metadata\ResourceProperty;
9
use POData\Providers\Metadata\ResourceSet;
10
use POData\Providers\Metadata\ResourceAssociationSet;
11
use POData\Configuration\ServiceConfiguration;
12
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
13
use POData\Common\ODataException;
14
use POData\Common\Messages;
15
use POData\Providers\Metadata\EdmSchemaVersion;
16
use POData\Providers\Query\IQueryProvider;
17
use POData\Providers\Metadata\IMetadataProvider;
18
use POData\Providers\Expression\IExpressionProvider;
19
use POData\Common\InvalidOperationException;
20
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
21
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
22
use POData\Providers\Query\QueryResult;
23
use POData\Providers\Query\QueryType;
24
use POData\UriProcessor\QueryProcessor\SkipTokenParser\SkipTokenInfo;
25
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\ExpandedProjectionNode;
26
use POData\UriProcessor\RequestDescription;
27
28
/**
29
 * Class ProvidersWrapper
30
 *
31
 * A wrapper class over IMetadataProvider and IQueryProvider implementations, All calls to implementation of methods
32
 * of these interfaces should go through this wrapper class so that wrapper methods of this class can perform validation
33
 *
34
 * @package POData\Providers
35
 */
36
class ProvidersWrapper
37
{
38
    /**
39
     * Holds reference to IMetadataProvider implementation
40
     *
41
     * @var IMetadataProvider
42
     */
43
    private $metaProvider;
44
45
    /**
46
     * Holds reference to IQueryProvider implementation
47
     *
48
     * @var IQueryProvider
49
     *
50
     */
51
    private $queryProvider;
52
53
    /**
54
     * Holds reference to IServiceConfiguration implementation
55
     *
56
     * @var ServiceConfiguration
57
     */
58
    private $config;
59
60
61
    /**
62
     * Cache for ResourceProperties of a resource type that belongs to a
63
     * resource set. An entry (ResourceProperty collection) in this cache
64
     * contains only the visible properties of ResourceType.
65
     *
66
     * @var array(string, array(string, ResourceProperty))
67
     */
68
    private $propertyCache;
69
70
    /**
71
     * Cache for ResourceSetWrappers. If ResourceSet is invisible value will
72
     * be null.
73
     *
74
     * @var ResourceSetWrapper[] indexed by resource set name
75
     */
76
    private $setWrapperCache;
77
78
    /**
79
     * Cache for ResourceTypes
80
     *
81
     * @var ResourceType[] indexed by resource type name
82
     */
83
    private $typeCache;
84
85
    /**
86
     * Cache for ResourceAssociationSet. If ResourceAssociationSet is invisible
87
     * value will be null.
88
     *
89
     * @var ResourceAssociationSet[] indexed by name
90
     */
91
    private $associationSetCache;
92
93
    /**
94
     * Creates a new instance of ProvidersWrapper
95
     *
96
     * @param IMetadataProvider $metadataProvider Reference to IMetadataProvider implementation
97
     * @param IQueryProvider    $queryProvider    Reference to IQueryProvider implementation
98
     * @param ServiceConfiguration    $configuration    Reference to IServiceConfiguration implementation
99
     */
100 171
    public function __construct(IMetadataProvider $metadataProvider, IQueryProvider $queryProvider, ServiceConfiguration $configuration)
101
    {
102 171
        $this->metaProvider = $metadataProvider;
103 171
        $this->queryProvider = $queryProvider;
104 171
        $this->config = $configuration;
105 171
        $this->setWrapperCache = array();
106 171
        $this->typeCache = array();
107 171
        $this->associationSetCache = array();
108 171
        $this->propertyCache = array();
109
    }
110
111
    //Wrappers for IMetadataProvider methods
112
113
    /**
114
     * To get the Container name for the data source,
115
     * Note: Wrapper for IMetadataProvider::getContainerName method
116
     * implementation
117
     *
118
     * @return string that contains the name of the container
119
     *
120
     * @throws ODataException Exception if implementation returns empty container name
121
     *
122
     */
123 4
    public function getContainerName()
124
    {
125 4
        $containerName = $this->metaProvider->getContainerName();
126 4
        if (empty($containerName)) {
127 2
            throw new ODataException(
128 2
                Messages::providersWrapperContainerNameMustNotBeNullOrEmpty(),
129 2
                500
130 2
            );
131
        }
132
133 2
        return $containerName;
134
    }
135
136
    /**
137
     * To get Namespace name for the data source,
138
     * Note: Wrapper for IMetadataProvider::getContainerNamespace method implementation
139
     *
140
     * @return string that contains the namespace name.
141
     *
142
     * @throws ODataException Exception if implementation returns empty container namespace
143
     *
144
     */
145 4
    public function getContainerNamespace()
146
    {
147 4
        $containerNamespace = $this->metaProvider->getContainerNamespace();
148 4
        if (empty($containerNamespace)) {
149 2
            throw new ODataException(
150 2
                Messages::providersWrapperContainerNamespaceMustNotBeNullOrEmpty(),
151 2
                500
152 2
            );
153
        }
154
155 2
        return $containerNamespace;
156
    }
157
158
    /**
159
     * To get the data service configuration
160
     *
161
     * @return ServiceConfiguration
162
     */
163 18
    public function getConfiguration()
164
    {
165 18
        return $this->config;
166
    }
167
168
    /**
169
     *  To get all entity set information,
170
     *  Note: Wrapper for IMetadataProvider::getResourceSets method implementation,
171
     *  This method returns array of ResourceSetWrapper instances but the corresponding IDSMP method returns array of ResourceSet instances
172
     *
173
     *  @return ResourceSetWrapper[] The ResourceSetWrappers for the visible ResourceSets
174
     *  @throws ODataException when two resource sets with the same name are encountered
175
     *
176
     */
177 4
    public function getResourceSets()
178
    {
179 4
        $resourceSets = $this->metaProvider->getResourceSets();
180 4
        $resourceSetWrappers = array();
181 4
        $resourceSetNames = array();
182 4
        foreach ($resourceSets as $resourceSet) {
183 4
            $name = $resourceSet->getName();
184 4
            if (in_array($name, $resourceSetNames)) {
185 1
                throw new ODataException(Messages::providersWrapperEntitySetNameShouldBeUnique($name), 500);
186
            }
187
188 4
            $resourceSetNames[] = $name;
189 4
            $resourceSetWrapper = $this->_validateResourceSetAndGetWrapper($resourceSet);
190 4
            if (!is_null($resourceSetWrapper)) {
191 4
                $resourceSetWrappers[] = $resourceSetWrapper;
192
            }
193
        }
194
195 3
        return $resourceSetWrappers;
196
    }
197
198
    /**
199
     * To get all resource types in the data source,
200
     * Note: Wrapper for IMetadataProvider::getTypes method implementation
201
     *
202
     * @return ResourceType[]
203
     */
204 2
    public function getTypes()
205
    {
206 2
        $resourceTypes = $this->metaProvider->getTypes();
207 2
        $resourceTypeNames = array();
208 2
        foreach ($resourceTypes as $resourceType) {
209 2
            if (in_array($resourceType->getName(), $resourceTypeNames)) {
210 1
                throw new ODataException(
211 1
                    Messages::providersWrapperEntityTypeNameShouldBeUnique($resourceType->getName()),
212 1
                    500
213 1
                );
214
            }
215
216 2
            $resourceTypeNames[] = $resourceType->getName();
217 2
            $this->_validateResourceType($resourceType);
218
        }
219
220 1
        return $resourceTypes;
221
    }
222
223
    /**
224
     * To get a resource set based on the specified resource set name which is
225
     * visible,
226
     * Note: Wrapper for IMetadataProvider::resolveResourceSet method
227
     * implementation
228
     *
229
     * @param string $name Name of the resource set
230
     *
231
     * @return ResourceSetWrapper|null Returns resource set with the given name if found, NULL if resource set is set to invisible or not found
232
     *
233
     */
234 133
    public function resolveResourceSet($name)
235
    {
236 133
        if (array_key_exists($name, $this->setWrapperCache)) {
237 11
            return $this->setWrapperCache[$name];
238
        }
239
240 132
        $resourceSet = $this->metaProvider->resolveResourceSet($name);
241 132
        if (is_null($resourceSet)) {
242 2
            return null;
243
        }
244
245 131
        return $this->_validateResourceSetAndGetWrapper($resourceSet);
246
    }
247
248
    /**
249
     * To get a resource type based on the resource set name,
250
     * Note: Wrapper for IMetadataProvider::resolveResourceType
251
     * method implementation
252
     *
253
     * @param string $name Name of the resource set
254
     *
255
     * @return ResourceType|null resource type with the given resource set name if found else NULL
256
     *
257
     *
258
     * @throws ODataException If the ResourceType is invalid
259
     */
260 3
    public function resolveResourceType($name)
261
    {
262 3
        $resourceType = $this->metaProvider->resolveResourceType($name);
263 3
        if (is_null($resourceType)) {
264 1
            return null;
265
        }
266
267 2
        return $this->_validateResourceType($resourceType);
268
    }
269
270
    public function resolveResourceTypeByClassname($classname)
271
    {
272
        $resourceType = $this->metaProvider->resolveResourceTypeByClassname($classname);
0 ignored issues
show
Bug introduced by
The method resolveResourceTypeByClassname() does not exist on POData\Providers\Metadata\IMetadataProvider. Did you maybe mean resolveResourceType()? ( Ignorable by Annotation )

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

272
        /** @scrutinizer ignore-call */ 
273
        $resourceType = $this->metaProvider->resolveResourceTypeByClassname($classname);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
273
        if (is_null($resourceType)) {
274
            return null;
275
        }
276
277
        return $this->_validateResourceType($resourceType);
278
    }
279
280
    /**
281
     * The method must return a collection of all the types derived from
282
     * $resourceType The collection returned should NOT include the type
283
     * passed in as a parameter
284
     * Note: Wrapper for IMetadataProvider::getDerivedTypes
285
     * method implementation
286
     *
287
     * @param ResourceType $resourceType Resource to get derived resource types from
288
     *
289
     * @return ResourceType[]
290
     *
291
     * @throws InvalidOperationException when the meat provider doesn't return an array
292
     */
293 73
    public function getDerivedTypes(ResourceType $resourceType)
294
    {
295 73
        $derivedTypes = $this->metaProvider->getDerivedTypes($resourceType);
296 73
        if (!is_array($derivedTypes)) {
0 ignored issues
show
introduced by
The condition is_array($derivedTypes) is always true.
Loading history...
297 1
            throw new InvalidOperationException(Messages::metadataAssociationTypeSetInvalidGetDerivedTypesReturnType($resourceType->getName()));
298
        }
299
300 72
        foreach ($derivedTypes as $derivedType) {
301 1
            $this->_validateResourceType($derivedType);
302
        }
303
304 72
        return $derivedTypes;
305
    }
306
307
    /**
308
     * Returns true if $resourceType represents an Entity Type which has derived
309
     * Entity Types, else false.
310
     * Note: Wrapper for IMetadataProvider::hasDerivedTypes method
311
     * implementation
312
     *
313
     * @param ResourceType $resourceType Resource to check for derived resource
314
     *                                   types.
315
     *
316
     * @return boolean
317
     *
318
     * @throws ODataException If the ResourceType is invalid
319
     */
320 1
    public function hasDerivedTypes(ResourceType $resourceType)
321
    {
322 1
        $this->_validateResourceType($resourceType);
323 1
        return $this->metaProvider->hasDerivedTypes($resourceType);
324
    }
325
326
    /**
327
     * Gets the ResourceAssociationSet instance for the given source association end,
328
     * Note: Wrapper for IMetadataProvider::getResourceAssociationSet
329
     * method implementation
330
     *
331
     * @param ResourceSet $set Resource set of the source association end
332
     * @param ResourceType       $type       Resource type of the source association end
333
     * @param ResourceProperty   $property   Resource property of the source association end
334
     *
335
     *
336
     * @return ResourceAssociationSet|null Returns ResourceAssociationSet for the source
337
     *                                             association end, NULL if no such
338
     *                                             association end or resource set in the
339
     *                                             other end of the association is invisible
340
     */
341 48
    public function getResourceAssociationSet(
342
        ResourceSet $set,
343
        ResourceType $type,
344
        ResourceProperty $property
345
    ) {
346 48
        $type = $this->_getResourceTypeWherePropertyIsDeclared($type, $property);
347 48
        $cacheKey = $set->getName() . '_' . $type->getName() . '_' . $property->getName();
348
349 48
        if (array_key_exists($cacheKey, $this->associationSetCache)) {
350 11
            return $this->associationSetCache[$cacheKey];
351
        }
352
353 48
        $associationSet = $this->metaProvider->getResourceAssociationSet(
354 48
            $set,
355 48
            $type,
356 48
            $property
357 48
        );
358
359 48
        if (!is_null($associationSet)) {
360 48
            $thisAssociationSetEnd = $associationSet->getResourceAssociationSetEnd(
361 48
                $set,
362 48
                $type,
363 48
                $property
364 48
            );
365
366 48
            $relatedAssociationSetEnd = $associationSet->getRelatedResourceAssociationSetEnd(
367 48
                $set,
368 48
                $type,
369 48
                $property
370 48
            );
371
372
            //If $thisAssociationSetEnd or $relatedAssociationSetEnd
373
            //is null means the associationset
374
            //we got from the IDSMP::getResourceAssociationSet is invalid.
375
            //AssociationSet::getResourceAssociationSetEnd
376
            //return null, if AssociationSet's End1 or End2's resourceset name
377
            //is not matching with the name of
378
            //resource set wrapper (param1) and resource type is not assignable
379
            //from given resource type (param2)
380 48
            if (is_null($thisAssociationSetEnd) || is_null($relatedAssociationSetEnd)) {
381
                throw new ODataException(
382
                    Messages::providersWrapperIDSMPGetResourceSetReturnsInvalidResourceSet(
383
                        $set->getName(),
384
                        $type->getFullName(),
385
                        $property->getName()
386
                    ),
387
                    500
388
                );
389
            }
390
391 48
            $relatedResourceSetWrapper = $this->_validateResourceSetAndGetWrapper(
392 48
                $relatedAssociationSetEnd->getResourceSet()
393 48
            );
394 48
            if ($relatedResourceSetWrapper === null) {
395 4
                $associationSet = null;
396
            } else {
397 46
                $this->_validateResourceType($thisAssociationSetEnd->getResourceType());
398 46
                $this->_validateResourceType($relatedAssociationSetEnd->getResourceType());
399
            }
400
        }
401
402 48
        $this->associationSetCache[$cacheKey] = $associationSet;
403 48
        return $associationSet;
404
    }
405
406
    /**
407
     * Gets the target resource set wrapper for the given navigation property,
408
     * source resource set wrapper and the source resource type
409
     *
410
     * @param ResourceSetWrapper $resourceSetWrapper         Source resource set.
411
     * @param ResourceType       $resourceType               Source resource type.
412
     * @param ResourceProperty   $navigationResourceProperty Navigation property.
413
     *
414
     * @return ResourceSetWrapper|null Returns instance of ResourceSetWrapper
415
     *     (describes the entity set and associated configuration) for the
416
     *     given navigation property. returns NULL if resourceset for the
417
     *     navigation property is invisible or if metadata provider returns
418
     *     null resource association set
419
     */
420 45
    public function getResourceSetWrapperForNavigationProperty(
421
        ResourceSetWrapper $resourceSetWrapper,
422
        ResourceType $resourceType,
423
        ResourceProperty $navigationResourceProperty
424
    ) {
425 45
        $associationSet = $this->getResourceAssociationSet(
426 45
            $resourceSetWrapper,
427 45
            $resourceType,
428 45
            $navigationResourceProperty
429 45
        );
430
431 45
        if (!is_null($associationSet)) {
432 44
            $relatedAssociationSetEnd = $associationSet->getRelatedResourceAssociationSetEnd(
433 44
                $resourceSetWrapper->getResourceSet(),
434 44
                $resourceType,
435 44
                $navigationResourceProperty
436 44
            );
437 44
            return $this->_validateResourceSetAndGetWrapper(
438 44
                $relatedAssociationSetEnd->getResourceSet()
439 44
            );
440
        }
441
442 3
        return null;
443
    }
444
445
    /**
446
     * Gets the visible resource properties for the given resource type from the given resource set wrapper.
447
     *
448
     * @param ResourceSetWrapper $setWrapper Resource set wrapper in question.
449
     * @param ResourceType       $resourceType       Resource type in question.
450
     * @return ResourceProperty[] Collection of visible resource properties from the given resource set wrapper and resource type.
451
     */
452
    public function getResourceProperties(ResourceSetWrapper $setWrapper, ResourceType $resourceType) {
453
        if ($resourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY) {
0 ignored issues
show
introduced by
The condition $resourceType->getResour...esourceTypeKind::ENTITY is always true.
Loading history...
454
            //Complex resource type
455
            return $resourceType->getAllProperties();
456
        }
457
        //TODO: move this to doctrine annotations
458
        $cacheKey = $setWrapper->getName() . '_' . $resourceType->getFullName();
459
        if (!array_key_exists($cacheKey, $this->propertyCache)) {
460
            //Fill the cache
461
            $this->propertyCache[$cacheKey] = array();
462
            foreach ($resourceType->getAllProperties() as $resourceProperty) {
463
                //Check whether this is a visible navigation property
464
                //TODO: is this broken?? see #87
465
                if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY
466
                    && !is_null($this->getResourceSetWrapperForNavigationProperty($setWrapper, $resourceType, $resourceProperty))
467
                ) {
468
                    $this->propertyCache[$cacheKey][$resourceProperty->getName()] = $resourceProperty;
469
                } else {
470
                    //primitive, bag or complex property
471
                    $this->propertyCache[$cacheKey][$resourceProperty->getName()] = $resourceProperty;
472
                }
473
            }
474
        }
475
        return $this->propertyCache[$cacheKey];
476
477
    }
478
479
    /**
480
     * Wrapper function over _validateResourceSetAndGetWrapper function
481
     *
482
     * @param ResourceSet $resourceSet see the comments of _validateResourceSetAndGetWrapper
483
     *
484
     * @return ResourceSetWrapper|null see the comments of _validateResourceSetAndGetWrapper
485
     */
486
    public function validateResourceSetAndGetWrapper(ResourceSet $resourceSet)
487
    {
488
        return $this->_validateResourceSetAndGetWrapper($resourceSet);
489
    }
490
491
    /**
492
     * Gets the Edm Schema version compliance to the metadata
493
     *
494
     * @return EdmSchemaVersion
495
     */
496 1
    public function getEdmSchemaVersion()
497
    {
498
        //The minimal schema version for custom provider is 1.1
499 1
        return EdmSchemaVersion::VERSION_1_DOT_1;
0 ignored issues
show
Bug Best Practice introduced by
The expression return POData\Providers\...ersion::VERSION_1_DOT_1 returns the type double which is incompatible with the documented return type POData\Providers\Metadata\EdmSchemaVersion.
Loading history...
500
    }
501
502
    /**
503
     * This function perform the following operations
504
     *  (1) If the cache contain an entry [key, value] for the resourceset then
505
     *      return the entry-value
506
     *  (2) If the cache not contain an entry for the resourceset then validate
507
     *      the resourceset
508
     *            (a) If valid add entry as [resouceset_name, resourceSetWrapper]
509
     *            (b) if not valid add entry as [resouceset_name, null]
510
     *  Note: validating a resourceset means checking the resourceset is visible
511
     *  or not using configuration
512
     *
513
     * @param ResourceSet $resourceSet The resourceset to validate and get the
514
     *                                 wrapper for
515
     *
516
     * @return ResourceSetWrapper|null Returns an instance if a resource set with the given name is visible
517
     */
518 137
    private function _validateResourceSetAndGetWrapper(ResourceSet $resourceSet)
519
    {
520 137
        $cacheKey = $resourceSet->getName();
521 137
        if (array_key_exists($cacheKey, $this->setWrapperCache)) {
522 46
            return $this->setWrapperCache[$cacheKey];
523
        }
524
525 137
        $this->_validateResourceType($resourceSet->getResourceType());
526 137
        $wrapper = new ResourceSetWrapper($resourceSet, $this->config);
527 137
        if ($wrapper->isVisible()) {
528 135
            $this->setWrapperCache[$cacheKey] = $wrapper;
529
        } else {
530 7
            $this->setWrapperCache[$cacheKey] = null;
531
        }
532
533 137
        return $this->setWrapperCache[$cacheKey];
534
    }
535
536
    /**
537
     * Validates the given instance of ResourceType
538
     *
539
     * @param ResourceType $resourceType The ResourceType to validate
540
     *
541
     * @return ResourceType
542
     *
543
     * @throws ODataException Exception if $resourceType is invalid
544
     */
545 142
    private function _validateResourceType(ResourceType $resourceType)
546
    {
547 142
        $cacheKey = $resourceType->getName();
548 142
        if (array_key_exists($cacheKey, $this->typeCache)) {
549 47
            return $this->typeCache[$cacheKey];
550
        }
551
552
        //TODO: Do validation if any for the ResourceType
553 142
        $this->typeCache[$cacheKey] = $resourceType;
554 142
        return $resourceType;
555
    }
556
557
    /**
558
     * Gets the resource type on which the resource property is declared on,
559
     * If property is not declared in the given resource type, then this
560
     * function drill down to the inheritance hierarchy of the given resource
561
     * type to find out the base class in which the property is declared
562
     *
563
     * @param ResourceType     $resourceType     The resource type to start looking
564
     * @param ResourceProperty $resourceProperty The resource property in question
565
     *
566
     * @return ResourceType|null Returns reference to the ResourceType on which
567
     *                                   the $resourceProperty is declared, NULL if
568
     *                                   $resourceProperty is not declared anywhere
569
     *                                   in the inheritance hierarchy
570
     */
571 48
    private function _getResourceTypeWherePropertyIsDeclared(ResourceType $resourceType,
572
        ResourceProperty $resourceProperty
573
    ) {
574 48
        $type = $resourceType;
575 48
        while ($type !== null) {
576 48
            if ($type->resolvePropertyDeclaredOnThisType($resourceProperty->getName()) !== null) {
577 48
                break;
578
            }
579
580
            $type = $type->getBaseType();
581
        }
582
583 48
        return $type;
584
    }
585
586
    /**
587
     * Gets the underlying custom expression provider, the end developer is
588
     * responsible for implementing IExpressionProvider if he choose for
589
     *
590
     * @return IExpressionProvider Instance of IExpressionProvider implementation.
591
     *
592
     */
593 44
    public function getExpressionProvider(RequestDescription $request)
594
    {
595 44
        $expressionProvider = $this->queryProvider->getExpressionProvider($request);
596 44
        if (is_null($expressionProvider)) {
597
            throw ODataException::createInternalServerError(Messages::providersWrapperExpressionProviderMustNotBeNullOrEmpty());
598
        }
599
600 44
        if (!$expressionProvider instanceof IExpressionProvider)
0 ignored issues
show
introduced by
$expressionProvider is always a sub-type of POData\Providers\Expression\IExpressionProvider.
Loading history...
601
        {
602
            throw ODataException::createInternalServerError(Messages::providersWrapperInvalidExpressionProviderInstance());
603
        }
604
605 44
        return $expressionProvider;
606
    }
607
608
    /**
609
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
610
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
611
     * perform the ordering and paging
612
     *
613
     * @return Boolean True if the query provider can handle ordered paging, false if POData should perform the paging
614
     */
615
    public function handlesOrderedPaging()
616
    {
617
        return $this->queryProvider->handlesOrderedPaging();
618
    }
619
620
621
    /**
622
     * @param QueryResult $queryResult
623
     * @param string $methodName
624
     */
625 16
    private function ValidateQueryResult($queryResult, $queryType, $methodName) {
626 16
        if (!$queryResult instanceof QueryResult) {
0 ignored issues
show
introduced by
$queryResult is always a sub-type of POData\Providers\Query\QueryResult.
Loading history...
627 2
            throw ODataException::createInternalServerError(
628 2
                Messages::queryProviderReturnsNonQueryResult($methodName)
629 2
            );
630
        }
631
632 14
        if ($queryType == QueryType::COUNT || $queryType == QueryType::ENTITIES_WITH_COUNT) {
633
            //and the provider is supposed to handle the ordered paging they must return a count!
634 10
            if ($this->queryProvider->handlesOrderedPaging() && !is_numeric($queryResult->count)) {
635 4
                throw ODataException::createInternalServerError(
636 4
                    Messages::queryProviderResultCountMissing($methodName, $queryType)
637 4
                );
638
            }
639
640
            //If POData is supposed to handle the ordered aging they must return results! (possibly empty)
641 6
            if (!$this->queryProvider->handlesOrderedPaging() && !is_iterable($queryResult->results)) {
642 6
                throw ODataException::createInternalServerError(
643 6
                    Messages::queryProviderResultsMissing($methodName, $queryType)
644 6
                );
645
            }
646
        }
647
648 4
        if (($queryType == QueryType::ENTITIES || $queryType == QueryType::ENTITIES_WITH_COUNT) && !is_iterable($queryResult->results)) {
649 2
            throw ODataException::createInternalServerError(
650 2
                Messages::queryProviderResultsMissing($methodName, $queryType)
651 2
            );
652
        }
653
    }
654
655
    /**
656
     * Gets collection of entities belongs to an entity set
657
     *
658
     * @param string $queryType indicates if this is a query for a count, entities, or entities with a count
659
     * @param ResourceSet $resourceSet The entity set containing the entities that need to be fetched
660
     * @param FilterInfo|null $filterInfo represents the $filter parameter of the OData query.  NULL if no $filter specified
661
     * @param InternalOrderByInfo|null $orderBy The orderBy information
662
     * @param int|null $top The top count
663
     * @param int|null $skip The skip count
664
     * @param SkipTokenInfo|null $skiptoken
665
     * @param ExpandedProjectionNode[] $expansion
666
     *
667
     * @return QueryResult
668
     */
669 8
    public function getResourceSet(
670
        string $queryType,
671
        ResourceSet $resourceSet,
672
        RequestDescription $request,
673
        FilterInfo $filterInfo = null,
674
        InternalOrderByInfo $orderBy = null,
675
        int $top = null,
676
        int $skip = null,
677
        SkipTokenInfo $skiptoken = null,
678
        array $expansion = []
679
    ) {
680
681 8
        $queryResult = $this->queryProvider->getResourceSet(
682 8
            $queryType,
683 8
            $resourceSet,
684 8
            $request,
685 8
            $filterInfo,
686 8
            $orderBy,
687 8
            $top,
688 8
            $skip,
689 8
            $skiptoken,
690 8
            $expansion
691 8
        );
692
693 8
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getResourceSet');
694
695 1
        return $queryResult;
696
    }
697
698
699
700
    /**
701
     * Gets an entity instance from an entity set identified by a key
702
     *
703
     * @param ResourceSet $resourceSet The entity set containing the entity to fetch
704
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
705
     *
706
     * @return object|null Returns entity instance if found else null
707
     */
708
    public function getResourceFromResourceSet(ResourceSet $resourceSet, RequestDescription $request, KeyDescriptor $keyDescriptor, $expand)
709
    {
710
        $entityInstance = $this->queryProvider->getResourceFromResourceSet($resourceSet, $request, $keyDescriptor, $expand);
0 ignored issues
show
Unused Code introduced by
The call to POData\Providers\Query\I...sourceFromResourceSet() has too many arguments starting with $expand. ( Ignorable by Annotation )

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

710
        /** @scrutinizer ignore-call */ 
711
        $entityInstance = $this->queryProvider->getResourceFromResourceSet($resourceSet, $request, $keyDescriptor, $expand);

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. Please note the @ignore annotation hint above.

Loading history...
711
        $this->_validateEntityInstance(
712
            $entityInstance,
713
            $resourceSet,
714
            $keyDescriptor,
715
            'IQueryProvider::getResourceFromResourceSet'
716
        );
717
        return $entityInstance;
718
    }
719
720
    /**
721
     * Puts an entity instance to entity set identified by a key
722
     *
723
     * @param ResourceSet $resourceSet The entity set containing the entity to update
724
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
725
     *
726
     * @return bool|null Returns result of executiong query
727
     */
728
    public function putResource(
729
        ResourceSet $resourceSet,
730
        RequestDescription $request,
731
        KeyDescriptor $keyDescriptor,
732
        $data,
733
        $filter,
734
        $expand
735
    ) {
736
        $queryResult = $this->queryProvider->putResource(
737
            $resourceSet,
738
            $request,
739
            $keyDescriptor,
740
            $data,
741
            $filter,
742
            $expand
743
        );
744
745
        return $queryResult;
746
    }
747
748
    /**
749
     * Posts an entity instance to entity set identified by a key
750
     *
751
     * @param ResourceSet $resourceSet The entity set containing the entity to update
752
     *
753
     * @return bool|null Returns result of executiong query
754
     */
755
    public function postResource(
756
        ResourceSet $resourceSet,
757
        RequestDescription $request,
758
        $data
759
    ) {
760
        $queryResult = $this->queryProvider->postResource(
761
            $resourceSet,
762
            $request,
763
            $data
764
        );
765
766
        return $queryResult;
767
    }
768
769
    /**
770
     * Posts an entity instance to entity set identified by a key
771
     *
772
     * @param ResourceSet $resourceSet The entity set containing the entity to update
773
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
774
     *
775
     * @return bool|null Returns result of executiong query
776
     */
777
    public function deleteResource(
778
        ResourceSet $resourceSet,
779
        RequestDescription $request,
780
        $keyDescriptor
781
    ) {
782
        $queryResult = $this->queryProvider->deleteResource(
783
            $resourceSet,
784
            $request,
785
            $keyDescriptor
786
        );
787
788
        return $queryResult;
789
    }
790
791
    /**
792
     * Get related resource set for a resource
793
     *
794
     * @param string $queryType indicates if this is a query for a count, entities, or entities with a count
795
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
796
     * @param object $sourceEntity The source entity instance.
797
     * @param ResourceSet      $targetResourceSet    The resource set of containing the target of the navigation property
798
     * @param ResourceProperty $targetProperty       The navigation property to retrieve
799
     * @param FilterInfo  $filterInfo represents the $filter parameter of the OData query.  NULL if no $filter specified
800
     * @param mixed $orderBy sorted order if we want to get the data in some specific order
801
     * @param int $top number of records which  need to be skip
802
     * @param String $skip value indicating what records to skip
803
     *
804
     * @return QueryResult
805
     *
806
     * @throws ODataException
807
     */
808 8
    public function getRelatedResourceSet(
809
        $queryType,
810
        ResourceSet $sourceResourceSet,
811
        $sourceEntity,
812
        ResourceSet $targetResourceSet,
813
        ResourceProperty $targetProperty,
814
        $filterInfo,
815
        $orderBy,
816
        $top,
817
        $skip
818
    ) {
819
820 8
        $queryResult = $this->queryProvider->getRelatedResourceSet(
821 8
            $queryType,
822 8
            $sourceResourceSet,
823 8
            $sourceEntity,
824 8
            $targetResourceSet,
825 8
            $targetProperty,
826 8
            $filterInfo,
827 8
            $orderBy,
828 8
            $top,
829 8
            $skip
830 8
        );
831
832
833 8
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getRelatedResourceSet');
834
835
836 1
        return $queryResult;
837
    }
838
839
    /**
840
     * Gets a related entity instance from an entity set identified by a key
841
     *
842
     * @param ResourceSet      $sourceResourceSet The entity set related to the entity to be fetched.
843
     * @param object           $sourceEntity      The related entity instance.
844
     * @param ResourceSet      $targetResourceSet The entity set from which entity needs to be fetched.
845
     * @param ResourceProperty $targetProperty    The metadata of the target property.
846
     * @param KeyDescriptor    $keyDescriptor     The key to identify the entity to be fetched.
847
     *
848
     *
849
     * @return object|null Returns entity instance if found else null
850
     */
851
    public function getResourceFromRelatedResourceSet(ResourceSet $sourceResourceSet,
852
        $sourceEntity, ResourceSet $targetResourceSet, ResourceProperty $targetProperty,
853
        KeyDescriptor $keyDescriptor
854
    ) {
855
        $entityInstance = $this->queryProvider->getResourceFromRelatedResourceSet(
856
            $sourceResourceSet,
857
            $sourceEntity,
858
            $targetResourceSet,
859
            $targetProperty,
860
            $keyDescriptor
861
        );
862
863
        $this->_validateEntityInstance(
864
            $entityInstance, $targetResourceSet,
865
            $keyDescriptor,
866
            'IQueryProvider::getResourceFromRelatedResourceSet'
867
        );
868
        return $entityInstance;
869
    }
870
871
    /**
872
     * Get related resource for a resource
873
     *
874
     * @param ResourceSet      $sourceResourceSet The source resource set
875
     * @param object           $sourceEntity      The source resource
876
     * @param ResourceSet      $targetResourceSet The resource set of the navigation
877
     *                                            property
878
     * @param ResourceProperty $targetProperty    The navigation property to be
879
     *                                            retrieved
880
     *
881
     * @return object|null The related resource if exists else null
882
     */
883
    public function getRelatedResourceReference(ResourceSet $sourceResourceSet,
884
        $sourceEntity, ResourceSet $targetResourceSet,
885
        ResourceProperty $targetProperty
886
    ) {
887
        $entityInstance = $this->queryProvider->getRelatedResourceReference(
888
            $sourceResourceSet,
889
            $sourceEntity,
890
            $targetResourceSet,
891
            $targetProperty
892
        );
893
894
        // we will not throw error if the resource reference is null
895
        // e.g. Orders(1234)/Customer => Customer can be null, this is
896
        // allowed if Customer is last segment. consider the following:
897
        // Orders(1234)/Customer/Orders => here if Customer is null then
898
        // the UriProcessor will throw error.
899
        if (!is_null($entityInstance)) {
900
            $entityName
901
                = $targetResourceSet
902
                    ->getResourceType()
903
                    ->getInstanceType()
904
                    ->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on POData\Providers\Metadata\Type\IType. ( Ignorable by Annotation )

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

904
                    ->/** @scrutinizer ignore-call */ getName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
905
            if (!(is_array($entityInstance) || (is_object($entityInstance) && $entityInstance instanceof $entityName)))
906
            {
907
                throw ODataException::createInternalServerError(
908
                    Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
909
                        $entityName,
910
                        'IQueryProvider::getRelatedResourceReference'
911
                    )
912
                );
913
            }
914
915
            foreach ($targetProperty->getResourceType()->getKeyProperties() as $keyName => $resourceProperty) {
916
                try {
917
                    if (is_array($entityInstance)) {
918
                        $keyValue = $entityInstance[$keyName];
919
                    } else {
920
                        $keyProperty = new \ReflectionProperty(
921
                            $entityInstance,
922
                            $keyName
923
                        );
924
                        $keyProperty->setAccessible(true);
925
                        $keyValue = $keyProperty->getValue($entityInstance);
926
                    }
927
                    if (is_null($keyValue)) {
928
                        throw ODataException::createInternalServerError(
929
                            Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties('IDSQP::getRelatedResourceReference')
930
                        );
931
                    }
932
                } catch (\ReflectionException $reflectionException) {
933
                    //throw ODataException::createInternalServerError(
934
                    //    Messages::orderByParserFailedToAccessOrInitializeProperty(
935
                    //        $resourceProperty->getName(), $resourceType->getName()
936
                    //    )
937
                    //);
938
                }
939
            }
940
        }
941
942
        return $entityInstance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $entityInstance also could return the type array which is incompatible with the documented return type null|object.
Loading history...
943
    }
944
945
    /**
946
     * Validate the given entity instance.
947
     *
948
     * @param object        $entityInstance Entity instance to validate
949
     * @param ResourceSet   &$resourceSet   Resource set to which the entity
950
     *                                      instance belongs to.
951
     * @param KeyDescriptor &$keyDescriptor The key descriptor.
952
     * @param string        $methodName     Method from which this function
953
     *                                      invoked.
954
     *
955
     * @return void
956
     *
957
     * @throws ODataException
958
     */
959
    private function _validateEntityInstance($entityInstance,
960
        ResourceSet &$resourceSet,
961
        KeyDescriptor &$keyDescriptor,
962
        $methodName
963
    ) {
964
        if (is_null($entityInstance)) {
965
            throw ODataException::createResourceNotFoundError($resourceSet->getName());
966
        }
967
968
        $entityName = $resourceSet->getResourceType()->getInstanceType()->getName();
969
        if (!is_object($entityInstance)
970
            || !($entityInstance instanceof $entityName)
971
        ) {
972
            throw ODataException::createInternalServerError(
973
                Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
974
                    $entityName,
975
                    $methodName
976
                )
977
            );
978
        }
979
980
        foreach ($keyDescriptor->getValidatedNamedValues()
981
            as $keyName => $valueDescription) {
982
            try {
983
                $keyProperty = new \ReflectionProperty($entityInstance, $keyName);
984
                $keyProperty->setAccessible(true);
985
                $keyValue = $keyProperty->getValue($entityInstance);
986
                if (is_null($keyValue)) {
987
                    throw ODataException::createInternalServerError(
988
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties($methodName)
989
                    );
990
                }
991
992
                $convertedValue
993
                    = $valueDescription[1]->convert($valueDescription[0]);
994
                if ($keyValue != $convertedValue) {
995
                    throw ODataException::createInternalServerError(
996
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNonMatchingKeys($methodName)
997
                    );
998
                }
999
            } catch (\ReflectionException $reflectionException) {
1000
                //throw ODataException::createInternalServerError(
1001
                //  Messages::orderByParserFailedToAccessOrInitializeProperty(
1002
                //      $resourceProperty->getName(), $resourceType->getName()
1003
                //  )
1004
                //);
1005
            }
1006
        }
1007
    }
1008
1009
    /**
1010
     * Assert that the given condition is true.
1011
     *
1012
     * @param boolean $condition         Condition to be asserted.
1013
     * @param string  $conditionAsString String containing message incase
1014
     *                                   if assertion fails.
1015
     *
1016
     * @throws InvalidOperationException Incase if assertion fails.
1017
     *
1018
     * @return void
1019
     */
1020
    protected function assert($condition, $conditionAsString)
1021
    {
1022
        if (!$condition) {
1023
            throw new InvalidOperationException("Unexpected state, expecting $conditionAsString");
1024
        }
1025
    }
1026
}
1027