ProvidersWrapper::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 8
nc 1
nop 3
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
    /**
268
     * The method must return a collection of all the types derived from 
269
     * $resourceType The collection returned should NOT include the type 
270
     * passed in as a parameter
271
     * Note: Wrapper for IMetadataProvider::getDerivedTypes
272
     * method implementation
273
     * 
274
     * @param ResourceType $resourceType Resource to get derived resource types from
275
     * 
276
     * @return ResourceType[]
277
     *
278
     * @throws InvalidOperationException when the meat provider doesn't return an array
279
     */
280
    public function getDerivedTypes(ResourceType $resourceType)
281
    {
282
        $derivedTypes = $this->metaProvider->getDerivedTypes($resourceType);
283
	    if (!is_array($derivedTypes)) {
284
		    throw new InvalidOperationException(Messages::metadataAssociationTypeSetInvalidGetDerivedTypesReturnType($resourceType->getName()));
285
	    }
286
287
        foreach ($derivedTypes as $derivedType) {
288
            $this->_validateResourceType($derivedType);
289
        }
290
291
        return $derivedTypes;
292
    }
293
294
    /**
295
     * Returns true if $resourceType represents an Entity Type which has derived 
296
     * Entity Types, else false.
297
     * Note: Wrapper for IMetadataProvider::hasDerivedTypes method
298
     * implementation
299
     * 
300
     * @param ResourceType $resourceType Resource to check for derived resource 
301
     *                                   types.
302
     * 
303
     * @return boolean
304
     * 
305
     * @throws ODataException If the ResourceType is invalid
306
     */
307
    public function hasDerivedTypes(ResourceType $resourceType)
308
    {
309
        $this->_validateResourceType($resourceType);
310
        return $this->metaProvider->hasDerivedTypes($resourceType);
311
    }
312
313
    /**
314
     * Gets the ResourceAssociationSet instance for the given source association end,
315
     * Note: Wrapper for IMetadataProvider::getResourceAssociationSet
316
     * method implementation
317
     * 
318
     * @param ResourceSet $set Resource set of the source association end
319
     * @param ResourceType       $type       Resource type of the source association end
320
     * @param ResourceProperty   $property   Resource property of the source association end
321
     *
322
     * 
323
     * @return ResourceAssociationSet|null Returns ResourceAssociationSet for the source
324
     *                                             association end, NULL if no such 
325
     *                                             association end or resource set in the
326
     *                                             other end of the association is invisible
327
     */
328
    public function getResourceAssociationSet(
329
	    ResourceSet $set,
330
        ResourceType $type,
331
        ResourceProperty $property
332
    ) {        
333
        $type = $this->_getResourceTypeWherePropertyIsDeclared($type, $property);
334
        $cacheKey = $set->getName() . '_' . $type->getName() . '_' . $property->getName();
335
336
        if (array_key_exists($cacheKey,  $this->associationSetCache)) {
337
            return $this->associationSetCache[$cacheKey];
338
        }
339
340
        $associationSet = $this->metaProvider->getResourceAssociationSet(
341
            $set,
342
            $type,
0 ignored issues
show
Bug introduced by
It seems like $type defined by $this->_getResourceTypeW...lared($type, $property) on line 333 can be null; however, POData\Providers\Metadat...esourceAssociationSet() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
343
            $property
344
        );
345
346
        if (!is_null($associationSet)) {
347
            $thisAssociationSetEnd = $associationSet->getResourceAssociationSetEnd(
348
				$set,
349
                $type,
0 ignored issues
show
Bug introduced by
It seems like $type defined by $this->_getResourceTypeW...lared($type, $property) on line 333 can be null; however, POData\Providers\Metadat...urceAssociationSetEnd() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
350
                $property
351
            );
352
353
            $relatedAssociationSetEnd = $associationSet->getRelatedResourceAssociationSetEnd(
354
                $set,
355
                $type,
0 ignored issues
show
Bug introduced by
It seems like $type defined by $this->_getResourceTypeW...lared($type, $property) on line 333 can be null; however, POData\Providers\Metadat...urceAssociationSetEnd() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
356
                $property
357
            );
358
359
            //If $thisAssociationSetEnd or $relatedAssociationSetEnd
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
360
            //is null means the associationset
361
            //we got from the IDSMP::getResourceAssociationSet is invalid. 
362
            //AssociationSet::getResourceAssociationSetEnd
363
            //return null, if AssociationSet's End1 or End2's resourceset name 
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
364
            //is not matching with the name of
365
            //resource set wrapper (param1) and resource type is not assignable 
366
            //from given resource type (param2)   
367
            if (is_null($thisAssociationSetEnd) || is_null($relatedAssociationSetEnd)) {
368
                throw new ODataException(
369
                    Messages::providersWrapperIDSMPGetResourceSetReturnsInvalidResourceSet(
370
                        $set->getName(),
371
                        $type->getFullName(),
372
                        $property->getName()
373
                    ), 
374
                    500
375
                );
376
            }
377
378
            $relatedResourceSetWrapper = $this->_validateResourceSetAndGetWrapper(
379
                $relatedAssociationSetEnd->getResourceSet()
380
            );
381
            if ($relatedResourceSetWrapper === null) {
382
                $associationSet = null;
383
            } else {
384
                $this->_validateResourceType($thisAssociationSetEnd->getResourceType());
385
                $this->_validateResourceType($relatedAssociationSetEnd->getResourceType());
386
            }
387
        }
388
389
        $this->associationSetCache[$cacheKey] = $associationSet;
390
        return $associationSet;
391
    }
392
 
393
    /**
394
     * Gets the target resource set wrapper for the given navigation property, 
395
     * source resource set wrapper and the source resource type
396
     * 
397
     * @param ResourceSetWrapper $resourceSetWrapper         Source resource set.
398
     * @param ResourceType       $resourceType               Source resource type.
399
     * @param ResourceProperty   $navigationResourceProperty Navigation property.
400
     * 
401
     * @return ResourceSetWrapper|null Returns instance of ResourceSetWrapper 
402
     *     (describes the entity set and associated configuration) for the 
403
     *     given navigation property. returns NULL if resourceset for the 
404
     *     navigation property is invisible or if metadata provider returns 
405
     *     null resource association set
406
     */
407
    public function getResourceSetWrapperForNavigationProperty(
408
        ResourceSetWrapper $resourceSetWrapper,
409
        ResourceType $resourceType,
410
        ResourceProperty $navigationResourceProperty
411
    ) {
412
        $associationSet = $this->getResourceAssociationSet(
413
            $resourceSetWrapper,
414
            $resourceType,
415
            $navigationResourceProperty
416
        );
417
418
        if (!is_null($associationSet)) {
419
            $relatedAssociationSetEnd = $associationSet->getRelatedResourceAssociationSetEnd(
420
                $resourceSetWrapper->getResourceSet(),
421
                $resourceType,
422
                $navigationResourceProperty
423
            );
424
            return $this->_validateResourceSetAndGetWrapper(
425
                $relatedAssociationSetEnd->getResourceSet()
426
            );
427
        }
428
429
        return null;
430
    }
431
432
    /**
433
     * Gets the visible resource properties for the given resource type from the given resource set wrapper.
434
     *
435
     * @param ResourceSetWrapper $setWrapper Resource set wrapper in question.
436
     * @param ResourceType       $resourceType       Resource type in question.
437
     * @return ResourceProperty[] Collection of visible resource properties from the given resource set wrapper and resource type.
438
     */
439
    public function getResourceProperties(ResourceSetWrapper $setWrapper, ResourceType $resourceType) {
440
        if ($resourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY) {
441
	        //Complex resource type
442
	        return $resourceType->getAllProperties();
443
        }
444
	    //TODO: move this to doctrine annotations
445
	    $cacheKey = $setWrapper->getName() . '_' . $resourceType->getFullName();
446
        if (!array_key_exists($cacheKey,  $this->propertyCache)) {
447
	        //Fill the cache
448
	        $this->propertyCache[$cacheKey] = array();
449
	        foreach ($resourceType->getAllProperties() as $resourceProperty) {
450
	            //Check whether this is a visible navigation property
451
		        //TODO: is this broken?? see #87
452
	            if ($resourceProperty->getTypeKind() == ResourceTypeKind::ENTITY
453
	                && !is_null($this->getResourceSetWrapperForNavigationProperty($setWrapper, $resourceType, $resourceProperty))
454
	            ) {
455
	                $this->propertyCache[$cacheKey][$resourceProperty->getName()] = $resourceProperty;
456
	            } else {
457
	                //primitive, bag or complex property
458
	                $this->propertyCache[$cacheKey][$resourceProperty->getName()] = $resourceProperty;
459
	            }
460
	        }
461
        }
462
        return $this->propertyCache[$cacheKey];
463
464
    }
465
466
    /**
467
     * Wrapper function over _validateResourceSetAndGetWrapper function
468
     *  
469
     * @param ResourceSet $resourceSet see the comments of _validateResourceSetAndGetWrapper
470
     * 
471
     * @return ResourceSetWrapper|null see the comments of _validateResourceSetAndGetWrapper
472
     */
473
    public function validateResourceSetAndGetWrapper(ResourceSet $resourceSet)
474
    {
475
        return $this->_validateResourceSetAndGetWrapper($resourceSet);
476
    }
477
478
    /**
479
     * Gets the Edm Schema version compliance to the metadata
480
     * 
481
     * @return EdmSchemaVersion
482
     */
483
    public function getEdmSchemaVersion()
484
    {
485
        //The minimal schema version for custom provider is 1.1
486
        return EdmSchemaVersion::VERSION_1_DOT_1;
487
    }
488
489
    /**
490
     * This function perform the following operations
491
     *  (1) If the cache contain an entry [key, value] for the resourceset then 
492
     *      return the entry-value
493
     *  (2) If the cache not contain an entry for the resourceset then validate 
494
     *      the resourceset
495
     *            (a) If valid add entry as [resouceset_name, resourceSetWrapper]
496
     *            (b) if not valid add entry as [resouceset_name, null]
497
     *  Note: validating a resourceset means checking the resourceset is visible 
498
     *  or not using configuration
499
     *  
500
     * @param ResourceSet $resourceSet The resourceset to validate and get the 
501
     *                                 wrapper for
502
     * 
503
     * @return ResourceSetWrapper|null Returns an instance if a resource set with the given name is visible
504
     */
505
    private function _validateResourceSetAndGetWrapper(ResourceSet $resourceSet)
506
    {
507
        $cacheKey = $resourceSet->getName();
508
        if (array_key_exists($cacheKey, $this->setWrapperCache)) {
509
            return $this->setWrapperCache[$cacheKey];
510
        }
511
512
        $this->_validateResourceType($resourceSet->getResourceType());
513
        $wrapper = new ResourceSetWrapper($resourceSet, $this->config);
514
        if ($wrapper->isVisible()) {
515
            $this->setWrapperCache[$cacheKey] = $wrapper;
516
        } else {
517
            $this->setWrapperCache[$cacheKey] = null;
518
        }
519
520
        return $this->setWrapperCache[$cacheKey];
521
    }
522
523
    /**
524
     * Validates the given instance of ResourceType
525
     * 
526
     * @param ResourceType $resourceType The ResourceType to validate
527
     * 
528
     * @return ResourceType
529
     * 
530
     * @throws ODataException Exception if $resourceType is invalid
531
     */
532
    private function _validateResourceType(ResourceType $resourceType)
533
    {
534
        $cacheKey = $resourceType->getName();
535
        if (array_key_exists($cacheKey, $this->typeCache)) {
536
            return $this->typeCache[$cacheKey];
537
        }
538
539
        //TODO: Do validation if any for the ResourceType
540
        $this->typeCache[$cacheKey] = $resourceType;
541
        return $resourceType;
542
    }
543
544
    /**
545
     * Gets the resource type on which the resource property is declared on, 
546
     * If property is not declared in the given resource type, then this 
547
     * function drill down to the inheritance hierarchy of the given resource
548
     * type to find out the base class in which the property is declared
549
     * 
550
     * @param ResourceType     $resourceType     The resource type to start looking
551
     * @param ResourceProperty $resourceProperty The resource property in question
552
     * 
553
     * @return ResourceType|null Returns reference to the ResourceType on which 
554
     *                                   the $resourceProperty is declared, NULL if 
555
     *                                   $resourceProperty is not declared anywhere 
556
     *                                   in the inheritance hierarchy
557
     */
558
    private function _getResourceTypeWherePropertyIsDeclared(ResourceType $resourceType, 
559
        ResourceProperty $resourceProperty
560
    ) {
561
        $type = $resourceType;
562
        while ($type !== null) {
563
            if ($type->resolvePropertyDeclaredOnThisType($resourceProperty->getName()) !== null) {
564
                break;
565
            }
566
567
            $type = $type->getBaseType();
568
        }
569
570
        return $type;
571
    }
572
573
    /**
574
     * Gets the underlying custom expression provider, the end developer is 
575
     * responsible for implementing IExpressionProvider if he choose for
576
     * 
577
     * @return IExpressionProvider Instance of IExpressionProvider implementation.
578
     *
579
     */
580
    public function getExpressionProvider()
581
    {
582
	    $expressionProvider = $this->queryProvider->getExpressionProvider();
583
        if (is_null($expressionProvider)) {
584
            throw ODataException::createInternalServerError(Messages::providersWrapperExpressionProviderMustNotBeNullOrEmpty());
585
        }
586
587
        if (!$expressionProvider instanceof IExpressionProvider)
588
        {
589
			throw ODataException::createInternalServerError( Messages::providersWrapperInvalidExpressionProviderInstance() );
590
        }
591
592
        return $expressionProvider;
593
    }
594
595
	/**
596
	 * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
597
	 * If the query provider can not handle ordered paging, it must return the entire result set and POData will
598
	 * perform the ordering and paging
599
	 *
600
	 * @return Boolean True if the query provider can handle ordered paging, false if POData should perform the paging
601
	 */
602
	public function handlesOrderedPaging()
603
	{
604
		return $this->queryProvider->handlesOrderedPaging();
605
	}
606
607
608
    private function ValidateQueryResult($queryResult, QueryType $queryType, $methodName){
609
        if (!$queryResult instanceof QueryResult) {
610
            throw ODataException::createInternalServerError(
611
                Messages::queryProviderReturnsNonQueryResult($methodName)
612
            );
613
        }
614
615
        if($queryType == QueryType::COUNT() || $queryType == QueryType::ENTITIES_WITH_COUNT()){
616
            //and the provider is supposed to handle the ordered paging they must return a count!
617
            if($this->queryProvider->handlesOrderedPaging() && !is_numeric($queryResult->count)){
618
                throw ODataException::createInternalServerError(
619
                    Messages::queryProviderResultCountMissing($methodName, $queryType)
620
                );
621
            }
622
623
            //If POData is supposed to handle the ordered aging they must return results! (possibly empty)
624
            if(!$this->queryProvider->handlesOrderedPaging() && !is_array($queryResult->results)){
625
                throw ODataException::createInternalServerError(
626
                    Messages::queryProviderResultsMissing($methodName, $queryType)
627
                );
628
            }
629
        }
630
631
        if(($queryType == QueryType::ENTITIES() || $queryType == QueryType::ENTITIES_WITH_COUNT()) && !is_array($queryResult->results)){
632
            throw ODataException::createInternalServerError(
633
                Messages::queryProviderResultsMissing($methodName, $queryType)
634
            );
635
        }
636
    }
637
638
    /**
639
     * Gets collection of entities belongs to an entity set
640
     *
641
     * @param QueryType $queryType indicates if this is a query for a count, entities, or entities with a count
642
     * @param ResourceSet $resourceSet The entity set containing the entities that need to be fetched
643
     * @param FilterInfo $filterInfo represents the $filter parameter of the OData query.  NULL if no $filter specified
644
     * @param InternalOrderByInfo $orderBy The orderBy information
645
     * @param int $top The top count
646
     * @param int $skip The skip count
647
     * 
648
     * @return QueryResult
649
     */
650
    public function getResourceSet(QueryType $queryType, ResourceSet $resourceSet, $filterInfo, $orderBy, $top, $skip)
651
    {
652
653
		$queryResult = $this->queryProvider->getResourceSet(
654
			$queryType,
655
			$resourceSet,
656
			$filterInfo,
657
			$orderBy,
658
			$top,
659
			$skip
660
		);
661
662
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getResourceSet');
663
664
        return $queryResult;
665
    }
666
 
667
668
    
669
    /**
670
     * Gets an entity instance from an entity set identified by a key
671
     *
672
     * @param ResourceSet $resourceSet The entity set containing the entity to fetch
673
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
674
     *
675
     * @return object|null Returns entity instance if found else null
676
     */
677
    public function getResourceFromResourceSet(ResourceSet $resourceSet, KeyDescriptor $keyDescriptor)
678
    {
679
        $entityInstance = $this->queryProvider->getResourceFromResourceSet( $resourceSet, $keyDescriptor );
680
        $this->_validateEntityInstance(
681
            $entityInstance, 
0 ignored issues
show
Bug introduced by
It seems like $entityInstance defined by $this->queryProvider->ge...rceSet, $keyDescriptor) on line 679 can also be of type null; however, POData\Providers\Provide...alidateEntityInstance() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
682
            $resourceSet, 
683
            $keyDescriptor, 
684
            'IQueryProvider::getResourceFromResourceSet'
685
        );
686
        return $entityInstance;
687
    }
688
689
    /**
690
     * Get related resource set for a resource
691
     *
692
     * @param QueryType $queryType indicates if this is a query for a count, entities, or entities with a count
693
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
694
     * @param object $sourceEntity The source entity instance.
695
     * @param ResourceSet      $targetResourceSet    The resource set of containing the target of the navigation property
696
     * @param ResourceProperty $targetProperty       The navigation property to retrieve
697
     * @param FilterInfo  $filterInfo represents the $filter parameter of the OData query.  NULL if no $filter specified
698
     * @param mixed $orderBy sorted order if we want to get the data in some specific order
699
     * @param int $top number of records which  need to be skip
700
     * @param String $skip value indicating what records to skip
701
     *
702
     * @return QueryResult
703
     *
704
     * @throws ODataException
705
     */
706
	public function getRelatedResourceSet(
707
		QueryType $queryType,
708
	    ResourceSet $sourceResourceSet,
709
	    $sourceEntity,
710
        ResourceSet $targetResourceSet,
711
        ResourceProperty $targetProperty, 
712
        $filterInfo,
713
        $orderBy,
714
        $top,
715
        $skip
716
    ) {
717
718
		$queryResult = $this->queryProvider->getRelatedResourceSet(
719
			$queryType,
720
		    $sourceResourceSet,
721
		    $sourceEntity,
722
		    $targetResourceSet,
723
		    $targetProperty,
724
			$filterInfo,
725
		    $orderBy,
726
		    $top,
727
		    $skip
728
		);
729
730
731
        $this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getRelatedResourceSet');
732
733
734
        return $queryResult;
735
    }
736
737
    /**
738
     * Gets a related entity instance from an entity set identified by a key
739
     * 
740
     * @param ResourceSet      $sourceResourceSet The entity set related to the entity to be fetched.
741
     * @param object           $sourceEntity      The related entity instance.
742
     * @param ResourceSet      $targetResourceSet The entity set from which entity needs to be fetched.
743
     * @param ResourceProperty $targetProperty    The metadata of the target property.
744
     * @param KeyDescriptor    $keyDescriptor     The key to identify the entity to be fetched.
745
     *
746
     * 
747
     * @return object|null Returns entity instance if found else null
748
     */
749
    public function getResourceFromRelatedResourceSet(ResourceSet $sourceResourceSet,
750
        $sourceEntity, ResourceSet $targetResourceSet, ResourceProperty $targetProperty,
751
        KeyDescriptor $keyDescriptor
752
    ) {
753
        $entityInstance = $this->queryProvider->getResourceFromRelatedResourceSet(
754
			$sourceResourceSet,
755
			$sourceEntity,
756
			$targetResourceSet,
757
			$targetProperty,
758
			$keyDescriptor
759
		);
760
761
	    $this->_validateEntityInstance(
762
            $entityInstance, $targetResourceSet, 
0 ignored issues
show
Bug introduced by
It seems like $entityInstance defined by $this->queryProvider->ge...operty, $keyDescriptor) on line 753 can also be of type null; however, POData\Providers\Provide...alidateEntityInstance() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
763
            $keyDescriptor, 
764
            'IQueryProvider::getResourceFromRelatedResourceSet'
765
        );
766
        return $entityInstance;
767
    }
768
769
    /**
770
     * Get related resource for a resource
771
     * 
772
     * @param ResourceSet      $sourceResourceSet The source resource set
773
     * @param object           $sourceEntity      The source resource
774
     * @param ResourceSet      $targetResourceSet The resource set of the navigation
775
     *                                            property
776
     * @param ResourceProperty $targetProperty    The navigation property to be 
777
     *                                            retrieved
778
     * 
779
     * @return object|null The related resource if exists else null
780
     */
781
    public function getRelatedResourceReference(ResourceSet $sourceResourceSet, 
782
        $sourceEntity, ResourceSet $targetResourceSet, 
783
        ResourceProperty $targetProperty
784
    ) {
785
        $entityInstance = $this->queryProvider->getRelatedResourceReference(
786
            $sourceResourceSet, 
787
            $sourceEntity, 
788
            $targetResourceSet, 
789
            $targetProperty
790
        );
791
792
        // we will not throw error if the resource reference is null
793
        // e.g. Orders(1234)/Customer => Customer can be null, this is 
794
        // allowed if Customer is last segment. consider the following:
795
        // Orders(1234)/Customer/Orders => here if Customer is null then 
796
        // the UriProcessor will throw error.
797
        if (!is_null($entityInstance)) {
798
            $entityName 
799
                = $targetResourceSet
0 ignored issues
show
Bug introduced by
The method getName does only exist in ReflectionClass, but not in POData\Providers\Metadata\Type\IType.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
800
                    ->getResourceType()
801
                    ->getInstanceType()
802
                    ->getName();
803 View Code Duplication
            if (!is_object($entityInstance) 
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
804
                || !($entityInstance instanceof $entityName)
805
            ) {
806
                throw ODataException::createInternalServerError(
807
                    Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
808
                        $entityName, 
809
                        'IQueryProvider::getRelatedResourceReference'
810
                    )
811
                );
812
            }
813
814
            foreach ($targetProperty->getResourceType()->getKeyProperties() 
815
            as $keyName => $resourceProperty) {
816
                try {
817
                    $keyProperty = new \ReflectionProperty(
818
                        $entityInstance, 
819
                        $keyName
820
                    );
821
                    $keyValue = $keyProperty->getValue($entityInstance);
822
                    if (is_null($keyValue)) {
823
                        throw ODataException::createInternalServerError(
824
                            Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties('IDSQP::getRelatedResourceReference')
825
                        );
826
                    }
827
                } catch (\ReflectionException $reflectionException) {
828
                    //throw ODataException::createInternalServerError(
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
829
                    //    Messages::orderByParserFailedToAccessOrInitializeProperty(
830
                    //        $resourceProperty->getName(), $resourceType->getName()
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
831
                    //    )
832
                    //);
833
                }
834
            }
835
        }
836
837
        return $entityInstance;
838
    }
839
840
    /**
841
     * Validate the given entity instance.
842
     * 
843
     * @param object        $entityInstance Entity instance to validate
844
     * @param ResourceSet   &$resourceSet   Resource set to which the entity 
845
     *                                      instance belongs to.
846
     * @param KeyDescriptor &$keyDescriptor The key descriptor.
847
     * @param string        $methodName     Method from which this function 
848
     *                                      invoked.
849
     *
850
     * @return void
851
     * 
852
     * @throws ODataException
853
     */
854
    private function _validateEntityInstance($entityInstance, 
855
        ResourceSet &$resourceSet, 
856
        KeyDescriptor &$keyDescriptor, 
857
        $methodName
858
    ) {
859
        if (is_null($entityInstance)) {
860
            throw ODataException::createResourceNotFoundError($resourceSet->getName());
861
        }
862
863
        $entityName = $resourceSet->getResourceType()->getInstanceType()->getName();
0 ignored issues
show
Bug introduced by
The method getName does only exist in ReflectionClass, but not in POData\Providers\Metadata\Type\IType.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
864 View Code Duplication
        if (!is_object($entityInstance) 
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
865
            || !($entityInstance instanceof $entityName)
866
        ) {
867
            throw ODataException::createInternalServerError(
868
                Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
869
                    $entityName, 
870
                    $methodName
871
                )
872
            );
873
        }
874
875
        foreach ($keyDescriptor->getValidatedNamedValues() 
876
            as $keyName => $valueDescription) {
877
            try {
878
                $keyProperty = new \ReflectionProperty($entityInstance, $keyName);
879
                $keyValue = $keyProperty->getValue($entityInstance);
880
                if (is_null($keyValue)) {
881
                    throw ODataException::createInternalServerError(
882
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties($methodName)
883
                    );
884
                }
885
886
                $convertedValue 
887
                    = $valueDescription[1]->convert($valueDescription[0]);
888
                if ($keyValue != $convertedValue) {
889
                    throw ODataException::createInternalServerError(
890
                        Messages::providersWrapperIDSQPMethodReturnsInstanceWithNonMatchingKeys($methodName)
891
                    );
892
                }
893
            } catch (\ReflectionException $reflectionException) {
894
                //throw ODataException::createInternalServerError(
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
895
                //  Messages::orderByParserFailedToAccessOrInitializeProperty(
896
                //      $resourceProperty->getName(), $resourceType->getName()
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
897
                //  )
898
                //);
899
            }
900
        }
901
    }
902
903
    /**
904
     * Assert that the given condition is true.
905
     *
906
     * @param boolean $condition         Condition to be asserted.
907
     * @param string  $conditionAsString String containing message incase
908
     *                                   if assertion fails.
909
     *
910
     * @throws InvalidOperationException Incase if assertion fails.
911
     *
912
     * @return void
913
     */
914
    protected function assert($condition, $conditionAsString)
915
    {
916
    	if (!$condition) {
917
    		throw new InvalidOperationException("Unexpected state, expecting $conditionAsString");
918
    	}
919
    }
920
}