Passed
Pull Request — master (#198)
by Alex
05:30 queued 46s
created

SimpleMetadataProvider::addResourceSet()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 18
nc 6
nop 2
dl 0
loc 31
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
namespace POData\Providers\Metadata;
4
5
use AlgoWeb\ODataMetadata\MetadataManager;
6
use AlgoWeb\ODataMetadata\MetadataV3\edm\TComplexTypeType;
7
use AlgoWeb\ODataMetadata\MetadataV3\edm\TEntityTypeType;
8
use Illuminate\Support\Str;
9
use POData\Common\InvalidOperationException;
10
use POData\Providers\Metadata\Type\IType;
11
use POData\Providers\Metadata\Type\TypeCode;
12
13
/**
14
 * Class SimpleMetadataProvider.
15
 */
16
class SimpleMetadataProvider implements IMetadataProvider
17
{
18
    public $oDataEntityMap = [];
19
    protected $resourceSets = [];
20
    protected $resourceTypes = [];
21
    protected $associationSets = [];
22
    protected $containerName;
23
    protected $namespaceName;
24
    private $metadataManager;
25
    private $typeSetMapping = [];
26
    protected $singletons = [];
27
    private $baseTypes = [];
28
29
    /**
30
     * @param string $containerName container name for the datasource
31
     * @param string $namespaceName namespace for the datasource
32
     */
33
    public function __construct($containerName, $namespaceName)
34
    {
35
        $this->containerName = $containerName;
36
        $this->namespaceName = $namespaceName;
37
        $this->metadataManager = new MetadataManager($namespaceName, $containerName);
38
    }
39
40
    //Begin Implementation of IMetadataProvider
41
42
    /**
43
     * @return string|null
44
     */
45
    public function getXML()
46
    {
47
        return $this->getMetadataManager()->getEdmxXML();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMetadataManager()->getEdmxXML() returns the type JMS\Serializer\Serializer which is incompatible with the documented return type null|string.
Loading history...
48
    }
49
50
    /*
51
     * @return MetadataManager
52
     */
53
    public function getMetadataManager()
54
    {
55
        return $this->metadataManager;
56
    }
57
58
    /**
59
     * get the Container name for the data source.
60
     *
61
     * @return string container name
62
     */
63
    public function getContainerName()
64
    {
65
        return $this->containerName;
66
    }
67
68
    /**
69
     * get Namespace name for the data source.
70
     *
71
     * @return string namespace
72
     */
73
    public function getContainerNamespace()
74
    {
75
        return $this->namespaceName;
76
    }
77
78
    /**
79
     * get all entity set information.
80
     *
81
     * @param null|mixed $params
82
     *
83
     * @throws \ErrorException
84
     * @return ResourceSet[]
85
     */
86
    public function getResourceSets($params = null)
87
    {
88
        $parameters = [];
89
        if (is_string($params)) {
90
            $parameters[] = $params;
91
        } elseif (isset($params) && !is_array($params)) {
92
            throw new \ErrorException('Input parameter must be absent, null, string or array');
93
        } else {
94
            $parameters = $params;
95
        }
96
        if (!is_array($parameters) || 0 == count($parameters)) {
97
            return array_values($this->resourceSets);
98
        }
99
        assert(is_array($parameters));
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

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

99
        /** @scrutinizer ignore-call */ 
100
        assert(is_array($parameters));

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
100
        $return = [];
101
        $counter = 0;
102
        foreach ($this->resourceSets as $resource) {
103
            $resName = $resource->getName();
104
            if (in_array($resName, $parameters)) {
105
                $return[] = $resource;
106
                $counter++;
107
            }
108
        }
109
        assert($counter == count($return));
110
111
        return $return;
112
    }
113
114
    /**
115
     * get all resource types in the data source.
116
     *
117
     * @return ResourceType[]
118
     */
119
    public function getTypes()
120
    {
121
        return array_values($this->resourceTypes);
122
    }
123
124
    /**
125
     * get a resource set based on the specified resource set name.
126
     *
127
     * @param string $name Name of the resource set
128
     *
129
     * @return ResourceSet|null resource set with the given name if found else NULL
130
     */
131
    public function resolveResourceSet($name)
132
    {
133
        if (array_key_exists($name, $this->resourceSets)) {
134
            return $this->resourceSets[$name];
135
        }
136
        return null;
137
    }
138
139
    /**
140
     * get a resource type based on the resource type name.
141
     *
142
     * @param string $name Name of the resource type
143
     *
144
     * @return ResourceType|null resource type with the given resource type name if found else NULL
145
     */
146
    public function resolveResourceType($name)
147
    {
148
        if (array_key_exists($name, $this->resourceTypes)) {
149
            return $this->resourceTypes[$name];
150
        }
151
        return null;
152
    }
153
154
    /**
155
     * get a singelton based on the specified singleton name.
156
     *
157
     * @param string $name Name of the resource set
158
     *
159
     * @return ResourceFunctionType|null singleton with the given name if found else NULL
160
     */
161
    public function resolveSingleton($name)
162
    {
163
        if (array_key_exists($name, $this->singletons)) {
164
            return $this->singletons[$name];
165
        }
166
        return null;
167
    }
168
169
    /**
170
     * get a resource set based on the specified resource association set name.
171
     *
172
     * @param string $name Name of the resource assocation set
173
     *
174
     * @return ResourceAssociationSet|null resource association set with the given name if found else NULL
175
     */
176
    public function resolveAssociationSet($name)
177
    {
178
        if (array_key_exists($name, $this->associationSets)) {
179
            return $this->associationSets[$name];
180
        }
181
        return null;
182
    }
183
184
    /*
185
     * Get number of association sets hooked up
186
     */
187
    public function getAssociationCount()
188
    {
189
        return count($this->associationSets);
190
    }
191
192
    /**
193
     * The method must return a collection of all the types derived from
194
     * $resourceType The collection returned should NOT include the type
195
     * passed in as a parameter.
196
     *
197
     * @param ResourceEntityType $resourceType Resource to get derived resource types from
198
     *
199
     * @return ResourceType[]
200
     */
201
    public function getDerivedTypes(ResourceEntityType $resourceType)
202
    {
203
        $ret = [];
204
        foreach ($this->resourceTypes as $rType) {
205
            if ($rType->getBaseType() == $resourceType) {
206
                $ret[] = $rType;
207
            }
208
        }
209
        return $ret;
210
    }
211
212
    /**
213
     * @param ResourceEntityType $resourceType Resource to check for derived resource types
214
     *
215
     * @return bool true if $resourceType represents an Entity Type which has derived Entity Types, else false
216
     */
217
    public function hasDerivedTypes(ResourceEntityType $resourceType)
218
    {
219
        if (in_array($resourceType, $this->baseTypes)) {
220
            return true;
221
        }
222
        return false;
223
    }
224
225
    //End Implementation of IMetadataProvider
226
227
    /**
228
     * Gets the ResourceAssociationSet instance for the given source
229
     * association end.
230
     *
231
     * @param ResourceSet        $sourceResourceSet      Resource set of the source association end
232
     * @param ResourceEntityType $sourceResourceType     Resource type of the source association end
233
     * @param ResourceProperty   $targetResourceProperty Resource property of the target association end
234
     *
235
     * @throws InvalidOperationException
236
     * @return ResourceAssociationSet|null
237
     */
238
    public function getResourceAssociationSet(
239
        ResourceSet $sourceResourceSet,
240
        ResourceEntityType $sourceResourceType,
241
        ResourceProperty $targetResourceProperty
242
    ) {
243
        //e.g.
244
        //ResourceSet => Representing 'Customers' entity set
245
        //ResourceType => Representing'Customer' entity type
246
        //ResourceProperty => Representing 'Orders' property
247
        //We have created ResourceAssociationSet while adding
248
        //ResourceSetReference or ResourceReference
249
        //and kept in $this->associationSets
250
        //$metadata->addResourceSetReferenceProperty(
251
        //             $customersEntityType,
252
        //             'Orders',
253
        //             $ordersResourceSet
254
        //             );
255
256
        $targetResourceSet = $targetResourceProperty->getResourceType()->getCustomState();
257
        if (null === $targetResourceSet) {
258
            throw new InvalidOperationException(
259
                'Failed to retrieve the custom state from ' . $targetResourceProperty->getResourceType()->getName()
260
            );
261
        }
262
        assert($targetResourceSet instanceof ResourceSet);
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

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

262
        /** @scrutinizer ignore-call */ 
263
        assert($targetResourceSet instanceof ResourceSet);

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
263
264
        //Customer_Orders_Orders, Order_Customer_Customers
265
        $key = ResourceAssociationSet::keyName(
266
            $sourceResourceType,
267
            $targetResourceProperty->getName(),
268
            $targetResourceSet
269
        );
270
271
        $associationSet = array_key_exists($key, $this->associationSets) ? $this->associationSets[$key] : null;
272
        assert(
273
            null == $associationSet || $associationSet instanceof ResourceAssociationSet,
274
            'Retrieved resource association must be either null or an instance of ResourceAssociationSet'
275
        );
276
        return $associationSet;
277
    }
278
279
    /**
280
     * Add an entity type.
281
     *
282
     * @param  \ReflectionClass                                         $refClass reflection class of the entity
283
     * @param  string                                                   $name name of the entity
284
     * @param  null|string                                              $pluralName  Optional custom resource set name
285
     * @param  mixed                                                    $isAbstract
286
     * @param  null|mixed                                               $baseType
287
     * @return ResourceEntityType when the name is already in use
288
     * @throws InvalidOperationException when the name is already in use
289
     * @internal param string $namespace namespace of the data source
290
     */
291
    public function addEntityType(
292
        \ReflectionClass $refClass,
293
        $name,
294
        $pluralName = null,
295
        $isAbstract = false,
296
        $baseType = null
297
    ) {
298
        $result = $this->createResourceType(
299
            $refClass,
300
            $name,
301
            ResourceTypeKind::ENTITY(),
302
            $isAbstract,
303
            $baseType,
304
            $pluralName
305
        );
306
        assert($result instanceof ResourceEntityType);
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

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

306
        /** @scrutinizer ignore-call */ 
307
        assert($result instanceof ResourceEntityType);

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
307
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type POData\Providers\Metadata\ResourceComplexType which is incompatible with the documented return type POData\Providers\Metadata\ResourceEntityType.
Loading history...
308
    }
309
310
    /**
311
     * @param \ReflectionClass $refClass
312
     * @param string           $name
313
     * @param $typeKind
314
     * @param  mixed                                  $isAbstract
315
     * @param  null|mixed                             $baseType
316
     * @param  null|mixed                             $pluralName
317
     * @throws InvalidOperationException
318
     * @return ResourceEntityType|ResourceComplexType
319
     * @internal param null|string $namespace
320
     * @internal param null|ResourceType $baseResourceType
321
     */
322
    private function createResourceType(
323
        \ReflectionClass $refClass,
324
        $name,
325
        $typeKind,
326
        $isAbstract = false,
327
        $baseType = null,
328
        $pluralName = null
329
    ) {
330
        if (array_key_exists($name, $this->resourceTypes)) {
331
            throw new InvalidOperationException('Type with same name already added');
332
        }
333
        if (null !== $baseType) {
334
            $baseTEntityType = $this->oDataEntityMap[$baseType->getFullName()];
335
            $this->baseTypes[] = $baseType;
336
        } else {
337
            $baseTEntityType = null;
338
        }
339
340
        $type = null;
341
        if ($typeKind == ResourceTypeKind::ENTITY()) {
342
            list($oet, $entitySet) = $this->getMetadataManager()
343
                ->addEntityType($name, $baseTEntityType, $isAbstract, 'Public', null, null, $pluralName);
344
            assert($oet instanceof TEntityTypeType, 'Entity type ' . $name . ' not successfully added');
345
            $type = new ResourceEntityType($refClass, $oet, $this);
346
            $typeName = $type->getFullName();
347
            $returnName = MetadataManager::getResourceSetNameFromResourceType($typeName);
348
            $this->oDataEntityMap[$typeName] = $oet;
349
            $this->typeSetMapping[$name] = $entitySet;
350
            $this->typeSetMapping[$typeName] = $entitySet;
351
            $this->typeSetMapping[$returnName] = $entitySet;
352
        } elseif ($typeKind == ResourceTypeKind::COMPLEX()) {
353
            $complex = new TComplexTypeType();
354
            $complex->setName($name);
355
            $type = new ResourceComplexType($refClass, $complex);
356
        }
357
        assert(null != $type, 'Type variable must not be null');
358
359
        $this->resourceTypes[$name] = $type;
360
        ksort($this->resourceTypes);
361
362
        return $type;
363
    }
364
365
    /**
366
     * Add a complex type.
367
     *
368
     * @param  \ReflectionClass          $refClass reflection class of the complex entity type
369
     * @param  string                    $name     name of the entity
370
     * @throws InvalidOperationException when the name is already in use
371
     * @return ResourceComplexType
372
     *
373
     * @internal param string $namespace namespace of the data source
374
     * @internal param ResourceType $baseResourceType base resource type
375
     */
376
    public function addComplexType(\ReflectionClass $refClass, $name)
377
    {
378
        $result = $this->createResourceType($refClass, $name, ResourceTypeKind::COMPLEX());
379
        assert($result instanceof ResourceComplexType);
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

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

379
        /** @scrutinizer ignore-call */ 
380
        assert($result instanceof ResourceComplexType);

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
380
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type POData\Providers\Metadata\ResourceEntityType which is incompatible with the documented return type POData\Providers\Metadata\ResourceComplexType.
Loading history...
381
    }
382
383
    /**
384
     * @param string             $name         name of the resource set (now taken from resource type)
385
     * @param ResourceEntityType $resourceType resource type
386
     *
387
     * @throws InvalidOperationException
388
     *
389
     * @return ResourceSet
390
     */
391
    public function addResourceSet($name, ResourceEntityType $resourceType)
392
    {
393
        $typeName = $resourceType->getFullName();
394
        $pluralName = Str::plural($typeName);
395
        if (null == $name) {
396
            $returnName = $pluralName;
397
        } else {
398
            // handle case where $name is a fully-qualified PHP class name
399
            $nameBits = explode('\\', $name);
400
            $numBits = count($nameBits);
401
402
            if ($typeName == $nameBits[$numBits - 1]) {
403
                $returnName = $pluralName;
404
            } else {
405
                $returnName = $name;
406
            }
407
        }
408
409
        if (array_key_exists($returnName, $this->resourceSets)) {
410
            throw new InvalidOperationException('Resource Set already added');
411
        }
412
        $resourceType->validateType();
413
414
        $this->resourceSets[$returnName] = new ResourceSet($returnName, $resourceType);
415
416
        //No support for multiple ResourceSet with same EntityType
417
        //So keeping reference to the 'ResourceSet' with the entity type
418
        $resourceType->setCustomState($this->resourceSets[$returnName]);
419
        ksort($this->resourceSets);
420
421
        return $this->resourceSets[$returnName];
422
    }
423
424
    /**
425
     * To add a Key-primitive property to a resource (Complex/Entity).
426
     *
427
     * @param ResourceType $resourceType resource type to which key property
428
     *                                   is to be added
429
     * @param string       $name         name of the key property
430
     * @param TypeCode     $typeCode     type of the key property
431
     */
432
    public function addKeyProperty($resourceType, $name, $typeCode)
433
    {
434
        $this->addPrimitivePropertyInternal($resourceType, $name, $typeCode, true);
435
    }
436
437
    /**
438
     * To add a Key/NonKey-primitive property to a resource (complex/entity).
439
     *
440
     * @param ResourceType $resourceType   Resource type
441
     * @param string       $name           name of the property
442
     * @param TypeCode     $typeCode       type of property
443
     * @param bool         $isKey          property is key or not
444
     * @param bool         $isBag          property is bag or not
445
     * @param bool         $isETagProperty property is etag or not
446
     * @param null|mixed   $defaultValue
447
     * @param mixed        $nullable
448
     *
449
     * @throws InvalidOperationException
450
     */
451
    private function addPrimitivePropertyInternal(
452
        $resourceType,
453
        $name,
454
        $typeCode,
455
        $isKey = false,
456
        $isBag = false,
457
        $isETagProperty = false,
458
        $defaultValue = null,
459
        $nullable = false
460
    ) {
461
        if ($isETagProperty && $isBag) {
462
            throw new InvalidOperationException(
463
                'Only primitive property can be etag property, bag property cannot be etag property.'
464
            );
465
        }
466
467
        $this->checkInstanceProperty($name, $resourceType);
468
469
        // check that property and resource name don't up and collide - would violate OData spec
470
        if (strtolower($name) == strtolower($resourceType->getName())) {
471
            throw new InvalidOperationException(
472
                'Property name must be different from resource name.'
473
            );
474
        }
475
476
        $primitiveResourceType = ResourceType::getPrimitiveResourceType($typeCode);
0 ignored issues
show
Bug introduced by
$typeCode of type POData\Providers\Metadata\Type\TypeCode is incompatible with the type POData\Providers\Metadata\Type\EdmPrimitiveType expected by parameter $typeCode of POData\Providers\Metadat...PrimitiveResourceType(). ( Ignorable by Annotation )

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

476
        $primitiveResourceType = ResourceType::getPrimitiveResourceType(/** @scrutinizer ignore-type */ $typeCode);
Loading history...
477
478
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
479
        if ($isBag) {
480
            $kind = $kind | ResourcePropertyKind::BAG;
481
        }
482
483
        if ($isETagProperty) {
484
            $kind = $kind | ResourcePropertyKind::ETAG;
485
        }
486
487
        $resourceProperty = new ResourceProperty($name, null, $kind, $primitiveResourceType);
0 ignored issues
show
Bug introduced by
$kind of type integer is incompatible with the type POData\Providers\Metadata\ResourcePropertyKind expected by parameter $kind of POData\Providers\Metadat...Property::__construct(). ( Ignorable by Annotation )

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

487
        $resourceProperty = new ResourceProperty($name, null, /** @scrutinizer ignore-type */ $kind, $primitiveResourceType);
Loading history...
488
        $resourceType->addProperty($resourceProperty);
489
        if (array_key_exists($resourceType->getFullName(), $this->oDataEntityMap)) {
490
            $this->metadataManager->addPropertyToEntityType(
491
                $this->oDataEntityMap[$resourceType->getFullName()],
492
                $name,
493
                $primitiveResourceType->getFullName(),
494
                $defaultValue,
495
                $nullable,
496
                $isKey
497
            );
498
        }
499
    }
500
501
    /**
502
     * @param string       $name
503
     * @param ResourceType $resourceType
504
     *
505
     * @throws InvalidOperationException
506
     */
507
    private function checkInstanceProperty($name, ResourceType $resourceType)
508
    {
509
        $instance = $resourceType->getInstanceType();
510
        $hasMagicGetter = $instance instanceof IType || $instance->hasMethod('__get');
511
        if ($instance instanceof \ReflectionClass) {
512
            $hasMagicGetter |= $instance->isInstance(new \stdClass);
513
        }
514
515
        if (!$hasMagicGetter) {
516
            try {
517
                if ($instance instanceof \ReflectionClass) {
518
                    $instance->getProperty($name);
519
                }
520
            } catch (\ReflectionException $exception) {
521
                throw new InvalidOperationException(
522
                    'Can\'t add a property which does not exist on the instance type.'
523
                );
524
            }
525
        }
526
    }
527
528
    /**
529
     * To add a NonKey-primitive property (Complex/Entity).
530
     *
531
     * @param ResourceType $resourceType resource type to which key property
532
     *                                   is to be added
533
     * @param string       $name         name of the key property
534
     * @param TypeCode     $typeCode     type of the key property
535
     * @param bool         $isBag        property is bag or not
536
     * @param null|mixed   $defaultValue
537
     * @param mixed        $nullable
538
     */
539 View Code Duplication
    public function addPrimitiveProperty(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
540
        $resourceType,
541
        $name,
542
        $typeCode,
543
        $isBag = false,
544
        $defaultValue = null,
545
        $nullable = false
546
    ) {
547
        $this->addPrimitivePropertyInternal(
548
            $resourceType,
549
            $name,
550
            $typeCode,
551
            false,
552
            $isBag,
553
            false,
554
            $defaultValue,
555
            $nullable
556
        );
557
    }
558
559
    /**
560
     * To add a non-key etag property.
561
     *
562
     * @param ResourceType $resourceType resource type to which key property
563
     *                                   is to be added
564
     * @param string       $name         name of the property
565
     * @param TypeCode     $typeCode     type of the etag property
566
     * @param null|mixed   $defaultValue
567
     * @param mixed        $nullable
568
     */
569 View Code Duplication
    public function addETagProperty($resourceType, $name, $typeCode, $defaultValue = null, $nullable = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
570
    {
571
        $this->addPrimitivePropertyInternal(
572
            $resourceType,
573
            $name,
574
            $typeCode,
575
            false,
576
            false,
577
            true,
578
            $defaultValue,
579
            $nullable
580
        );
581
    }
582
583
    /**
584
     * To add a resource reference property.
585
     *
586
     * @param ResourceEntityType      $resourceType      The resource type to add the resource
587
     *                                                   reference property to
588
     * @param string                  $name              The name of the property to add
589
     * @param ResourceSet             $targetResourceSet The resource set the resource reference
590
     *                                                   property points to
591
     * @param mixed                   $flip
592
     * @param mixed                   $many
593
     * @param ResourceEntityType|null $concreteType      Underlying concrete resource reference type, if set
594
     */
595
    public function addResourceReferenceProperty(
596
        ResourceEntityType $resourceType,
597
        $name,
598
        ResourceSet $targetResourceSet,
599
        $flip = false,
600
        $many = false,
601
        ResourceEntityType $concreteType = null
602
    ) {
603
        $this->addReferencePropertyInternal(
604
            $resourceType,
605
            $name,
606
            $targetResourceSet,
607
            $flip ? '0..1' : '1',
608
            $many,
609
            $concreteType
610
        );
611
    }
612
613
    /**
614
     * To add a 1:N resource reference property.
615
     *
616
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
617
     *                                               reference property from
618
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
619
     *                                               reference property to
620
     * @param string             $sourceProperty     The name of the property to add, on source type
621
     * @param string             $targetProperty     The name of the property to add, on target type
622
     */
623 View Code Duplication
    public function addResourceReferencePropertyBidirectional(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
624
        ResourceEntityType $sourceResourceType,
625
        ResourceEntityType $targetResourceType,
626
        $sourceProperty,
627
        $targetProperty
628
    ) {
629
        $this->addReferencePropertyInternalBidirectional(
630
            $sourceResourceType,
631
            $targetResourceType,
632
            $sourceProperty,
633
            $targetProperty,
634
            '*',
635
            '1'
636
        );
637
        // verify resource property types are what we expect them to be
638
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
639
        assert(
640
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
641
            '1 side of 1:N relationship not pointing to resource reference'
642
        );
643
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
644
        assert(
645
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
646
            'N side of 1:N relationship not pointing to resource set reference'
647
        );
648
    }
649
650
    /**
651
     * To add a navigation property (resource set or resource reference)
652
     * to a resource type.
653
     *
654
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource reference
655
     *                                               or resource reference set property to
656
     * @param string             $name               The name of the property to add
657
     * @param ResourceSet        $targetResourceSet  The resource set the
658
     *                                               resource reference or reference
659
     *                                               set property points to
660
     * @param string             $resourceMult       The multiplicity of relation being added
661
     * @param mixed              $many
662
     *
663
     * @throws InvalidOperationException
664
     */
665
    private function addReferencePropertyInternal(
666
        ResourceEntityType $sourceResourceType,
667
        $name,
668
        ResourceSet $targetResourceSet,
669
        $resourceMult,
670
        $many = false,
671
        ResourceEntityType $concreteType = null
672
    ) {
673
        $allowedMult = ['*', '1', '0..1'];
674
        $backMultArray = [ '*' => '*', '1' => '0..1', '0..1' => '1'];
675
        $this->checkInstanceProperty($name, $sourceResourceType);
676
677
        // check that property and resource name don't up and collide - would violate OData spec
678
        if (strtolower($name) == strtolower($sourceResourceType->getName())) {
679
            throw new InvalidOperationException(
680
                'Property name must be different from resource name.'
681
            );
682
        }
683
        assert(in_array($resourceMult, $allowedMult), 'Supplied multiplicity ' . $resourceMult . ' not valid');
684
685
        $resourcePropertyKind = ('*' == $resourceMult)
686
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
687
            : ResourcePropertyKind::RESOURCE_REFERENCE;
688
        $targetResourceType = $targetResourceSet->getResourceType();
689
        $sourceResourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
0 ignored issues
show
Bug introduced by
$resourcePropertyKind of type integer is incompatible with the type POData\Providers\Metadata\ResourcePropertyKind expected by parameter $kind of POData\Providers\Metadat...Property::__construct(). ( Ignorable by Annotation )

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

689
        $sourceResourceProperty = new ResourceProperty($name, null, /** @scrutinizer ignore-type */ $resourcePropertyKind, $targetResourceType);
Loading history...
690
        $sourceResourceType->addProperty($sourceResourceProperty);
691
692
        //Create instance of AssociationSet for this relationship
693
        $sourceResourceSet = $sourceResourceType->getCustomState();
694
        if (!$sourceResourceSet instanceof ResourceSet) {
695
            throw new InvalidOperationException(
696
                'Failed to retrieve the custom state from '
697
                . $sourceResourceType->getName()
698
            );
699
        }
700
701
        //Customer_Orders_Orders, Order_Customer_Customers
702
        //(source type::name _ source property::name _ target set::name)
703
        $setKey = ResourceAssociationSet::keyName($sourceResourceType, $name, $targetResourceSet);
704
        //$setKey = $sourceResourceType->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...
705
        $set = new ResourceAssociationSet(
706
            $setKey,
707
            new ResourceAssociationSetEnd(
708
                $sourceResourceSet,
709
                $sourceResourceType,
710
                $sourceResourceProperty
711
            ),
712
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, null, $concreteType)
713
        );
714
        $mult = $resourceMult;
715
        $backMult = $many ? '*' : $backMultArray[$resourceMult];
716
        if (false === $many && '*' == $backMult && '*' == $resourceMult) {
717
            $backMult = '1';
718
        }
719
        $this->getMetadataManager()->addNavigationPropertyToEntityType(
720
            $this->oDataEntityMap[$sourceResourceType->getFullName()],
721
            $mult,
722
            $name,
723
            $this->oDataEntityMap[$targetResourceType->getFullName()],
724
            $backMult
725
        );
726
        $this->associationSets[$setKey] = $set;
727
    }
728
729
    /**
730
     * To add a navigation property (resource set or resource reference)
731
     * to a resource type.
732
     *
733
     * @param ResourceEntityType $sourceResourceType The source resource type to add
734
     *                                               the resource reference
735
     *                                               or resource reference set property to
736
     * @param ResourceEntityType $targetResourceType The target resource type to add
737
     *                                               the resource reference
738
     *                                               or resource reference set property to
739
     * @param string             $sourceProperty     The name of the
740
     *                                               property to add to source type
741
     * @param string             $targetProperty     The name of the
742
     *                                               property to add to target type
743
     * @param string             $sourceMultiplicity The multiplicity at the source end of relation
744
     * @param string             $targetMultiplicity The multiplicity at the target end of relation
745
     *
746
     * @throws InvalidOperationException
747
     */
748
    private function addReferencePropertyInternalBidirectional(
749
        ResourceEntityType $sourceResourceType,
750
        ResourceEntityType $targetResourceType,
751
        $sourceProperty,
752
        $targetProperty,
753
        $sourceMultiplicity,
754
        $targetMultiplicity
755
    ) {
756
        if (!is_string($sourceProperty) || !is_string($targetProperty)) {
757
            throw new InvalidOperationException('Source and target properties must both be strings');
758
        }
759
760
        $this->checkInstanceProperty($sourceProperty, $sourceResourceType);
761
        $this->checkInstanceProperty($targetProperty, $targetResourceType);
762
763
        // check that property and resource name don't up and collide - would violate OData spec
764
        if (strtolower($sourceProperty) == strtolower($sourceResourceType->getName())) {
765
            throw new InvalidOperationException(
766
                'Source property name must be different from source resource name.'
767
            );
768
        }
769
        if (strtolower($targetProperty) == strtolower($targetResourceType->getName())) {
770
            throw new InvalidOperationException(
771
                'Target property name must be different from target resource name.'
772
            );
773
        }
774
775
        //Create instance of AssociationSet for this relationship
776
        $sourceResourceSet = $sourceResourceType->getCustomState();
777
        if (!$sourceResourceSet instanceof ResourceSet) {
778
            throw new InvalidOperationException(
779
                'Failed to retrieve the custom state from '
780
                . $sourceResourceType->getName()
781
            );
782
        }
783
        $targetResourceSet = $targetResourceType->getCustomState();
784
        if (!$targetResourceSet instanceof ResourceSet) {
785
            throw new InvalidOperationException(
786
                'Failed to retrieve the custom state from '
787
                . $targetResourceType->getName()
788
            );
789
        }
790
791
        //Customer_Orders_Orders, Order_Customer_Customers
792
        $fwdSetKey = ResourceAssociationSet::keyName($sourceResourceType, $sourceProperty, $targetResourceSet);
793
        $revSetKey = ResourceAssociationSet::keyName($targetResourceType, $targetProperty, $sourceResourceSet);
794
        if (isset($this->associationSets[$fwdSetKey]) && isset($this->associationSets[$revSetKey])) {
795
            return;
796
        }
797
        $sourceKind = ('*' == $sourceMultiplicity)
798
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
799
            : ResourcePropertyKind::RESOURCE_REFERENCE;
800
        $targetKind = ('*' == $targetMultiplicity)
801
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
802
            : ResourcePropertyKind::RESOURCE_REFERENCE;
803
804
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, $targetKind, $targetResourceType);
0 ignored issues
show
Bug introduced by
$targetKind of type integer is incompatible with the type POData\Providers\Metadata\ResourcePropertyKind expected by parameter $kind of POData\Providers\Metadat...Property::__construct(). ( Ignorable by Annotation )

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

804
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, /** @scrutinizer ignore-type */ $targetKind, $targetResourceType);
Loading history...
805
        assert(
806
            $targetKind == $sourceResourceProperty->getKind(),
807
            'Resource property kind mismatch between $targetKind and $sourceResourceProperty'
808
        );
809
        $sourceResourceType->addProperty($sourceResourceProperty, false);
810
        $targetResourceProperty = new ResourceProperty($targetProperty, null, $sourceKind, $sourceResourceType);
811
        assert(
812
            $sourceKind == $targetResourceProperty->getKind(),
813
            'Resource property kind mismatch between $sourceKind and $targetResourceProperty'
814
        );
815
        $targetResourceType->addProperty($targetResourceProperty, false);
816
817
        //TODO: Audit this, figure out how it makes metadata go sproing
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
818
        $fwdSet = new ResourceAssociationSet(
819
            $fwdSetKey,
820
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
821
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty)
822
        );
823
        $revSet = new ResourceAssociationSet(
824
            $revSetKey,
825
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty),
826
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty)
827
        );
828
        $sourceName = $sourceResourceType->getFullName();
829
        $targetName = $targetResourceType->getFullName();
830
        $this->getMetadataManager()->addNavigationPropertyToEntityType(
831
            $this->oDataEntityMap[$sourceName],
832
            $sourceMultiplicity,
833
            $sourceProperty,
834
            $this->oDataEntityMap[$targetName],
835
            $targetMultiplicity,
836
            $targetProperty
837
        );
838
        $this->associationSets[$fwdSetKey] = $fwdSet;
839
        $this->associationSets[$revSetKey] = $revSet;
840
    }
841
842
    /**
843
     * To add a resource set reference property.
844
     *
845
     * @param ResourceEntityType      $resourceType      The resource type to add the
846
     *                                                   resource reference set property to
847
     * @param string                  $name              The name of the property to add
848
     * @param ResourceSet             $targetResourceSet The resource set the resource
849
     *                                                   reference set property points to
850
     * @param ResourceEntityType|null $concreteType      Underlying concrete resource type, if set
851
     * @param mixed                   $single
852
     */
853
    public function addResourceSetReferenceProperty(
854
        ResourceEntityType $resourceType,
855
        $name,
856
        ResourceSet $targetResourceSet,
857
        ResourceEntityType $concreteType = null,
858
        $single = false
859
    ) {
860
        $this->addReferencePropertyInternal(
861
            $resourceType,
862
            $name,
863
            $targetResourceSet,
864
            '*',
865
            (true === $single) ? false : null,
866
            $concreteType
867
        );
868
    }
869
870
    /**
871
     * To add a M:N resource reference property.
872
     *
873
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
874
     *                                               reference property from
875
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
876
     *                                               reference property to
877
     * @param string             $sourceProperty     The name of the property to add, on source type
878
     * @param string             $targetProperty     The name of the property to add, on target type
879
     */
880 View Code Duplication
    public function addResourceSetReferencePropertyBidirectional(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
881
        ResourceEntityType $sourceResourceType,
882
        ResourceEntityType $targetResourceType,
883
        $sourceProperty,
884
        $targetProperty
885
    ) {
886
        $this->addReferencePropertyInternalBidirectional(
887
            $sourceResourceType,
888
            $targetResourceType,
889
            $sourceProperty,
890
            $targetProperty,
891
            '*',
892
            '*'
893
        );
894
        // verify resource property types are what we expect them to be
895
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
896
        assert(
897
            ResourcePropertyKind::RESOURCESET_REFERENCE == $sourceResourceKind,
898
            'M side of M:N relationship not pointing to resource set reference'
899
        );
900
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
901
        assert(
902
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
903
            'N side of M:N relationship not pointing to resource set reference'
904
        );
905
    }
906
907
    /**
908
     * To add a 1-1 resource reference.
909
     *
910
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
911
     *                                               reference property from
912
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
913
     *                                               reference property to
914
     * @param string             $sourceProperty     The name of the property to add, on source type
915
     * @param string             $targetProperty     The name of the property to add, on target type
916
     */
917 View Code Duplication
    public function addResourceReferenceSinglePropertyBidirectional(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
918
        ResourceEntityType $sourceResourceType,
919
        ResourceEntityType $targetResourceType,
920
        $sourceProperty,
921
        $targetProperty
922
    ) {
923
        $this->addReferencePropertyInternalBidirectional(
924
            $sourceResourceType,
925
            $targetResourceType,
926
            $sourceProperty,
927
            $targetProperty,
928
            '1',
929
            '0..1'
930
        );
931
        // verify resource property types are what we expect them to be
932
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
933
        assert(
934
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
935
            '1 side of 1:1 relationship not pointing to resource reference'
936
        );
937
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
938
        assert(
939
            ResourcePropertyKind::RESOURCE_REFERENCE == $targetResourceKind,
940
            '0..1 side of 1:1 relationship not pointing to resource reference'
941
        );
942
    }
943
944
    /**
945
     * To add a complex property to entity or complex type.
946
     *
947
     * @param ResourceType        $targetResourceType  The resource type to which the complex property needs to add
948
     * @param string              $name                name of the complex property
949
     * @param ResourceComplexType $complexResourceType complex resource type
950
     * @param bool                $isBag               complex type is bag or not
951
     *
952
     * @throws InvalidOperationException
953
     * @return ResourceProperty
954
     */
955
    public function addComplexProperty(
956
        ResourceType $targetResourceType,
957
        $name,
958
        ResourceComplexType $complexResourceType,
959
        $isBag = false
960
    ) {
961
        if ($targetResourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY()
962
            && $targetResourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX()
963
        ) {
964
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
965
        }
966
967
        // check that property and resource name don't up and collide - would violate OData spec
968
        if (strtolower($name) == strtolower($targetResourceType->getName())) {
969
            throw new InvalidOperationException(
970
                'Property name must be different from resource name.'
971
            );
972
        }
973
974
        $this->checkInstanceProperty($name, $targetResourceType);
975
976
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
977
        if ($isBag) {
978
            $kind = $kind | ResourcePropertyKind::BAG;
979
        }
980
981
        $resourceProperty = new ResourceProperty($name, null, $kind, $complexResourceType);
0 ignored issues
show
Bug introduced by
$kind of type integer is incompatible with the type POData\Providers\Metadata\ResourcePropertyKind expected by parameter $kind of POData\Providers\Metadat...Property::__construct(). ( Ignorable by Annotation )

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

981
        $resourceProperty = new ResourceProperty($name, null, /** @scrutinizer ignore-type */ $kind, $complexResourceType);
Loading history...
982
        $targetResourceType->addProperty($resourceProperty);
983
984
        return $resourceProperty;
985
    }
986
987
    public function createSingleton($name, ResourceType $returnType, $functionName)
988
    {
989
        $msg = null;
990
        if (array_key_exists($name, $this->singletons)) {
991
            $msg = 'Singleton name already exists';
992
            throw new \InvalidArgumentException($msg);
993
        }
994
        if (array_key_exists($name, $this->resourceSets)) {
995
            $msg = 'Resource set with same name, ' . $name . ', exists';
996
            throw new \InvalidArgumentException($msg);
997
        }
998
        $typeName = $returnType->getName();
999
        if (!array_key_exists($typeName, $this->oDataEntityMap)) {
1000
            $msg = 'Mapping not defined for ' . $typeName;
1001
            throw new \InvalidArgumentException($msg);
1002
        }
1003
        $metaReturn = $this->oDataEntityMap[$typeName];
1004
        $anonymousSet = $this->typeSetMapping[$typeName];
1005
        $singleton = $this->getMetadataManager()->createSingleton($name, $metaReturn, $anonymousSet);
1006
        assert($singleton->isOK($msg), $msg);
1007
        $type = new ResourceFunctionType($functionName, $singleton, $returnType);
1008
        // Since singletons should take no args, enforce it here
1009
        assert(0 == count($type->getParms()));
0 ignored issues
show
Bug introduced by
The call to assert() has too few arguments starting with description. ( Ignorable by Annotation )

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

1009
        /** @scrutinizer ignore-call */ 
1010
        assert(0 == count($type->getParms()));

This check compares calls to functions or methods with their respective definitions. If the call has less 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...
1010
        $this->singletons[$name] = $type;
1011
    }
1012
1013
    public function getSingletons()
1014
    {
1015
        return $this->singletons;
1016
    }
1017
1018
    public function callSingleton($name)
1019
    {
1020
        if (!array_key_exists($name, $this->singletons)) {
1021
            $msg = 'Requested singleton does not exist';
1022
            throw new \InvalidArgumentException($msg);
1023
        }
1024
1025
        return $this->singletons[$name]->get();
1026
    }
1027
}
1028