Completed
Pull Request — master (#49)
by Alex
04:02
created

SimpleMetadataProvider::resolveResourceSet()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
3
namespace POData\Providers\Metadata;
4
5
use POData\Common\InvalidOperationException;
6
7
/**
8
 * Class SimpleMetadataProvider.
9
 */
10
class SimpleMetadataProvider implements IMetadataProvider
11
{
12
    protected $resourceSets = array();
13
    protected $resourceTypes = array();
14
    protected $associationSets = array();
15
    protected $containerName;
16
    protected $namespaceName;
17
    public $mappedDetails = null;
18
19
    //Begin Implementation of IMetadataProvider
20
    /**
21
     * get the Container name for the data source.
22
     *
23
     * @return string container name
24
     */
25
    public function getContainerName()
26
    {
27
        return $this->containerName;
28
    }
29
30
    /**
31
     * get Namespace name for the data source.
32
     *
33
     * @return string namespace
34
     */
35
    public function getContainerNamespace()
36
    {
37
        return $this->namespaceName;
38
    }
39
40
    /**
41
     * get all entity set information.
42
     *
43
     * @return ResourceSet[]
44
     */
45
    public function getResourceSets($params = null)
46
    {
47
        $parameters = [];
48
        if (is_string($params)) {
49
            $parameters[] = $params;
50
        } elseif (isset($params) && !is_array($params)) {
51
            throw new \ErrorException('Input parameter must be absent, null, string or array');
52
        } else {
53
            $parameters = $params;
54
        }
55
        if (!is_array($parameters) || 0 == count($parameters)) {
56
            return array_values($this->resourceSets);
57
        }
58
        assert(is_array($parameters));
59
        $return = [];
60
        $counter = 0;
61
        foreach ($this->resourceSets as $resource) {
62
            $resName = $resource->getName();
63
            if (in_array($resName, $parameters)) {
64
                $return[] = $resource;
65
                $counter++;
66
            }
67
        }
68
        assert($counter == count($return));
69
        return $return;
70
    }
71
72
    /**
73
     * get all resource types in the data source.
74
     *
75
     * @return ResourceType[]
76
     */
77
    public function getTypes()
78
    {
79
        return array_values($this->resourceTypes);
80
    }
81
82
    /**
83
     * get a resource set based on the specified resource set name.
84
     *
85
     * @param string $name Name of the resource set
86
     *
87
     * @return ResourceSet|null resource set with the given name if found else NULL
88
     */
89
    public function resolveResourceSet($name)
90
    {
91
        if (array_key_exists($name, $this->resourceSets)) {
92
            return $this->resourceSets[$name];
93
        }
94
95
        return null;
96
    }
97
98
    /**
99
     * get a resource type based on the resource set name.
100
     *
101
     * @param string $name Name of the resource set
102
     *
103
     * @return ResourceType|null resource type with the given resource set name if found else NULL
104
     */
105
    public function resolveResourceType($name)
106
    {
107
        if (array_key_exists($name, $this->resourceTypes)) {
108
            return $this->resourceTypes[$name];
109
        }
110
111
        return null;
112
    }
113
114
    /**
115
     * The method must return a collection of all the types derived from
116
     * $resourceType The collection returned should NOT include the type
117
     * passed in as a parameter.
118
     *
119
     * @param ResourceType $resourceType Resource to get derived resource types from
120
     *
121
     * @return ResourceType[]
122
     */
123
    public function getDerivedTypes(ResourceType $resourceType)
124
    {
125
        return array();
126
    }
127
128
    /**
129
     * @param ResourceType $resourceType Resource to check for derived resource types
130
     *
131
     * @return bool true if $resourceType represents an Entity Type which has derived Entity Types, else false
132
     */
133
    public function hasDerivedTypes(ResourceType $resourceType)
134
    {
135
        return false;
136
    }
137
138
    /**
139
     * Gets the ResourceAssociationSet instance for the given source
140
     * association end.
141
     *
142
     * @param ResourceSet      $sourceResourceSet      Resource set
143
     *                                                 of the source
144
     *                                                 association end
145
     * @param ResourceType     $sourceResourceType     Resource type of the source
146
     *                                                 association end
147
     * @param ResourceProperty $targetResourceProperty Resource property of
148
     *                                                 the source
149
     *                                                 association end
150
     *
151
     * @return ResourceAssociationSet
152
     */
153
    public function getResourceAssociationSet(ResourceSet $sourceResourceSet, ResourceType $sourceResourceType, ResourceProperty $targetResourceProperty)
154
    {
155
        //e.g.
156
        //ResourceSet => Representing 'Customers' entity set
157
        //ResourceType => Representing'Customer' entity type
158
        //ResourceProperty => Representing 'Orders' property
159
        //We have created ResourceAssoicationSet while adding
160
        //ResourceSetReference or ResourceReference
161
        //and kept in $this->associationSets
162
        //$metadata->addResourceSetReferenceProperty(
163
        //             $customersEntityType,
164
        //             'Orders',
165
        //             $ordersResourceSet
166
        //             );
167
168
        $targetResourceSet = $targetResourceProperty->getResourceType()->getCustomState();
169
        if (is_null($targetResourceSet)) {
170
            throw new InvalidOperationException('Failed to retrieve the custom state from ' . $targetResourceProperty->getResourceType()->getName());
171
        }
172
173
        //Customer_Orders_Orders, Order_Customer_Customers
174
        $key = $sourceResourceType->getName() . '_' . $targetResourceProperty->getName() . '_' . $targetResourceSet->getName();
175
        if (array_key_exists($key, $this->associationSets)) {
176
            return $this->associationSets[$key];
177
        }
178
179
        return null;
180
    }
181
182
    //End Implementation of IMetadataProvider
183
184
    /**
185
     * @param string $containerName container name for the datasource
186
     * @param string $namespaceName namespace for the datasource
187
     */
188
    public function __construct($containerName, $namespaceName)
189
    {
190
        $this->containerName = $containerName;
191
        $this->namespaceName = $namespaceName;
192
    }
193
194
    /**
195
     * Add an entity type.
196
     *
197
     * @param \ReflectionClass $refClass  reflection class of the entity
198
     * @param string           $name      name of the entity
199
     * @param string           $namespace namespace of the data source
200
     *
201
     * @return ResourceType
202
     *
203
     * @throws InvalidOperationException when the name is already in use
204
     */
205
    public function addEntityType(\ReflectionClass $refClass, $name, $namespace = null)
206
    {
207
        return $this->createResourceType($refClass, $name, $namespace, ResourceTypeKind::ENTITY, null);
208
    }
209
210
    /**
211
     * Add a complex type.
212
     *
213
     * @param \ReflectionClass $refClass         reflection class of the complex entity type
214
     * @param string           $name             name of the entity
215
     * @param string           $namespace        namespace of the data source
216
     * @param ResourceType     $baseResourceType base resource type
217
     *
218
     * @return ResourceType
219
     *
220
     * @throws InvalidOperationException when the name is already in use
221
     */
222
    public function addComplexType(\ReflectionClass $refClass, $name, $namespace = null, $baseResourceType = null)
223
    {
224
        return $this->createResourceType($refClass, $name, $namespace, ResourceTypeKind::COMPLEX, $baseResourceType);
225
    }
226
227
    /**
228
     * @param string       $name         name of the resource set
229
     * @param ResourceType $resourceType resource type
230
     *
231
     * @throws InvalidOperationException
232
     *
233
     * @return ResourceSet
234
     */
235
    public function addResourceSet($name, ResourceType $resourceType)
236
    {
237
        if (array_key_exists($name, $this->resourceSets)) {
238
            throw new InvalidOperationException('Resource Set already added');
239
        }
240
241
        $this->resourceSets[$name] = new ResourceSet($name, $resourceType);
242
        //No support for multiple ResourceSet with same EntityType
243
        //So keeping reference to the 'ResourceSet' with the entity type
244
        $resourceType->setCustomState($this->resourceSets[$name]);
245
        ksort($this->resourceSets);
246
247
        return $this->resourceSets[$name];
248
    }
249
250
    /**
251
     * To add a Key-primitive property to a resouce (Complex/Entuty).
252
     *
253
     * @param ResourceType $resourceType resource type to which key property
254
     *                                   is to be added
255
     * @param string       $name         name of the key property
256
     * @param TypeCode     $typeCode     type of the key property
257
     */
258
    public function addKeyProperty($resourceType, $name, $typeCode)
259
    {
260
        $this->_addPrimitivePropertyInternal($resourceType, $name, $typeCode, true);
261
    }
262
263
    /**
264
     * To add a NonKey-primitive property (Complex/Entity).
265
     *
266
     * @param ResourceType $resourceType resource type to which key property
267
     *                                   is to be added
268
     * @param string       $name         name of the key property
269
     * @param TypeCode     $typeCode     type of the key property
270
     * @param bool         $isBag        property is bag or not
271
     */
272
    public function addPrimitiveProperty($resourceType, $name, $typeCode, $isBag = false)
273
    {
274
        $this->_addPrimitivePropertyInternal($resourceType, $name, $typeCode, false, $isBag);
275
    }
276
277
    /**
278
     * To add a non-key etag property.
279
     *
280
     * @param ResourceType $resourceType resource type to which key property
281
     *                                   is to be added
282
     * @param string       $name         name of the property
283
     * @param string       $typeCode     type of the etag property
284
     */
285
    public function addETagProperty($resourceType, $name, $typeCode)
286
    {
287
        $this->_addPrimitivePropertyInternal($resourceType, $name, $typeCode, false, false, true);
0 ignored issues
show
Documentation introduced by
$typeCode is of type string, but the function expects a object<POData\Providers\Metadata\TypeCode>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
288
    }
289
290
    /**
291
     * To add a resource reference property.
292
     *
293
     * @param ResourceType $resourceType      The resource type to add the resource
294
     *                                        reference property to
295
     * @param string       $name              The name of the property to add
296
     * @param ResourceSet  $targetResourceSet The resource set the resource reference
297
     *                                        property points to
298
     */
299
    public function addResourceReferenceProperty($resourceType, $name, $targetResourceSet)
300
    {
301
        $this->_addReferencePropertyInternal(
302
            $resourceType,
303
            $name,
304
            $targetResourceSet,
305
            ResourcePropertyKind::RESOURCE_REFERENCE
306
        );
307
    }
308
309
    /**
310
     * To add a resource set reference property.
311
     *
312
     * @param ResourceType $resourceType      The resource type to add the
313
     *                                        resource reference set property to
314
     * @param string       $name              The name of the property to add
315
     * @param ResourceSet  $targetResourceSet The resource set the resource
316
     *                                        reference set property points to
317
     */
318
    public function addResourceSetReferenceProperty($resourceType, $name, $targetResourceSet)
319
    {
320
        $this->_addReferencePropertyInternal(
321
            $resourceType,
322
            $name,
323
            $targetResourceSet,
324
            ResourcePropertyKind::RESOURCESET_REFERENCE
325
        );
326
    }
327
328
    /**
329
     * To add a complex property to entity or complex type.
330
     *
331
     * @param ResourceType $resourceType        The resource type to which the
332
     *                                          complex property needs to add
333
     * @param string       $name                name of the complex property
334
     * @param ResourceType $complexResourceType complex resource type
335
     * @param bool         $isBag               complex type is bag or not
336
     *
337
     * @return ResourceProperty
338
     */
339
    public function addComplexProperty($resourceType, $name, $complexResourceType, $isBag = false)
340
    {
341
        if ($resourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY
342
            && $resourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX
343
        ) {
344
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
345
        }
346
347
        $this->checkInstanceProperty($name, $resourceType);
348
349
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
350
        if ($isBag) {
351
            $kind = $kind | ResourcePropertyKind::BAG;
352
        }
353
354
        $resourceProperty = new ResourceProperty($name, null, $kind, $complexResourceType);
355
        $resourceType->addProperty($resourceProperty);
356
357
        return $resourceProperty;
358
    }
359
360
    /**
361
     * To add a Key/NonKey-primitive property to a resource (complex/entity).
362
     *
363
     * @param ResourceType $resourceType   Resource type
364
     * @param string       $name           name of the property
365
     * @param TypeCode     $typeCode       type of property
366
     * @param bool         $isKey          property is key or not
367
     * @param bool         $isBag          property is bag or not
368
     * @param bool         $isETagProperty property is etag or not
369
     */
370
    private function _addPrimitivePropertyInternal($resourceType, $name, $typeCode, $isKey = false, $isBag = false, $isETagProperty = false)
371
    {
372
        $this->checkInstanceProperty($name, $resourceType);
373
374
        $primitiveResourceType = ResourceType::getPrimitiveResourceType($typeCode);
375
376
        if ($isETagProperty && $isBag) {
377
            throw new InvalidOperationException('Only primitve property can be etag property, bag property cannot be etag property');
378
        }
379
380
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
381
        if ($isBag) {
382
            $kind = $kind | ResourcePropertyKind::BAG;
383
        }
384
385
        if ($isETagProperty) {
386
            $kind = $kind | ResourcePropertyKind::ETAG;
387
        }
388
389
        $resourceProperty = new ResourceProperty($name, null, $kind, $primitiveResourceType);
390
        $resourceType->addProperty($resourceProperty);
391
    }
392
393
    /**
394
     * To add a navigation property (resource set or resource reference)
395
     * to a resource type.
396
     *
397
     * @param ResourceType         $resourceType         The resource type to add
398
     *                                                   the resource reference
399
     *                                                   or resource
400
     *                                                   reference set property to
401
     * @param string               $name                 The name of the
402
     *                                                   property to add
403
     * @param ResourceSet          $targetResourceSet    The resource set the
404
     *                                                   resource reference
405
     *                                                   or reference
406
     *                                                   set property
407
     *                                                   ponits to
408
     * @param ResourcePropertyKind $resourcePropertyKind The property kind
409
     */
410
    private function _addReferencePropertyInternal(
411
        ResourceType $resourceType,
412
        $name,
413
        ResourceSet $targetResourceSet,
414
        $resourcePropertyKind
415
    ) {
416
        $this->checkInstanceProperty($name, $resourceType);
417
418
        if (!($resourcePropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE
419
            || $resourcePropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE)
420
        ) {
421
            throw new InvalidOperationException(
422
                'Property kind should be ResourceSetReference or ResourceReference'
423
            );
424
        }
425
426
        $targetResourceType = $targetResourceSet->getResourceType();
427
        $resourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
428
        $resourceType->addProperty($resourceProperty);
429
430
        //Create instance of AssociationSet for this relationship
431
        $sourceResourceSet = $resourceType->getCustomState();
432
        if (is_null($sourceResourceSet)) {
433
            throw new InvalidOperationException('Failed to retrieve the custom state from ' . $resourceType->getName());
434
        }
435
436
        //Customer_Orders_Orders, Order_Customer_Customers
437
        //(source type::name _ source property::name _ target set::name)
438
        $setKey = $resourceType->getName() . '_' . $name . '_' . $targetResourceSet->getName();
439
        $set = new ResourceAssociationSet(
440
            $setKey,
441
            new ResourceAssociationSetEnd($sourceResourceSet, $resourceType, $resourceProperty),
442
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceSet->getResourceType(), null)
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<POData\Providers\...adata\ResourceProperty>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
443
        );
444
        $this->associationSets[$setKey] = $set;
445
    }
446
447
    /**
448
     * @param \ReflectionClass $refClass
449
     * @param $name
450
     * @param $namespace
451
     * @param $typeKind
452
     * @param $baseResourceType
453
     * @return ResourceType
454
     * @throws InvalidOperationException
455
     */
456
    private function createResourceType(
457
        \ReflectionClass $refClass,
458
        $name,
459
        $namespace,
460
        $typeKind,
461
        $baseResourceType
462
    ) {
463
        if (array_key_exists($name, $this->resourceTypes)) {
464
            throw new InvalidOperationException('Type with same name already added');
465
        }
466
467
        $entityType = new ResourceType($refClass, $typeKind, $name, $namespace, $baseResourceType);
468
        $this->resourceTypes[$name] = $entityType;
469
        ksort($this->resourceTypes);
470
471
        return $entityType;
472
    }
473
474
    /**
475
     * @param $name
476
     * @param ResourceType $resourceType
477
     * @throws InvalidOperationException
478
     */
479
    private function checkInstanceProperty($name, ResourceType $resourceType)
480
    {
481
        $instance = $resourceType->getInstanceType();
482
483
        if (!method_exists($instance, '__get')) {
484
            try {
485
                $instance->getProperty($name);
0 ignored issues
show
Bug introduced by
The method getProperty 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...
486
            } catch (\ReflectionException $exception) {
487
                throw new InvalidOperationException(
488
                    'Can\'t add a property which does not exist on the instance type.'
489
                );
490
            }
491
        }
492
    }
493
}
494