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

ProvidersWrapper::getRelatedResourceSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 29
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 29
c 0
b 0
f 0
rs 9.8666
cc 1
nc 1
nop 9

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
741
            $resourceSet,
742
            $data
743
        );
744
745
        return $queryResult;
746
    }
747
748
    /**
749
     * Posts an entity instance to entity set identified by a key
750
     *
751
     * @param ResourceSet $resourceSet The entity set containing the entity to update
752
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
753
     *
754
     * @return bool|null Returns result of executiong query
755
     */
756
    public function deleteResource(
757
        ResourceSet $resourceSet,
758
        $keyDescriptor
759
    ) {
760
        $queryResult = $this->queryProvider->deleteResource(
0 ignored issues
show
Bug introduced by
The method deleteResource() does not exist on POData\Providers\Query\IQueryProvider. ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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