Test Setup Failed
Pull Request — master (#147)
by Alex
03:48
created

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

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
379
    {
380
        $returnName = Str::plural($resourceType->getFullName());
381
        if (array_key_exists($returnName, $this->resourceSets)) {
382
            throw new InvalidOperationException('Resource Set already added');
383
        }
384
385
        $this->resourceSets[$returnName] = new ResourceSet($returnName, $resourceType);
386
387
        //No support for multiple ResourceSet with same EntityType
388
        //So keeping reference to the 'ResourceSet' with the entity type
389
        $resourceType->setCustomState($this->resourceSets[$returnName]);
390
        ksort($this->resourceSets);
391
392
        return $this->resourceSets[$returnName];
393
    }
394
395
    /**
396
     * To add a Key-primitive property to a resource (Complex/Entity).
397
     *
398
     * @param ResourceType $resourceType resource type to which key property
399
     *                                   is to be added
400
     * @param string       $name         name of the key property
401
     * @param TypeCode     $typeCode     type of the key property
402
     */
403
    public function addKeyProperty($resourceType, $name, $typeCode)
404
    {
405
        $this->addPrimitivePropertyInternal($resourceType, $name, $typeCode, true);
406
    }
407
408
    /**
409
     * To add a Key/NonKey-primitive property to a resource (complex/entity).
410
     *
411
     * @param ResourceType $resourceType   Resource type
412
     * @param string       $name           name of the property
413
     * @param TypeCode     $typeCode       type of property
414
     * @param bool         $isKey          property is key or not
415
     * @param bool         $isBag          property is bag or not
416
     * @param bool         $isETagProperty property is etag or not
417
     * @param null|mixed   $defaultValue
418
     * @param mixed        $nullable
419
     *
420
     * @throws InvalidOperationException
421
     */
422
    private function addPrimitivePropertyInternal(
423
        $resourceType,
424
        $name,
425
        $typeCode,
426
        $isKey = false,
427
        $isBag = false,
428
        $isETagProperty = false,
429
        $defaultValue = null,
430
        $nullable = false
431
    ) {
432
        $this->checkInstanceProperty($name, $resourceType);
433
434
        // check that property and resource name don't up and collide - would violate OData spec
435
        if (strtolower($name) == strtolower($resourceType->getName())) {
436
            throw new InvalidOperationException(
437
                'Property name must be different from resource name.'
438
            );
439
        }
440
441
        $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...
442
443
        if ($isETagProperty && $isBag) {
444
            throw new InvalidOperationException(
445
                'Only primitive property can be etag property, bag property cannot be etag property.'
446
            );
447
        }
448
449
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
450
        if ($isBag) {
451
            $kind = $kind | ResourcePropertyKind::BAG;
452
        }
453
454
        if ($isETagProperty) {
455
            $kind = $kind | ResourcePropertyKind::ETAG;
456
        }
457
458
        $resourceProperty = new ResourceProperty($name, null, $kind, $primitiveResourceType);
459
        $resourceType->addProperty($resourceProperty);
460
        if (array_key_exists($resourceType->getFullName(), $this->oDataEntityMap)) {
461
            $this->metadataManager->addPropertyToEntityType(
462
                $this->oDataEntityMap[$resourceType->getFullName()],
463
                $name,
464
                $primitiveResourceType->getFullName(),
465
                $defaultValue,
466
                $nullable,
467
                $isKey
468
            );
469
        }
470
    }
471
472
    /**
473
     * @param string       $name
474
     * @param ResourceType $resourceType
475
     *
476
     * @throws InvalidOperationException
477
     */
478
    private function checkInstanceProperty($name, ResourceType $resourceType)
479
    {
480
        $instance = $resourceType->getInstanceType();
481
        $hasMagicGetter = $instance instanceof IType || $instance->hasMethod('__get');
482
        if ($instance instanceof \ReflectionClass) {
483
            $hasMagicGetter |= $instance->isInstance(new \stdClass);
484
        }
485
486
        if (!$hasMagicGetter) {
487
            try {
488
                if ($instance instanceof \ReflectionClass) {
489
                    $instance->getProperty($name);
490
                }
491
            } catch (\ReflectionException $exception) {
492
                throw new InvalidOperationException(
493
                    'Can\'t add a property which does not exist on the instance type.'
494
                );
495
            }
496
        }
497
    }
498
499
    /**
500
     * To add a NonKey-primitive property (Complex/Entity).
501
     *
502
     * @param ResourceType $resourceType resource type to which key property
503
     *                                   is to be added
504
     * @param string       $name         name of the key property
505
     * @param TypeCode     $typeCode     type of the key property
506
     * @param bool         $isBag        property is bag or not
507
     * @param null|mixed   $defaultValue
508
     * @param mixed        $nullable
509
     */
510 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...
511
        $resourceType,
512
        $name,
513
        $typeCode,
514
        $isBag = false,
515
        $defaultValue = null,
516
        $nullable = false
517
    ) {
518
        $this->addPrimitivePropertyInternal(
519
            $resourceType,
520
            $name,
521
            $typeCode,
522
            false,
523
            $isBag,
524
            false,
525
            $defaultValue,
526
            $nullable
527
        );
528
    }
529
530
    /**
531
     * To add a non-key etag property.
532
     *
533
     * @param ResourceType $resourceType resource type to which key property
534
     *                                   is to be added
535
     * @param string       $name         name of the property
536
     * @param TypeCode     $typeCode     type of the etag property
537
     * @param null|mixed   $defaultValue
538
     * @param mixed        $nullable
539
     */
540 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...
541
    {
542
        $this->addPrimitivePropertyInternal(
543
            $resourceType,
544
            $name,
545
            $typeCode,
546
            false,
547
            false,
548
            true,
549
            $defaultValue,
550
            $nullable
551
        );
552
    }
553
554
    /**
555
     * To add a resource reference property.
556
     *
557
     * @param ResourceEntityType $resourceType      The resource type to add the resource
558
     *                                              reference property to
559
     * @param string             $name              The name of the property to add
560
     * @param ResourceSet        $targetResourceSet The resource set the resource reference
561
     *                                              property points to
562
     */
563
    public function addResourceReferenceProperty(
564
        ResourceEntityType $resourceType,
565
        $name,
566
        ResourceSet $targetResourceSet,
567
        $flip = false,
568
        $many = false
569
    ) {
570
        $this->addReferencePropertyInternal(
571
            $resourceType,
572
            $name,
573
            $targetResourceSet,
574
            $flip ? '0..1' : '1',
575
            $many
576
        );
577
    }
578
579
    /**
580
     * To add a 1:N resource reference property.
581
     *
582
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
583
     *                                               reference property from
584
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
585
     *                                               reference property to
586
     * @param string             $sourceProperty     The name of the property to add, on source type
587
     * @param string             $targetProperty     The name of the property to add, on target type
588
     */
589 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...
590
        ResourceEntityType $sourceResourceType,
591
        ResourceEntityType $targetResourceType,
592
        $sourceProperty,
593
        $targetProperty
594
    ) {
595
        $this->addReferencePropertyInternalBidirectional(
596
            $sourceResourceType,
597
            $targetResourceType,
598
            $sourceProperty,
599
            $targetProperty,
600
            '*',
601
            '1'
602
        );
603
        // verify resource property types are what we expect them to be
604
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
605
        assert(
606
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
607
            '1 side of 1:N relationship not pointing to resource reference'
608
        );
609
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
610
        assert(
611
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
612
            'N side of 1:N relationship not pointing to resource set reference'
613
        );
614
    }
615
616
    /**
617
     * To add a navigation property (resource set or resource reference)
618
     * to a resource type.
619
     *
620
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource reference
621
     *                                               or resource reference set property to
622
     * @param string             $name               The name of the property to add
623
     * @param ResourceSet        $targetResourceSet  The resource set the
624
     *                                               resource reference or reference
625
     *                                               set property points to
626
     * @param string             $resourceMult       The multiplicity of relation being added
627
     *
628
     * @throws InvalidOperationException
629
     */
630
    private function addReferencePropertyInternal(
631
        ResourceEntityType $sourceResourceType,
632
        $name,
633
        ResourceSet $targetResourceSet,
634
        $resourceMult,
635
        $many = false
636
    ) {
637
        $allowedMult = ['*', '1', '0..1'];
638
        $backMultArray = [ '*' => '*', '1' => '0..1', '0..1' => '1'];
639
        $this->checkInstanceProperty($name, $sourceResourceType);
640
641
        // check that property and resource name don't up and collide - would violate OData spec
642
        if (strtolower($name) == strtolower($sourceResourceType->getName())) {
643
            throw new InvalidOperationException(
644
                'Property name must be different from resource name.'
645
            );
646
        }
647
        assert(in_array($resourceMult, $allowedMult), 'Supplied multiplicity ' . $resourceMult . ' not valid');
648
649
        $resourcePropertyKind = ('*' == $resourceMult)
650
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
651
            : ResourcePropertyKind::RESOURCE_REFERENCE;
652
        $targetResourceType = $targetResourceSet->getResourceType();
653
        $sourceResourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
654
        $sourceResourceType->addProperty($sourceResourceProperty);
655
656
        //Create instance of AssociationSet for this relationship
657
        $sourceResourceSet = $sourceResourceType->getCustomState();
658
        if (!$sourceResourceSet instanceof ResourceSet) {
659
            throw new InvalidOperationException(
660
                'Failed to retrieve the custom state from '
661
                . $sourceResourceType->getName()
662
            );
663
        }
664
665
        //Customer_Orders_Orders, Order_Customer_Customers
666
        //(source type::name _ source property::name _ target set::name)
667
        $setKey = ResourceAssociationSet::keyName($sourceResourceType, $name, $targetResourceSet);
668
        //$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...
669
        $set = new ResourceAssociationSet(
670
            $setKey,
671
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
672
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, null)
673
        );
674
        $mult = $resourceMult;
675
        $backMult = $many ? '*' : $backMultArray[$resourceMult];
676
        $this->getMetadataManager()->addNavigationPropertyToEntityType(
677
            $this->oDataEntityMap[$sourceResourceType->getFullName()],
678
            $mult,
679
            $name,
680
            $this->oDataEntityMap[$targetResourceType->getFullName()],
681
            $backMult
682
        );
683
        $this->associationSets[$setKey] = $set;
684
    }
685
686
    /**
687
     * To add a navigation property (resource set or resource reference)
688
     * to a resource type.
689
     *
690
     * @param ResourceEntityType $sourceResourceType The source resource type to add
691
     *                                               the resource reference
692
     *                                               or resource reference set property to
693
     * @param ResourceEntityType $targetResourceType The target resource type to add
694
     *                                               the resource reference
695
     *                                               or resource reference set property to
696
     * @param string             $sourceProperty     The name of the
697
     *                                               property to add to source type
698
     * @param string             $targetProperty     The name of the
699
     *                                               property to add to target type
700
     * @param string             $sourceMultiplicity The multiplicity at the source end of relation
701
     * @param string             $targetMultiplicity The multiplicity at the target end of relation
702
     *
703
     * @throws InvalidOperationException
704
     */
705
    private function addReferencePropertyInternalBidirectional(
706
        ResourceEntityType $sourceResourceType,
707
        ResourceEntityType $targetResourceType,
708
        $sourceProperty,
709
        $targetProperty,
710
        $sourceMultiplicity,
711
        $targetMultiplicity
712
    ) {
713
        if (!is_string($sourceProperty) || !is_string($targetProperty)) {
714
            throw new InvalidOperationException('Source and target properties must both be strings');
715
        }
716
717
        $this->checkInstanceProperty($sourceProperty, $sourceResourceType);
718
        $this->checkInstanceProperty($targetProperty, $targetResourceType);
719
720
        // check that property and resource name don't up and collide - would violate OData spec
721
        if (strtolower($sourceProperty) == strtolower($sourceResourceType->getName())) {
722
            throw new InvalidOperationException(
723
                'Source property name must be different from source resource name.'
724
            );
725
        }
726
        if (strtolower($targetProperty) == strtolower($targetResourceType->getName())) {
727
            throw new InvalidOperationException(
728
                'Target property name must be different from target resource name.'
729
            );
730
        }
731
732
        //Create instance of AssociationSet for this relationship
733
        $sourceResourceSet = $sourceResourceType->getCustomState();
734
        if (!$sourceResourceSet instanceof ResourceSet) {
735
            throw new InvalidOperationException(
736
                'Failed to retrieve the custom state from '
737
                . $sourceResourceType->getName()
738
            );
739
        }
740
        $targetResourceSet = $targetResourceType->getCustomState();
741
        if (!$targetResourceSet instanceof ResourceSet) {
742
            throw new InvalidOperationException(
743
                'Failed to retrieve the custom state from '
744
                . $targetResourceType->getName()
745
            );
746
        }
747
748
        //Customer_Orders_Orders, Order_Customer_Customers
749
        $fwdSetKey = ResourceAssociationSet::keyName($sourceResourceType, $sourceProperty, $targetResourceSet);
750
        $revSetKey = ResourceAssociationSet::keyName($targetResourceType, $targetProperty, $sourceResourceSet);
751
        if (isset($this->associationSets[$fwdSetKey]) && $this->associationSets[$revSetKey]) {
752
            return;
753
        }
754
        $sourceKind = ('*' == $sourceMultiplicity)
755
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
756
            : ResourcePropertyKind::RESOURCE_REFERENCE;
757
        $targetKind = ('*' == $targetMultiplicity)
758
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
759
            : ResourcePropertyKind::RESOURCE_REFERENCE;
760
761
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, $targetKind, $targetResourceType);
762
        assert(
763
            $targetKind == $sourceResourceProperty->getKind(),
764
            'Resource property kind mismatch between $targetKind and $sourceResourceProperty'
765
        );
766
        $sourceResourceType->addProperty($sourceResourceProperty, false);
767
        $targetResourceProperty = new ResourceProperty($targetProperty, null, $sourceKind, $sourceResourceType);
768
        assert(
769
            $sourceKind == $targetResourceProperty->getKind(),
770
            'Resource property kind mismatch between $sourceKind and $targetResourceProperty'
771
        );
772
        $targetResourceType->addProperty($targetResourceProperty, false);
773
774
        //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...
775
        $fwdSet = new ResourceAssociationSet(
776
            $fwdSetKey,
777
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
778
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty)
779
        );
780
        $revSet = new ResourceAssociationSet(
781
            $revSetKey,
782
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty),
783
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty)
784
        );
785
        $sourceName = $sourceResourceType->getFullName();
786
        $targetName = $targetResourceType->getFullName();
787
        $this->getMetadataManager()->addNavigationPropertyToEntityType(
788
            $this->oDataEntityMap[$sourceName],
789
            $sourceMultiplicity,
790
            $sourceProperty,
791
            $this->oDataEntityMap[$targetName],
792
            $targetMultiplicity,
793
            $targetProperty
794
        );
795
        $this->associationSets[$fwdSetKey] = $fwdSet;
796
        $this->associationSets[$revSetKey] = $revSet;
797
    }
798
799
    /**
800
     * To add a resource set reference property.
801
     *
802
     * @param ResourceEntityType $resourceType      The resource type to add the
803
     *                                              resource reference set property to
804
     * @param string             $name              The name of the property to add
805
     * @param ResourceSet        $targetResourceSet The resource set the resource
806
     *                                              reference set property points to
807
     */
808
    public function addResourceSetReferenceProperty(ResourceEntityType $resourceType, $name, $targetResourceSet)
809
    {
810
        $this->addReferencePropertyInternal(
811
            $resourceType,
812
            $name,
813
            $targetResourceSet,
814
            '*'
815
        );
816
    }
817
818
    /**
819
     * To add a M:N resource reference property.
820
     *
821
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
822
     *                                               reference property from
823
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
824
     *                                               reference property to
825
     * @param string             $sourceProperty     The name of the property to add, on source type
826
     * @param string             $targetProperty     The name of the property to add, on target type
827
     */
828 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...
829
        ResourceEntityType $sourceResourceType,
830
        ResourceEntityType $targetResourceType,
831
        $sourceProperty,
832
        $targetProperty
833
    ) {
834
        $this->addReferencePropertyInternalBidirectional(
835
            $sourceResourceType,
836
            $targetResourceType,
837
            $sourceProperty,
838
            $targetProperty,
839
            '*',
840
            '*'
841
        );
842
        // verify resource property types are what we expect them to be
843
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
844
        assert(
845
            ResourcePropertyKind::RESOURCESET_REFERENCE == $sourceResourceKind,
846
            'M side of M:N relationship not pointing to resource set reference'
847
        );
848
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
849
        assert(
850
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
851
            'N side of M:N relationship not pointing to resource set reference'
852
        );
853
    }
854
855
    /**
856
     * To add a 1-1 resource reference.
857
     *
858
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
859
     *                                               reference property from
860
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
861
     *                                               reference property to
862
     * @param string             $sourceProperty     The name of the property to add, on source type
863
     * @param string             $targetProperty     The name of the property to add, on target type
864
     */
865 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...
866
        ResourceEntityType $sourceResourceType,
867
        ResourceEntityType $targetResourceType,
868
        $sourceProperty,
869
        $targetProperty
870
    ) {
871
        $this->addReferencePropertyInternalBidirectional(
872
            $sourceResourceType,
873
            $targetResourceType,
874
            $sourceProperty,
875
            $targetProperty,
876
            '1',
877
            '0..1'
878
        );
879
        // verify resource property types are what we expect them to be
880
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
881
        assert(
882
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
883
            '1 side of 1:1 relationship not pointing to resource reference'
884
        );
885
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
886
        assert(
887
            ResourcePropertyKind::RESOURCE_REFERENCE == $targetResourceKind,
888
            '0..1 side of 1:1 relationship not pointing to resource reference'
889
        );
890
    }
891
892
    /**
893
     * To add a complex property to entity or complex type.
894
     *
895
     * @param ResourceType        $targetResourceType  The resource type to which the complex property needs to add
896
     * @param string              $name                name of the complex property
897
     * @param ResourceComplexType $complexResourceType complex resource type
898
     * @param bool                $isBag               complex type is bag or not
899
     *
900
     * @throws InvalidOperationException
901
     * @return ResourceProperty
902
     */
903
    public function addComplexProperty(
904
        ResourceType $targetResourceType,
905
        $name,
906
        ResourceComplexType $complexResourceType,
907
        $isBag = false
908
    ) {
909
        if ($targetResourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY()
910
            && $targetResourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX()
911
        ) {
912
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
913
        }
914
915
        // check that property and resource name don't up and collide - would violate OData spec
916
        if (strtolower($name) == strtolower($targetResourceType->getName())) {
917
            throw new InvalidOperationException(
918
                'Property name must be different from resource name.'
919
            );
920
        }
921
922
        $this->checkInstanceProperty($name, $targetResourceType);
923
924
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
925
        if ($isBag) {
926
            $kind = $kind | ResourcePropertyKind::BAG;
927
        }
928
929
        $resourceProperty = new ResourceProperty($name, null, $kind, $complexResourceType);
930
        $targetResourceType->addProperty($resourceProperty);
931
932
        return $resourceProperty;
933
    }
934
935
    public function createSingleton($name, ResourceType $returnType, $functionName)
936
    {
937
        $msg = null;
938
        if (array_key_exists($name, $this->singletons)) {
939
            $msg = 'Singleton name already exists';
940
            throw new \InvalidArgumentException($msg);
941
        }
942
        if (array_key_exists($name, $this->resourceSets)) {
943
            $msg = 'Resource set with same name, ' . $name . ', exists';
944
            throw new \InvalidArgumentException($msg);
945
        }
946
        $typeName = $returnType->getName();
947
        if (!array_key_exists($typeName, $this->oDataEntityMap)) {
948
            $msg = 'Mapping not defined for ' . $typeName;
949
            throw new \InvalidArgumentException($msg);
950
        }
951
        $metaReturn = $this->oDataEntityMap[$typeName];
952
        $anonymousSet = $this->typeSetMapping[$typeName];
953
        $singleton = $this->getMetadataManager()->createSingleton($name, $metaReturn, $anonymousSet);
954
        assert($singleton->isOK($msg), $msg);
955
        $type = new ResourceFunctionType($functionName, $singleton, $returnType);
956
        // Since singletons should take no args, enforce it here
957
        assert(0 == count($type->getParms()));
958
        $this->singletons[$name] = $type;
959
    }
960
961
    public function getSingletons()
962
    {
963
        return $this->singletons;
964
    }
965
966
    public function callSingleton($name)
967
    {
968
        if (!array_key_exists($name, $this->singletons)) {
969
            $msg = 'Requested singleton does not exist';
970
            throw new \InvalidArgumentException($msg);
971
        }
972
973
        return $this->singletons[$name]->get();
974
    }
975
}
976