ProvidersWrapper   F
last analyzed

Complexity

Total Complexity 89

Size/Duplication

Total Lines 988
Duplicated Lines 0 %

Test Coverage

Coverage 57.35%

Importance

Changes 0
Metric Value
wmc 89
eloc 289
c 0
b 0
f 0
dl 0
loc 988
ccs 195
cts 340
cp 0.5735
rs 2

32 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveResourceType() 0 8 2
A resolveResourceTypeByClassname() 0 8 2
A getResourceSet() 0 27 1
A getResourceProperties() 0 24 6
A getEdmSchemaVersion() 0 4 1
A hasDerivedTypes() 0 4 1
A assert() 0 4 2
A getConfiguration() 0 3 1
A getRelatedResourceSet() 0 29 1
A getResourceFromRelatedResourceSet() 0 18 1
B getResourceAssociationSet() 0 63 6
A getResourceSets() 0 19 4
B _validateEntityInstance() 0 41 8
A _validateResourceType() 0 10 2
A getContainerNamespace() 0 11 2
A getResourceFromResourceSet() 0 10 1
A __construct() 0 9 1
A getResourceSetWrapperForNavigationProperty() 0 23 2
A postResource() 0 12 1
A getContainerName() 0 11 2
A _validateResourceSetAndGetWrapper() 0 16 3
A validateResourceSetAndGetWrapper() 0 3 1
B getRelatedResourceReference() 0 60 9
A handlesOrderedPaging() 0 3 1
A getExpressionProvider() 0 13 3
A resolveResourceSet() 0 12 3
A _getResourceTypeWherePropertyIsDeclared() 0 13 3
A getDerivedTypes() 0 12 3
A putResource() 0 18 1
A getTypes() 0 17 3
A deleteResource() 0 12 1
B ValidateQueryResult() 0 26 11

How to fix   Complexity   

Complex Class

Complex classes like ProvidersWrapper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ProvidersWrapper, and based on these observations, apply Extract Interface, too.

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