Test Failed
Push — master ( 7228dc...961f38 )
by Bálint
12:45
created

ProvidersWrapper   F

Complexity

Total Complexity 87

Size/Duplication

Total Lines 966
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 87
eloc 282
c 0
b 0
f 0
dl 0
loc 966
rs 2

32 Methods

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

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

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

676
        /** @scrutinizer ignore-call */ 
677
        $queryResult = $this->queryProvider->getResourceSet(

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...
677
            $expansion
678
        );
679
680
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getResourceSet');
681
682
        return $queryResult;
683
    }
684
685
686
687
    /**
688
     * Gets an entity instance from an entity set identified by a key
689
     *
690
     * @param ResourceSet $resourceSet The entity set containing the entity to fetch
691
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
692
     *
693
     * @return object|null Returns entity instance if found else null
694
     */
695
    public function getResourceFromResourceSet(ResourceSet $resourceSet, KeyDescriptor $keyDescriptor)
696
    {
697
        $entityInstance = $this->queryProvider->getResourceFromResourceSet($resourceSet, $keyDescriptor);
698
        $this->_validateEntityInstance(
699
            $entityInstance,
700
            $resourceSet,
701
            $keyDescriptor,
702
            'IQueryProvider::getResourceFromResourceSet'
703
        );
704
        return $entityInstance;
705
    }
706
707
    /**
708
     * Puts an entity instance to entity set identified by a key
709
     *
710
     * @param ResourceSet $resourceSet The entity set containing the entity to update
711
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
712
     *
713
     * @return bool|null Returns result of executiong query
714
     */
715
    public function putResource(
716
        ResourceSet $resourceSet,
717
        KeyDescriptor $keyDescriptor,
718
        $data
719
    ) {
720
        $queryResult = $this->queryProvider->putResource(
0 ignored issues
show
Bug introduced by
The method putResource() does not exist on POData\Providers\Query\IQueryProvider. ( Ignorable by Annotation )

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

720
        /** @scrutinizer ignore-call */ 
721
        $queryResult = $this->queryProvider->putResource(

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...
721
            $resourceSet,
722
            $keyDescriptor,
723
            $data
724
        );
725
726
        return $queryResult;
727
    }
728
729
    /**
730
     * Posts an entity instance to entity set identified by a key
731
     *
732
     * @param ResourceSet $resourceSet The entity set containing the entity to update
733
     *
734
     * @return bool|null Returns result of executiong query
735
     */
736
    public function postResource(
737
        ResourceSet $resourceSet,
738
        $data
739
    ) {
740
        $queryResult = $this->queryProvider->postResource(
0 ignored issues
show
Bug introduced by
The method postResource() does not exist on POData\Providers\Query\IQueryProvider. ( Ignorable by Annotation )

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

740
        /** @scrutinizer ignore-call */ 
741
        $queryResult = $this->queryProvider->postResource(

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...
741
            $resourceSet,
742
            $data
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
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
753
     *
754
     * @return bool|null Returns result of executiong query
755
     */
756
    public function deleteResource(
757
        ResourceSet $resourceSet,
758
        $keyDescriptor
759
    ) {
760
        $queryResult = $this->queryProvider->deleteResource(
0 ignored issues
show
Bug introduced by
The method deleteResource() does not exist on POData\Providers\Query\IQueryProvider. ( Ignorable by Annotation )

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

760
        /** @scrutinizer ignore-call */ 
761
        $queryResult = $this->queryProvider->deleteResource(

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...
761
            $resourceSet,
762
            $keyDescriptor
763
        );
764
765
        return $queryResult;
766
    }
767
768
    /**
769
     * Get related resource set for a resource
770
     *
771
     * @param string $queryType indicates if this is a query for a count, entities, or entities with a count
772
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
773
     * @param object $sourceEntity The source entity instance.
774
     * @param ResourceSet      $targetResourceSet    The resource set of containing the target of the navigation property
775
     * @param ResourceProperty $targetProperty       The navigation property to retrieve
776
     * @param FilterInfo  $filterInfo represents the $filter parameter of the OData query.  NULL if no $filter specified
777
     * @param mixed $orderBy sorted order if we want to get the data in some specific order
778
     * @param int $top number of records which  need to be skip
779
     * @param String $skip value indicating what records to skip
780
     *
781
     * @return QueryResult
782
     *
783
     * @throws ODataException
784
     */
785
    public function getRelatedResourceSet(
786
        $queryType,
787
        ResourceSet $sourceResourceSet,
788
        $sourceEntity,
789
        ResourceSet $targetResourceSet,
790
        ResourceProperty $targetProperty,
791
        $filterInfo,
792
        $orderBy,
793
        $top,
794
        $skip
795
    ) {
796
797
        $queryResult = $this->queryProvider->getRelatedResourceSet(
798
            $queryType,
799
            $sourceResourceSet,
800
            $sourceEntity,
801
            $targetResourceSet,
802
            $targetProperty,
803
            $filterInfo,
804
            $orderBy,
805
            $top,
806
            $skip
807
        );
808
809
810
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getRelatedResourceSet');
811
812
813
        return $queryResult;
814
    }
815
816
    /**
817
     * Gets a related entity instance from an entity set identified by a key
818
     *
819
     * @param ResourceSet      $sourceResourceSet The entity set related to the entity to be fetched.
820
     * @param object           $sourceEntity      The related entity instance.
821
     * @param ResourceSet      $targetResourceSet The entity set from which entity needs to be fetched.
822
     * @param ResourceProperty $targetProperty    The metadata of the target property.
823
     * @param KeyDescriptor    $keyDescriptor     The key to identify the entity to be fetched.
824
     *
825
     *
826
     * @return object|null Returns entity instance if found else null
827
     */
828
    public function getResourceFromRelatedResourceSet(ResourceSet $sourceResourceSet,
829
        $sourceEntity, ResourceSet $targetResourceSet, ResourceProperty $targetProperty,
830
        KeyDescriptor $keyDescriptor
831
    ) {
832
        $entityInstance = $this->queryProvider->getResourceFromRelatedResourceSet(
833
            $sourceResourceSet,
834
            $sourceEntity,
835
            $targetResourceSet,
836
            $targetProperty,
837
            $keyDescriptor
838
        );
839
840
        $this->_validateEntityInstance(
841
            $entityInstance, $targetResourceSet,
842
            $keyDescriptor,
843
            'IQueryProvider::getResourceFromRelatedResourceSet'
844
        );
845
        return $entityInstance;
846
    }
847
848
    /**
849
     * Get related resource for a resource
850
     *
851
     * @param ResourceSet      $sourceResourceSet The source resource set
852
     * @param object           $sourceEntity      The source resource
853
     * @param ResourceSet      $targetResourceSet The resource set of the navigation
854
     *                                            property
855
     * @param ResourceProperty $targetProperty    The navigation property to be
856
     *                                            retrieved
857
     *
858
     * @return object|null The related resource if exists else null
859
     */
860
    public function getRelatedResourceReference(ResourceSet $sourceResourceSet,
861
        $sourceEntity, ResourceSet $targetResourceSet,
862
        ResourceProperty $targetProperty
863
    ) {
864
        $entityInstance = $this->queryProvider->getRelatedResourceReference(
865
            $sourceResourceSet,
866
            $sourceEntity,
867
            $targetResourceSet,
868
            $targetProperty
869
        );
870
871
        // we will not throw error if the resource reference is null
872
        // e.g. Orders(1234)/Customer => Customer can be null, this is
873
        // allowed if Customer is last segment. consider the following:
874
        // Orders(1234)/Customer/Orders => here if Customer is null then
875
        // the UriProcessor will throw error.
876
        if (!is_null($entityInstance)) {
877
            $entityName
878
                = $targetResourceSet
879
                    ->getResourceType()
880
                    ->getInstanceType()
881
                    ->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

881
                    ->/** @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...
882
            if (!is_object($entityInstance)
883
                || !($entityInstance instanceof $entityName)
884
            ) {
885
                throw ODataException::createInternalServerError(
886
                    Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
887
                        $entityName,
888
                        'IQueryProvider::getRelatedResourceReference'
889
                    )
890
                );
891
            }
892
893
            foreach ($targetProperty->getResourceType()->getKeyProperties()
894
            as $keyName => $resourceProperty) {
895
                try {
896
                    $keyProperty = new \ReflectionProperty(
897
                        $entityInstance,
898
                        $keyName
899
                    );
900
                    $keyProperty->setAccessible(true);
901
                    $keyValue = $keyProperty->getValue($entityInstance);
902
                    if (is_null($keyValue)) {
903
                        throw ODataException::createInternalServerError(
904
                            Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties('IDSQP::getRelatedResourceReference')
905
                        );
906
                    }
907
                } catch (\ReflectionException $reflectionException) {
908
                    //throw ODataException::createInternalServerError(
909
                    //    Messages::orderByParserFailedToAccessOrInitializeProperty(
910
                    //        $resourceProperty->getName(), $resourceType->getName()
911
                    //    )
912
                    //);
913
                }
914
            }
915
        }
916
917
        return $entityInstance;
918
    }
919
920
    /**
921
     * Validate the given entity instance.
922
     *
923
     * @param object        $entityInstance Entity instance to validate
924
     * @param ResourceSet   &$resourceSet   Resource set to which the entity
925
     *                                      instance belongs to.
926
     * @param KeyDescriptor &$keyDescriptor The key descriptor.
927
     * @param string        $methodName     Method from which this function
928
     *                                      invoked.
929
     *
930
     * @return void
931
     *
932
     * @throws ODataException
933
     */
934
    private function _validateEntityInstance($entityInstance,
935
        ResourceSet &$resourceSet,
936
        KeyDescriptor &$keyDescriptor,
937
        $methodName
938
    ) {
939
        if (is_null($entityInstance)) {
940
            throw ODataException::createResourceNotFoundError($resourceSet->getName());
941
        }
942
943
        $entityName = $resourceSet->getResourceType()->getInstanceType()->getName();
944
        if (!is_object($entityInstance)
945
            || !($entityInstance instanceof $entityName)
946
        ) {
947
            throw ODataException::createInternalServerError(
948
                Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
949
                    $entityName,
950
                    $methodName
951
                )
952
            );
953
        }
954
955
        foreach ($keyDescriptor->getValidatedNamedValues()
956
            as $keyName => $valueDescription) {
957
            try {
958
                $keyProperty = new \ReflectionProperty($entityInstance, $keyName);
959
                $keyProperty->setAccessible(true);
960
                $keyValue = $keyProperty->getValue($entityInstance);
961
                if (is_null($keyValue)) {
962
                    throw ODataException::createInternalServerError(
963
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties($methodName)
964
                    );
965
                }
966
967
                $convertedValue
968
                    = $valueDescription[1]->convert($valueDescription[0]);
969
                if ($keyValue != $convertedValue) {
970
                    throw ODataException::createInternalServerError(
971
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNonMatchingKeys($methodName)
972
                    );
973
                }
974
            } catch (\ReflectionException $reflectionException) {
975
                //throw ODataException::createInternalServerError(
976
                //  Messages::orderByParserFailedToAccessOrInitializeProperty(
977
                //      $resourceProperty->getName(), $resourceType->getName()
978
                //  )
979
                //);
980
            }
981
        }
982
    }
983
984
    /**
985
     * Assert that the given condition is true.
986
     *
987
     * @param boolean $condition         Condition to be asserted.
988
     * @param string  $conditionAsString String containing message incase
989
     *                                   if assertion fails.
990
     *
991
     * @throws InvalidOperationException Incase if assertion fails.
992
     *
993
     * @return void
994
     */
995
    protected function assert($condition, $conditionAsString)
996
    {
997
        if (!$condition) {
998
            throw new InvalidOperationException("Unexpected state, expecting $conditionAsString");
999
        }
1000
    }
1001
}
1002