Completed
Push — master ( f1c3b7...bffd1d )
by Bálint
10:19
created

ProvidersWrapper::getContainerName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 0
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 View Code Duplication
	public function getResourceSet(QueryType $queryType, ResourceSet $resourceSet, $filterInfo, $orderBy, $top, $skip, $skiptoken = null, $expansion=null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
651
	{
652
653
		$queryResult = $this->queryProvider->getResourceSet(
654
			$queryType,
655
			$resourceSet,
656
			$filterInfo,
657
			$orderBy,
658
			$top,
659
			$skip,
660
			$skiptoken,
0 ignored issues
show
Unused Code introduced by
The call to IQueryProvider::getResourceSet() has too many arguments starting with $skiptoken.

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

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

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

Loading history...
661
			$expansion
662
		);
663
664
		$this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getResourceSet');
665
666
		return $queryResult;
667
	}
668
669
670
671
	/**
672
	 * Gets an entity instance from an entity set identified by a key
673
	 *
674
	 * @param ResourceSet $resourceSet The entity set containing the entity to fetch
675
	 * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
676
	 *
677
	 * @return object|null Returns entity instance if found else null
678
	 */
679
	public function getResourceFromResourceSet(ResourceSet $resourceSet, KeyDescriptor $keyDescriptor)
680
	{
681
		$entityInstance = $this->queryProvider->getResourceFromResourceSet( $resourceSet, $keyDescriptor );
682
		$this->_validateEntityInstance(
683
			$entityInstance,
0 ignored issues
show
Bug introduced by
It seems like $entityInstance defined by $this->queryProvider->ge...rceSet, $keyDescriptor) on line 681 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...
684
			$resourceSet,
685
			$keyDescriptor,
686
			'IQueryProvider::getResourceFromResourceSet'
687
		);
688
		return $entityInstance;
689
	}
690
691
	/**
692
	 * Puts an entity instance to entity set identified by a key
693
	 *
694
	 * @param ResourceSet $resourceSet The entity set containing the entity to update
695
	 * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
696
	 *
697
	 * @return bool|null Returns result of executiong query
698
	 */
699
	public function putResource(
700
		ResourceSet $resourceSet,
701
		KeyDescriptor $keyDescriptor,
702
		$data
703
	) {
704
		$queryResult = $this->queryProvider->putResource(
0 ignored issues
show
Bug introduced by
The method putResource() does not seem to exist on object<POData\Providers\Query\IQueryProvider>.

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...
705
			$resourceSet,
706
			$keyDescriptor,
707
			$data
708
		);
709
710
		return $queryResult;
711
	}
712
713
	/**
714
	 * Posts an entity instance to entity set identified by a key
715
	 *
716
	 * @param ResourceSet $resourceSet The entity set containing the entity to update
717
	 * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
0 ignored issues
show
Bug introduced by
There is no parameter named $keyDescriptor. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
718
	 *
719
	 * @return bool|null Returns result of executiong query
720
	 */
721
	public function postResource(
722
		ResourceSet $resourceSet,
723
		$data
724
	) {
725
		$queryResult = $this->queryProvider->postResource(
0 ignored issues
show
Bug introduced by
The method postResource() does not seem to exist on object<POData\Providers\Query\IQueryProvider>.

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...
726
			$resourceSet,
727
			$data
728
		);
729
730
		return $queryResult;
731
	}
732
733
	/**
734
	 * Get related resource set for a resource
735
	 *
736
	 * @param QueryType $queryType indicates if this is a query for a count, entities, or entities with a count
737
	 * @param ResourceSet $sourceResourceSet The entity set containing the source entity
738
	 * @param object $sourceEntity The source entity instance.
739
	 * @param ResourceSet      $targetResourceSet    The resource set of containing the target of the navigation property
740
	 * @param ResourceProperty $targetProperty       The navigation property to retrieve
741
	 * @param FilterInfo  $filterInfo represents the $filter parameter of the OData query.  NULL if no $filter specified
742
	 * @param mixed $orderBy sorted order if we want to get the data in some specific order
743
	 * @param int $top number of records which  need to be skip
744
	 * @param String $skip value indicating what records to skip
745
	 *
746
	 * @return QueryResult
747
	 *
748
	 * @throws ODataException
749
	 */
750 View Code Duplication
	public function getRelatedResourceSet(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
751
		QueryType $queryType,
752
		ResourceSet $sourceResourceSet,
753
		$sourceEntity,
754
		ResourceSet $targetResourceSet,
755
		ResourceProperty $targetProperty,
756
		$filterInfo,
757
		$orderBy,
758
		$top,
759
		$skip
760
	) {
761
762
		$queryResult = $this->queryProvider->getRelatedResourceSet(
763
			$queryType,
764
			$sourceResourceSet,
765
			$sourceEntity,
766
			$targetResourceSet,
767
			$targetProperty,
768
			$filterInfo,
769
			$orderBy,
770
			$top,
771
			$skip
772
		);
773
774
775
		$this->validateQueryResult($queryResult, $queryType, 'IQueryProvider::getRelatedResourceSet');
776
777
778
		return $queryResult;
779
	}
780
781
	/**
782
	 * Gets a related entity instance from an entity set identified by a key
783
	 *
784
	 * @param ResourceSet      $sourceResourceSet The entity set related to the entity to be fetched.
785
	 * @param object           $sourceEntity      The related entity instance.
786
	 * @param ResourceSet      $targetResourceSet The entity set from which entity needs to be fetched.
787
	 * @param ResourceProperty $targetProperty    The metadata of the target property.
788
	 * @param KeyDescriptor    $keyDescriptor     The key to identify the entity to be fetched.
789
	 *
790
	 *
791
	 * @return object|null Returns entity instance if found else null
792
	 */
793
	public function getResourceFromRelatedResourceSet(ResourceSet $sourceResourceSet,
794
		$sourceEntity, ResourceSet $targetResourceSet, ResourceProperty $targetProperty,
795
		KeyDescriptor $keyDescriptor
796
	) {
797
		$entityInstance = $this->queryProvider->getResourceFromRelatedResourceSet(
798
			$sourceResourceSet,
799
			$sourceEntity,
800
			$targetResourceSet,
801
			$targetProperty,
802
			$keyDescriptor
803
		);
804
805
		$this->_validateEntityInstance(
806
			$entityInstance, $targetResourceSet,
0 ignored issues
show
Bug introduced by
It seems like $entityInstance defined by $this->queryProvider->ge...operty, $keyDescriptor) on line 797 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...
807
			$keyDescriptor,
808
			'IQueryProvider::getResourceFromRelatedResourceSet'
809
		);
810
		return $entityInstance;
811
	}
812
813
	/**
814
	 * Get related resource for a resource
815
	 *
816
	 * @param ResourceSet      $sourceResourceSet The source resource set
817
	 * @param object           $sourceEntity      The source resource
818
	 * @param ResourceSet      $targetResourceSet The resource set of the navigation
819
	 *                                            property
820
	 * @param ResourceProperty $targetProperty    The navigation property to be
821
	 *                                            retrieved
822
	 *
823
	 * @return object|null The related resource if exists else null
824
	 */
825
	public function getRelatedResourceReference(ResourceSet $sourceResourceSet,
826
		$sourceEntity, ResourceSet $targetResourceSet,
827
		ResourceProperty $targetProperty
828
	) {
829
		$entityInstance = $this->queryProvider->getRelatedResourceReference(
830
			$sourceResourceSet,
831
			$sourceEntity,
832
			$targetResourceSet,
833
			$targetProperty
834
		);
835
836
		// we will not throw error if the resource reference is null
837
		// e.g. Orders(1234)/Customer => Customer can be null, this is
838
		// allowed if Customer is last segment. consider the following:
839
		// Orders(1234)/Customer/Orders => here if Customer is null then
840
		// the UriProcessor will throw error.
841
		if (!is_null($entityInstance)) {
842
			$entityName
843
				= $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...
844
					->getResourceType()
845
					->getInstanceType()
846
					->getName();
847 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...
848
				|| !($entityInstance instanceof $entityName)
849
			) {
850
				throw ODataException::createInternalServerError(
851
					Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
852
						$entityName,
853
						'IQueryProvider::getRelatedResourceReference'
854
					)
855
				);
856
			}
857
858
			foreach ($targetProperty->getResourceType()->getKeyProperties()
859
			as $keyName => $resourceProperty) {
860
				try {
861
					$keyProperty = new \ReflectionProperty(
862
						$entityInstance,
863
						$keyName
864
					);
865
					$keyProperty->setAccessible(true);
866
					$keyValue = $keyProperty->getValue($entityInstance);
867
					if (is_null($keyValue)) {
868
						throw ODataException::createInternalServerError(
869
							Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties('IDSQP::getRelatedResourceReference')
870
						);
871
					}
872
				} catch (\ReflectionException $reflectionException) {
873
					//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...
874
					//    Messages::orderByParserFailedToAccessOrInitializeProperty(
875
					//        $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...
876
					//    )
877
					//);
878
				}
879
			}
880
		}
881
882
		return $entityInstance;
883
	}
884
885
	/**
886
	 * Validate the given entity instance.
887
	 *
888
	 * @param object        $entityInstance Entity instance to validate
889
	 * @param ResourceSet   &$resourceSet   Resource set to which the entity
890
	 *                                      instance belongs to.
891
	 * @param KeyDescriptor &$keyDescriptor The key descriptor.
892
	 * @param string        $methodName     Method from which this function
893
	 *                                      invoked.
894
	 *
895
	 * @return void
896
	 *
897
	 * @throws ODataException
898
	 */
899
	private function _validateEntityInstance($entityInstance,
900
		ResourceSet &$resourceSet,
901
		KeyDescriptor &$keyDescriptor,
902
		$methodName
903
	) {
904
		if (is_null($entityInstance)) {
905
			throw ODataException::createResourceNotFoundError($resourceSet->getName());
906
		}
907
908
		$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...
909 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...
910
			|| !($entityInstance instanceof $entityName)
911
		) {
912
			throw ODataException::createInternalServerError(
913
				Messages::providersWrapperIDSQPMethodReturnsUnExpectedType(
914
					$entityName,
915
					$methodName
916
				)
917
			);
918
		}
919
920
		foreach ($keyDescriptor->getValidatedNamedValues()
921
			as $keyName => $valueDescription) {
922
			try {
923
				$keyProperty = new \ReflectionProperty($entityInstance, $keyName);
924
				$keyProperty->setAccessible(true);
925
				$keyValue = $keyProperty->getValue($entityInstance);
926
				if (is_null($keyValue)) {
927
					throw ODataException::createInternalServerError(
928
						Messages::providersWrapperIDSQPMethodReturnsInstanceWithNullKeyProperties($methodName)
929
					);
930
				}
931
932
				$convertedValue
933
					= $valueDescription[1]->convert($valueDescription[0]);
934
				if ($keyValue != $convertedValue) {
935
					throw ODataException::createInternalServerError(
936
						Messages::providersWrapperIDSQPMethodReturnsInstanceWithNonMatchingKeys($methodName)
937
					);
938
				}
939
			} catch (\ReflectionException $reflectionException) {
940
				//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...
941
				//  Messages::orderByParserFailedToAccessOrInitializeProperty(
942
				//      $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...
943
				//  )
944
				//);
945
			}
946
		}
947
	}
948
949
	/**
950
	 * Assert that the given condition is true.
951
	 *
952
	 * @param boolean $condition         Condition to be asserted.
953
	 * @param string  $conditionAsString String containing message incase
954
	 *                                   if assertion fails.
955
	 *
956
	 * @throws InvalidOperationException Incase if assertion fails.
957
	 *
958
	 * @return void
959
	 */
960
	protected function assert($condition, $conditionAsString)
961
	{
962
		if (!$condition) {
963
			throw new InvalidOperationException("Unexpected state, expecting $conditionAsString");
964
		}
965
	}
966
}