Completed
Pull Request — master (#75)
by Christopher
14:19 queued 03:48
created

SimpleMetadataProvider::getXML()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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...
360
361
        if ($isETagProperty && $isBag) {
362
            throw new InvalidOperationException(
363
                'Only primitve property can be etag property, bag property cannot be etag property.'
364
            );
365
        }
366
367
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
368
        if ($isBag) {
369
            $kind = $kind | ResourcePropertyKind::BAG;
370
        }
371
372
        if ($isETagProperty) {
373
            $kind = $kind | ResourcePropertyKind::ETAG;
374
        }
375
376
        $resourceProperty = new ResourceProperty($name, null, $kind, $primitiveResourceType);
377
        $resourceType->addProperty($resourceProperty);
378
        if (array_key_exists($resourceType->getFullName(), $this->OdataEntityMap)) {
379
            $this->metadataManager->addPropertyToEntityType($this->OdataEntityMap[$resourceType->getFullName()], $name, $primitiveResourceType->getFullName(), null, false, $isKey);
380
        }
381
    }
382
383
    /**
384
     * @param string $name
385
     * @param ResourceType $resourceType
386
     *
387
     * @throws InvalidOperationException
388
     */
389
    private function checkInstanceProperty($name, ResourceType $resourceType)
390
    {
391
        $instance = $resourceType->getInstanceType();
392
        $hasMagicGetter = $instance instanceof IType || $instance->hasMethod('__get');
393
394
        if (!$hasMagicGetter) {
395
            try {
396
                if ($instance instanceof \ReflectionClass) {
397
                    $instance->getProperty($name);
398
                }
399
            } catch (\ReflectionException $exception) {
400
                throw new InvalidOperationException(
401
                    'Can\'t add a property which does not exist on the instance type.'
402
                );
403
            }
404
        }
405
    }
406
407
    /**
408
     * To add a NonKey-primitive property (Complex/Entity).
409
     *
410
     * @param ResourceType $resourceType resource type to which key property
411
     *                                   is to be added
412
     * @param string $name name of the key property
413
     * @param TypeCode $typeCode type of the key property
414
     * @param bool $isBag property is bag or not
415
     */
416
    public function addPrimitiveProperty($resourceType, $name, $typeCode, $isBag = false)
417
    {
418
        $this->_addPrimitivePropertyInternal($resourceType, $name, $typeCode, false, $isBag);
419
    }
420
421
    /**
422
     * To add a non-key etag property.
423
     *
424
     * @param ResourceType $resourceType resource type to which key property
425
     *                                   is to be added
426
     * @param string $name name of the property
427
     * @param TypeCode $typeCode type of the etag property
428
     */
429
    public function addETagProperty($resourceType, $name, $typeCode)
430
    {
431
        $this->_addPrimitivePropertyInternal($resourceType, $name, $typeCode, false, false, true);
432
    }
433
434
    /**
435
     * To add a resource reference property.
436
     *
437
     * @param ResourceType $resourceType The resource type to add the resource
438
     *                                        reference property to
439
     * @param string $name The name of the property to add
440
     * @param ResourceSet $targetResourceSet The resource set the resource reference
441
     *                                        property points to
442
     */
443
    public function addResourceReferenceProperty($resourceType, $name, $targetResourceSet)
444
    {
445
        $this->_addReferencePropertyInternal(
446
            $resourceType,
447
            $name,
448
            $targetResourceSet,
449
            ResourcePropertyKind::RESOURCE_REFERENCE
450
        );
451
    }
452
453
    /**
454
     * To add a navigation property (resource set or resource reference)
455
     * to a resource type.
456
     *
457
     * @param ResourceType         $resourceType         The resource type to add
458
     *                                                   the resource reference
459
     *                                                   or resource
460
     *                                                   reference set property to
461
     * @param string               $name                 The name of the
462
     *                                                   property to add
463
     * @param ResourceSet          $targetResourceSet    The resource set the
464
     *                                                   resource reference
465
     *                                                   or reference
466
     *                                                   set property
467
     *                                                   points to
468
     * @param ResourcePropertyKind $resourcePropertyKind The property kind
469
     */
470
    private function _addReferencePropertyInternal(
471
        ResourceType $resourceType,
472
        $name,
473
        ResourceSet $targetResourceSet,
474
        $resourcePropertyKind
475
    ) {
476
        $this->checkInstanceProperty($name, $resourceType);
477
478
        // check that property and resource name don't up and collide - would violate OData spec
479
        if (strtolower($name) == strtolower($resourceType->getName())) {
480
            throw new InvalidOperationException(
481
                'Property name must be different from resource name.'
482
            );
483
        }
484
485
        if (!($resourcePropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE
486
            || $resourcePropertyKind == ResourcePropertyKind::RESOURCE_REFERENCE)
487
        ) {
488
            throw new InvalidOperationException(
489
                'Property kind should be ResourceSetReference or ResourceReference'
490
            );
491
        }
492
493
        $targetResourceType = $targetResourceSet->getResourceType();
494
        $resourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
495
        $resourceType->addProperty($resourceProperty);
496
497
        //Create instance of AssociationSet for this relationship
498
        $sourceResourceSet = $resourceType->getCustomState();
499
        if (is_null($sourceResourceSet)) {
500
            throw new InvalidOperationException('Failed to retrieve the custom state from ' . $resourceType->getName());
501
        }
502
503
        //Customer_Orders_Orders, Order_Customer_Customers
504
        //(source type::name _ source property::name _ target set::name)
505
        $setKey = ResourceAssociationSet::keyName($resourceType, $name, $targetResourceSet);
506
        //$setKey = $resourceType->getName() . '_' . $name . '_' . $targetResourceType->getName();
0 ignored issues
show
Unused Code Comprehensibility introduced by
44% 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...
507
        $set = new ResourceAssociationSet(
508
            $setKey,
509
            new ResourceAssociationSetEnd($sourceResourceSet, $resourceType, $resourceProperty),
510
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, null)
511
        );
512
        if ($resourcePropertyKind == ResourcePropertyKind::RESOURCESET_REFERENCE) {
513
            $this->metadataManager->addNavigationPropertyToEntityType($this->OdataEntityMap[$resourceType->getFullName()], "*", $name, $this->OdataEntityMap[$targetResourceType->getFullName()], "*");
514
        } else {
515
            $this->metadataManager->addNavigationPropertyToEntityType($this->OdataEntityMap[$resourceType->getFullName()], "0..1", $name, $this->OdataEntityMap[$targetResourceType->getFullName()], "0..1");
516
        }
517
        $this->associationSets[$setKey] = $set;
518
    }
519
520
    /**
521
     * To add a resource set reference property.
522
     *
523
     * @param ResourceType $resourceType The resource type to add the
524
     *                                        resource reference set property to
525
     * @param string $name The name of the property to add
526
     * @param ResourceSet $targetResourceSet The resource set the resource
527
     *                                        reference set property points to
528
     */
529
    public function addResourceSetReferenceProperty($resourceType, $name, $targetResourceSet)
530
    {
531
        $this->_addReferencePropertyInternal(
532
            $resourceType,
533
            $name,
534
            $targetResourceSet,
535
            ResourcePropertyKind::RESOURCESET_REFERENCE
536
        );
537
    }
538
539
    /**
540
     * To add a complex property to entity or complex type.
541
     *
542
     * @param ResourceType $resourceType The resource type to which the
543
     *                                          complex property needs to add
544
     * @param string $name name of the complex property
545
     * @param ResourceType $complexResourceType complex resource type
546
     * @param bool $isBag complex type is bag or not
547
     *
548
     * @return ResourceProperty
549
     */
550
    public function addComplexProperty($resourceType, $name, $complexResourceType, $isBag = false)
551
    {
552
        if ($resourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY
553
            && $resourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX
554
        ) {
555
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
556
        }
557
558
        // check that property and resource name don't up and collide - would violate OData spec
559
        if (strtolower($name) == strtolower($resourceType->getName())) {
560
            throw new InvalidOperationException(
561
                'Property name must be different from resource name.'
562
            );
563
        }
564
565
        $this->checkInstanceProperty($name, $resourceType);
566
567
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
568
        if ($isBag) {
569
            $kind = $kind | ResourcePropertyKind::BAG;
570
        }
571
572
        $resourceProperty = new ResourceProperty($name, null, $kind, $complexResourceType);
573
        $resourceType->addProperty($resourceProperty);
574
575
        return $resourceProperty;
576
    }
577
}
578