Test Setup Failed
Push — master ( 19756d...d44196 )
by Alex
04:02
created

SimpleMetadataProvider::resolveAssociationSet()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
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
30
    /**
31
     * @param string $containerName container name for the datasource
32
     * @param string $namespaceName namespace for the datasource
33
     */
34
    public function __construct($containerName, $namespaceName)
35
    {
36
        $this->containerName = $containerName;
37
        $this->namespaceName = $namespaceName;
38
        $this->metadataManager = new MetadataManager($namespaceName, $containerName);
39
    }
40
41
    //Begin Implementation of IMetadataProvider
42
43
    public function getXML()
44
    {
45
        return $this->metadataManager->getEdmxXML();
46
    }
47
48
    /**
49
     * get the Container name for the data source.
50
     *
51
     * @return string container name
52
     */
53
    public function getContainerName()
54
    {
55
        return $this->containerName;
56
    }
57
58
    /**
59
     * get Namespace name for the data source.
60
     *
61
     * @return string namespace
62
     */
63
    public function getContainerNamespace()
64
    {
65
        return $this->namespaceName;
66
    }
67
68
    /**
69
     * get all entity set information.
70
     *
71
     * @return ResourceSet[]
72
     */
73
    public function getResourceSets($params = null)
74
    {
75
        $parameters = [];
76
        if (is_string($params)) {
77
            $parameters[] = $params;
78
        } elseif (isset($params) && !is_array($params)) {
79
            throw new \ErrorException('Input parameter must be absent, null, string or array');
80
        } else {
81
            $parameters = $params;
82
        }
83
        if (!is_array($parameters) || 0 == count($parameters)) {
84
            return array_values($this->resourceSets);
85
        }
86
        assert(is_array($parameters));
87
        $return = [];
88
        $counter = 0;
89
        foreach ($this->resourceSets as $resource) {
90
            $resName = $resource->getName();
91
            if (in_array($resName, $parameters)) {
92
                $return[] = $resource;
93
                $counter++;
94
            }
95
        }
96
        assert($counter == count($return));
97
98
        return $return;
99
    }
100
101
    /**
102
     * get all resource types in the data source.
103
     *
104
     * @return ResourceType[]
105
     */
106
    public function getTypes()
107
    {
108
        return array_values($this->resourceTypes);
109
    }
110
111
    /**
112
     * get a resource set based on the specified resource set name.
113
     *
114
     * @param string $name Name of the resource set
115
     *
116
     * @return ResourceSet|null resource set with the given name if found else NULL
117
     */
118
    public function resolveResourceSet($name)
119
    {
120
        if (array_key_exists($name, $this->resourceSets)) {
121
            return $this->resourceSets[$name];
122
        }
123
        return null;
124
    }
125
126
    /**
127
     * get a resource type based on the resource type name.
128
     *
129
     * @param string $name Name of the resource type
130
     *
131
     * @return ResourceType|null resource type with the given resource type name if found else NULL
132
     */
133
    public function resolveResourceType($name)
134
    {
135
        if (array_key_exists($name, $this->resourceTypes)) {
136
            return $this->resourceTypes[$name];
137
        }
138
        return null;
139
    }
140
141
    /**
142
     * get a singelton based on the specified singleton name.
143
     *
144
     * @param string $name Name of the resource set
145
     *
146
     * @return ResourceFunctionType|null    singleton with the given name if found else NULL
147
     */
148
    public function resolveSingleton($name)
149
    {
150
        if (array_key_exists($name, $this->singletons)) {
151
            return $this->singletons[$name];
152
        }
153
        return null;
154
    }
155
156
    /**
157
     * get a resource set based on the specified resource association set name.
158
     *
159
     * @param string $name Name of the resource assocation set
160
     *
161
     * @return ResourceAssociationSet|null resource association set with the given name if found else NULL
162
     */
163
    public function resolveAssociationSet($name)
164
    {
165
        if (array_key_exists($name, $this->associationSets)) {
166
            return $this->associationSets[$name];
167
        }
168
        return null;
169
    }
170
171
    /*
172
     * Get number of association sets hooked up
173
     */
174
    public function getAssociationCount()
175
    {
176
        return count($this->associationSets);
177
    }
178
179
    /**
180
     * The method must return a collection of all the types derived from
181
     * $resourceType The collection returned should NOT include the type
182
     * passed in as a parameter.
183
     *
184
     * @param ResourceEntityType $resourceType Resource to get derived resource types from
185
     *
186
     * @return ResourceType[]
187
     */
188
    public function getDerivedTypes(ResourceEntityType $resourceType)
189
    {
190
        return [];
191
    }
192
193
    /**
194
     * @param ResourceType $resourceType Resource to check for derived resource types
195
     *
196
     * @return bool true if $resourceType represents an Entity Type which has derived Entity Types, else false
197
     */
198
    public function hasDerivedTypes(ResourceEntityType $resourceType)
199
    {
200
        return false;
201
    }
202
203
    //End Implementation of IMetadataProvider
204
205
    /**
206
     * Gets the ResourceAssociationSet instance for the given source
207
     * association end.
208
     *
209
     * @param ResourceSet      $sourceResourceSet      Resource set
210
     *                                                 of the source
211
     *                                                 association end
212
     * @param ResourceType     $sourceResourceType     Resource type of the source
213
     *                                                 association end
214
     * @param ResourceProperty $targetResourceProperty Resource property of
215
     *                                                 the source
216
     *                                                 association end
217
     *
218
     * @return ResourceAssociationSet|null
219
     */
220
    public function getResourceAssociationSet(
221
        ResourceSet $sourceResourceSet,
222
        ResourceEntityType $sourceResourceType,
223
        ResourceProperty $targetResourceProperty
224
    ) {
225
        //e.g.
226
        //ResourceSet => Representing 'Customers' entity set
227
        //ResourceType => Representing'Customer' entity type
228
        //ResourceProperty => Representing 'Orders' property
229
        //We have created ResourceAssoicationSet while adding
230
        //ResourceSetReference or ResourceReference
231
        //and kept in $this->associationSets
232
        //$metadata->addResourceSetReferenceProperty(
233
        //             $customersEntityType,
234
        //             'Orders',
235
        //             $ordersResourceSet
236
        //             );
237
238
        $targetResourceSet = $targetResourceProperty->getResourceType()->getCustomState();
239
        if (is_null($targetResourceSet)) {
240
            throw new InvalidOperationException(
241
                'Failed to retrieve the custom state from ' . $targetResourceProperty->getResourceType()->getName()
242
            );
243
        }
244
245
        //Customer_Orders_Orders, Order_Customer_Customers
246
        $key = ResourceAssociationSet::keyName(
247
            $sourceResourceType,
248
            $targetResourceProperty->getName(),
249
            $targetResourceSet
250
        );
251
252
        $associationSet = array_key_exists($key, $this->associationSets) ? $this->associationSets[$key] : null;
253
        assert(
254
            null == $associationSet || $associationSet instanceof ResourceAssociationSet,
255
            "Retrieved resource assocation must be either null or an instance of ResourceAssociationSet"
256
        );
257
        return $associationSet;
258
    }
259
260
    /**
261
     * Add an entity type.
262
     *
263
     * @param \ReflectionClass $refClass reflection class of the entity
264
     * @param string $name name of the entity
265
     * @return ResourceType when the name is already in use
266
     *
267
     * @throws InvalidOperationException when the name is already in use
268
     * @internal param string $namespace namespace of the data source
269
     *
270
     */
271
    public function addEntityType(\ReflectionClass $refClass, $name)
272
    {
273
        return $this->createResourceType($refClass, $name, ResourceTypeKind::ENTITY);
274
    }
275
276
    /**
277
     * @param \ReflectionClass $refClass
278
     * @param string $name
279
     * @param $typeKind
280
     * @return ResourceType
281
     * @throws InvalidOperationException
282
     * @internal param null|string $namespace
283
     * @internal param null|ResourceType $baseResourceType
284
     *
285
     */
286
    private function createResourceType(
287
        \ReflectionClass $refClass,
288
        $name,
289
        $typeKind
290
    ) {
291
        if (array_key_exists($name, $this->resourceTypes)) {
292
            throw new InvalidOperationException('Type with same name already added');
293
        }
294
295
        $type = null;
296
        if ($typeKind == ResourceTypeKind::ENTITY) {
297
            list($oet, $entitySet) = $this->metadataManager->addEntityType($name);
298
            assert($oet instanceof TEntityTypeType, "Entity type ".$name. " not successfully added");
299
            $type = new ResourceEntityType($refClass, $oet, $this);
300
            $typeName = $type->getFullName();
301
            $returnName = Str::plural($typeName);
302
            $this->OdataEntityMap[$typeName] = $oet;
303
            $this->typeSetMapping[$name] = $entitySet;
304
            $this->typeSetMapping[$typeName] = $entitySet;
305
            $this->typeSetMapping[$returnName] = $entitySet;
306
        } elseif ($typeKind == ResourceTypeKind::COMPLEX) {
307
            $complex = new TComplexTypeType();
308
            $complex->setName($name);
309
            $type = new ResourceComplexType($refClass, $complex);
310
        }
311
        assert(null != $type, "Type variable must not be null");
312
313
        $this->resourceTypes[$name] = $type;
314
        ksort($this->resourceTypes);
315
316
        return $type;
317
    }
318
319
    /**
320
     * Add a complex type.
321
     *
322
     * @param \ReflectionClass $refClass reflection class of the complex entity type
323
     * @param string $name name of the entity
324
     * @return ResourceType when the name is already in use
325
     *
326
     * @throws InvalidOperationException when the name is already in use
327
     * @internal param string $namespace namespace of the data source
328
     * @internal param ResourceType $baseResourceType base resource type
329
     *
330
     */
331
    public function addComplexType(\ReflectionClass $refClass, $name)
332
    {
333
        return $this->createResourceType($refClass, $name, ResourceTypeKind::COMPLEX);
334
    }
335
336
    /**
337
     * @param string                $name           name of the resource set (now taken from resource type)
338
     * @param ResourceEntityType    $resourceType   resource type
339
     *
340
     * @throws InvalidOperationException
341
     *
342
     * @return ResourceSet
343
     */
344
    public function addResourceSet($name, ResourceEntityType $resourceType)
345
    {
346
        $returnName = Str::plural($resourceType->getFullName());
347
        if (array_key_exists($returnName, $this->resourceSets)) {
348
            throw new InvalidOperationException('Resource Set already added');
349
        }
350
351
        $this->resourceSets[$returnName] = new ResourceSet($returnName, $resourceType);
352
353
        //No support for multiple ResourceSet with same EntityType
354
        //So keeping reference to the 'ResourceSet' with the entity type
355
        $resourceType->setCustomState($this->resourceSets[$returnName]);
356
        ksort($this->resourceSets);
357
358
        return $this->resourceSets[$returnName];
359
    }
360
361
    /**
362
     * To add a Key-primitive property to a resource (Complex/Entity).
363
     *
364
     * @param ResourceType $resourceType resource type to which key property
365
     *                                   is to be added
366
     * @param string       $name         name of the key property
367
     * @param TypeCode     $typeCode     type of the key property
368
     */
369
    public function addKeyProperty($resourceType, $name, $typeCode)
370
    {
371
        $this->_addPrimitivePropertyInternal($resourceType, $name, $typeCode, true);
372
    }
373
374
    /**
375
     * To add a Key/NonKey-primitive property to a resource (complex/entity).
376
     *
377
     * @param ResourceType $resourceType   Resource type
378
     * @param string       $name           name of the property
379
     * @param TypeCode     $typeCode       type of property
380
     * @param bool         $isKey          property is key or not
381
     * @param bool         $isBag          property is bag or not
382
     * @param bool         $isETagProperty property is etag or not
383
     */
384
    private function _addPrimitivePropertyInternal(
385
        $resourceType,
386
        $name,
387
        $typeCode,
388
        $isKey = false,
389
        $isBag = false,
390
        $isETagProperty = false,
391
        $defaultValue = null,
392
        $nullable = false
393
    ) {
394
        $this->checkInstanceProperty($name, $resourceType);
395
396
        // check that property and resource name don't up and collide - would violate OData spec
397
        if (strtolower($name) == strtolower($resourceType->getName())) {
398
            throw new InvalidOperationException(
399
                'Property name must be different from resource name.'
400
            );
401
        }
402
403
        $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...
404
405
        if ($isETagProperty && $isBag) {
406
            throw new InvalidOperationException(
407
                'Only primitve property can be etag property, bag property cannot be etag property.'
408
            );
409
        }
410
411
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
412
        if ($isBag) {
413
            $kind = $kind | ResourcePropertyKind::BAG;
414
        }
415
416
        if ($isETagProperty) {
417
            $kind = $kind | ResourcePropertyKind::ETAG;
418
        }
419
420
        $resourceProperty = new ResourceProperty($name, null, $kind, $primitiveResourceType);
421
        $resourceType->addProperty($resourceProperty);
422
        if (array_key_exists($resourceType->getFullName(), $this->OdataEntityMap)) {
423
            $this->metadataManager->addPropertyToEntityType(
424
                $this->OdataEntityMap[$resourceType->getFullName()],
425
                $name,
426
                $primitiveResourceType->getFullName(),
427
                $defaultValue,
428
                $nullable,
429
                $isKey
430
            );
431
        }
432
    }
433
434
    /**
435
     * @param string $name
436
     * @param ResourceType $resourceType
437
     *
438
     * @throws InvalidOperationException
439
     */
440
    private function checkInstanceProperty($name, ResourceType $resourceType)
441
    {
442
        $instance = $resourceType->getInstanceType();
443
        $hasMagicGetter = $instance instanceof IType || $instance->hasMethod('__get');
444
445
        if (!$hasMagicGetter) {
446
            try {
447
                if ($instance instanceof \ReflectionClass) {
448
                    $instance->getProperty($name);
449
                }
450
            } catch (\ReflectionException $exception) {
451
                throw new InvalidOperationException(
452
                    'Can\'t add a property which does not exist on the instance type.'
453
                );
454
            }
455
        }
456
    }
457
458
    /**
459
     * To add a NonKey-primitive property (Complex/Entity).
460
     *
461
     * @param ResourceType $resourceType resource type to which key property
462
     *                                   is to be added
463
     * @param string $name name of the key property
464
     * @param TypeCode $typeCode type of the key property
465
     * @param bool $isBag property is bag or not
466
     */
467 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...
468
        $resourceType,
469
        $name,
470
        $typeCode,
471
        $isBag = false,
472
        $defaultValue = null,
473
        $nullable = false
474
    ) {
475
        $this->_addPrimitivePropertyInternal(
476
            $resourceType,
477
            $name,
478
            $typeCode,
479
            false,
480
            $isBag,
481
            false,
482
            $defaultValue,
483
            $nullable
484
        );
485
    }
486
487
    /**
488
     * To add a non-key etag property.
489
     *
490
     * @param ResourceType $resourceType resource type to which key property
491
     *                                   is to be added
492
     * @param string $name name of the property
493
     * @param TypeCode $typeCode type of the etag property
494
     */
495 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...
496
    {
497
        $this->_addPrimitivePropertyInternal(
498
            $resourceType,
499
            $name,
500
            $typeCode,
501
            false,
502
            false,
503
            true,
504
            $defaultValue,
505
            $nullable
506
        );
507
    }
508
509
    /**
510
     * To add a resource reference property.
511
     *
512
     * @param ResourceEntityType    $resourceType   The resource type to add the resource
513
     *                                              reference property to
514
     * @param string                $name           The name of the property to add
515
     * @param ResourceSet           $targetResourceSet The resource set the resource reference
516
     *                                               property points to
517
     */
518
    public function addResourceReferenceProperty($resourceType, $name, $targetResourceSet)
519
    {
520
        $this->_addReferencePropertyInternal(
521
            $resourceType,
522
            $name,
523
            $targetResourceSet,
524
            '0..1'
525
        );
526
    }
527
528
    /**
529
     * To add a 1:N resource reference property.
530
     *
531
     * @param ResourceType $sourceResourceType  The resource type to add the resource
532
     *                                          reference property from
533
     * @param ResourceType $targetResourceType  The resource type to add the resource
534
     *                                          reference property to
535
     * @param string $sourceProperty            The name of the property to add, on source type
536
     * @param string $targetProperty            The name of the property to add, on target type
537
     */
538
    public function addResourceReferencePropertyBidirectional(
539
        ResourceType $sourceResourceType,
540
        ResourceType $targetResourceType,
541
        $sourceProperty,
542
        $targetProperty
543
    ) {
544
        $this->_addReferencePropertyInternalBidirectional(
545
            $sourceResourceType,
0 ignored issues
show
Compatibility introduced by
$sourceResourceType of type object<POData\Providers\Metadata\ResourceType> is not a sub-type of object<POData\Providers\...ata\ResourceEntityType>. It seems like you assume a child class of the class POData\Providers\Metadata\ResourceType to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
546
            $targetResourceType,
0 ignored issues
show
Compatibility introduced by
$targetResourceType of type object<POData\Providers\Metadata\ResourceType> is not a sub-type of object<POData\Providers\...ata\ResourceEntityType>. It seems like you assume a child class of the class POData\Providers\Metadata\ResourceType to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
547
            $sourceProperty,
548
            $targetProperty,
549
            '1',
550
            '*'
551
        );
552
    }
553
554
    /**
555
     * To add a navigation property (resource set or resource reference)
556
     * to a resource type.
557
     *
558
     * @param ResourceEntityType   $sourceResourceType   The resource type to add
559
     *                                                   the resource reference
560
     *                                                   or resource
561
     *                                                   reference set property to
562
     * @param string               $name                 The name of the
563
     *                                                   property to add
564
     * @param ResourceSet          $targetResourceSet    The resource set the
565
     *                                                   resource reference
566
     *                                                   or reference
567
     *                                                   set property
568
     *                                                   points to
569
     * @param string               $resourceMult         The multiplicity of relation being added
570
     */
571
    private function _addReferencePropertyInternal(
572
        ResourceEntityType $sourceResourceType,
573
        $name,
574
        ResourceSet $targetResourceSet,
575
        $resourceMult
576
    ) {
577
        $allowedMult = ['*', '1', '0..1'];
578
        $this->checkInstanceProperty($name, $sourceResourceType);
579
580
        // check that property and resource name don't up and collide - would violate OData spec
581
        if (strtolower($name) == strtolower($sourceResourceType->getName())) {
582
            throw new InvalidOperationException(
583
                'Property name must be different from resource name.'
584
            );
585
        }
586
        if (!in_array($resourceMult, $allowedMult)) {
587
            throw new InvalidOperationException("Supplied multiplicity ".$resourceMult." not valid");
588
        }
589
590
        $resourcePropertyKind = ('*' == $resourceMult)
591
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
592
            : ResourcePropertyKind::RESOURCE_REFERENCE;
593
        $targetResourceType = $targetResourceSet->getResourceType();
594
        $sourceResourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
595
        $sourceResourceType->addProperty($sourceResourceProperty);
596
597
        //Create instance of AssociationSet for this relationship
598
        $sourceResourceSet = $sourceResourceType->getCustomState();
599
        if (!$sourceResourceSet instanceof ResourceSet) {
600
            throw new InvalidOperationException(
601
                'Failed to retrieve the custom state from '
602
                . $sourceResourceType->getName()
603
            );
604
        }
605
606
        //Customer_Orders_Orders, Order_Customer_Customers
607
        //(source type::name _ source property::name _ target set::name)
608
        $setKey = ResourceAssociationSet::keyName($sourceResourceType, $name, $targetResourceSet);
609
        //$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...
610
        $set = new ResourceAssociationSet(
611
            $setKey,
612
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
613
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, null)
614
        );
615
        $mult = $resourceMult;
616
        $backMult = '*' == $resourceMult ? '*' : '1';
617
        $this->metadataManager->addNavigationPropertyToEntityType(
618
            $this->OdataEntityMap[$sourceResourceType->getFullName()],
619
            $mult,
620
            $name,
621
            $this->OdataEntityMap[$targetResourceType->getFullName()],
622
            $backMult
623
        );
624
        $this->associationSets[$setKey] = $set;
625
    }
626
627
    /**
628
     * To add a navigation property (resource set or resource reference)
629
     * to a resource type.
630
     *
631
     * @param ResourceEntityType   $sourceResourceType   The source resource type to add
632
     *                                                   the resource reference
633
     *                                                   or resource reference set property to
634
     * @param ResourceEntityType   $targetResourceType   The target resource type to add
635
     *                                                   the resource reference
636
     *                                                   or resource reference set property to
637
     * @param string               $sourceProperty       The name of the
638
     *                                                   property to add to source type
639
     * @param string               $targetProperty       The name of the
640
     *                                                   property to add to target type
641
     * @param string               $sourceMultiplicity   The multiplicity at the source end of relation
642
     * @param string               $targetMultiplicity   The multiplicity at the target end of relation
643
     */
644
    private function _addReferencePropertyInternalBidirectional(
645
        ResourceEntityType $sourceResourceType,
646
        ResourceEntityType $targetResourceType,
647
        $sourceProperty,
648
        $targetProperty,
649
        $sourceMultiplicity,
650
        $targetMultiplicity
651
    ) {
652
        if (!is_string($sourceProperty) || !is_string($targetProperty)) {
653
            throw new InvalidOperationException("Source and target properties must both be strings");
654
        }
655
656
        if (!is_string($sourceMultiplicity) || !is_string($targetMultiplicity)) {
657
            throw new InvalidOperationException("Source and target multiplicities must both be strings");
658
        }
659
660
        $this->checkInstanceProperty($sourceProperty, $sourceResourceType);
661
        $this->checkInstanceProperty($targetProperty, $targetResourceType);
662
663
        // check that property and resource name don't up and collide - would violate OData spec
664
        if (strtolower($sourceProperty) == strtolower($sourceResourceType->getName())) {
665
            throw new InvalidOperationException(
666
                'Source property name must be different from source resource name.'
667
            );
668
        }
669
        if (strtolower($targetProperty) == strtolower($targetResourceType->getName())) {
670
            throw new InvalidOperationException(
671
                'Target property name must be different from target resource name.'
672
            );
673
        }
674
675
        //Create instance of AssociationSet for this relationship
676
        $sourceResourceSet = $sourceResourceType->getCustomState();
677
        if (!$sourceResourceSet instanceof ResourceSet) {
678
            throw new InvalidOperationException(
679
                'Failed to retrieve the custom state from '
680
                . $sourceResourceType->getName()
681
            );
682
        }
683
        $targetResourceSet = $targetResourceType->getCustomState();
684
        if (!$targetResourceSet instanceof ResourceSet) {
685
            throw new InvalidOperationException(
686
                'Failed to retrieve the custom state from '
687
                . $targetResourceType->getName()
688
            );
689
        }
690
691
        //Customer_Orders_Orders, Order_Customer_Customers
692
        $fwdSetKey = ResourceAssociationSet::keyName($sourceResourceType, $sourceProperty, $targetResourceSet);
693
        $revSetKey = ResourceAssociationSet::keyName($targetResourceType, $targetProperty, $sourceResourceSet);
694
        if (isset($this->associationSets[$fwdSetKey]) && $this->associationSets[$revSetKey]) {
695
            return;
696
        }
697
        $sourceKind = ('*' == $sourceMultiplicity)
698
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
699
            : ResourcePropertyKind::RESOURCE_REFERENCE;
700
        $targetKind = ('*' == $targetMultiplicity)
701
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
702
            : ResourcePropertyKind::RESOURCE_REFERENCE;
703
704
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, $sourceKind, $targetResourceType);
705
        $sourceResourceType->addProperty($sourceResourceProperty, false);
706
        $targetResourceProperty = new ResourceProperty($targetProperty, null, $targetKind, $sourceResourceType);
707
        $targetResourceType->addProperty($targetResourceProperty, false);
708
709
710
        $fwdSet = new ResourceAssociationSet(
711
            $fwdSetKey,
712
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
713
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty)
714
        );
715
        $revSet = new ResourceAssociationSet(
716
            $revSetKey,
717
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty),
718
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty)
719
        );
720
        $sourceName = $sourceResourceType->getFullName();
721
        $targetName = $targetResourceType->getFullName();
722
        $this->metadataManager->addNavigationPropertyToEntityType(
723
            $this->OdataEntityMap[$sourceName],
724
            $sourceMultiplicity,
725
            $sourceProperty,
726
            $this->OdataEntityMap[$targetName],
727
            $targetMultiplicity,
728
            $targetProperty
729
        );
730
        $this->associationSets[$fwdSetKey] = $fwdSet;
731
        $this->associationSets[$revSetKey] = $revSet;
732
    }
733
734
    /**
735
     * To add a resource set reference property.
736
     *
737
     * @param ResourceEntityType    $resourceType   The resource type to add the
738
     *                                              resource reference set property to
739
     * @param string                $name           The name of the property to add
740
     * @param ResourceSet           $targetResourceSet The resource set the resource
741
     *                                              reference set property points to
742
     */
743
    public function addResourceSetReferenceProperty(ResourceEntityType $resourceType, $name, $targetResourceSet)
744
    {
745
        $this->_addReferencePropertyInternal(
746
            $resourceType,
747
            $name,
748
            $targetResourceSet,
749
            '*'
750
        );
751
    }
752
753
    /**
754
     * To add a M:N resource reference property.
755
     *
756
     * @param ResourceEntityType    $sourceResourceType     The resource type to add the resource
757
     *                                                      reference property from
758
     * @param ResourceEntityType    $targetResourceType     The resource type to add the resource
759
     *                                                      reference property to
760
     * @param string                $sourceProperty         The name of the property to add, on source type
761
     * @param string                $targetProperty         The name of the property to add, on target type
762
     */
763
    public function addResourceSetReferencePropertyBidirectional(
764
        ResourceEntityType $sourceResourceType,
765
        ResourceEntityType $targetResourceType,
766
        $sourceProperty,
767
        $targetProperty
768
    ) {
769
        $this->_addReferencePropertyInternalBidirectional(
770
            $sourceResourceType,
771
            $targetResourceType,
772
            $sourceProperty,
773
            $targetProperty,
774
            '*',
775
            '*'
776
        );
777
    }
778
779
    /**
780
     * To add a 1-1 resource reference.
781
     *
782
     * @param ResourceEntityType    $sourceResourceType     The resource type to add the resource
783
     *                                                      reference property from
784
     * @param ResourceEntityType    $targetResourceType     The resource type to add the resource
785
     *                                                      reference property to
786
     * @param string                $sourceProperty         The name of the property to add, on source type
787
     * @param string                $targetProperty         The name of the property to add, on target type
788
     */
789
    public function addResourceReferenceSinglePropertyBidirectional(
790
        ResourceEntityType $sourceResourceType,
791
        ResourceEntityType $targetResourceType,
792
        $sourceProperty,
793
        $targetProperty
794
    ) {
795
        $this->_addReferencePropertyInternalBidirectional(
796
            $sourceResourceType,
797
            $targetResourceType,
798
            $sourceProperty,
799
            $targetProperty,
800
            '1',
801
            '0..1'
802
        );
803
    }
804
805
    /**
806
     * To add a complex property to entity or complex type.
807
     *
808
     * @param ResourceType          $targetResourceType     The resource type to which the complex property needs to add
809
     * @param string                $name                   name of the complex property
810
     * @param ResourceComplexType   $complexResourceType    complex resource type
811
     * @param bool                  $isBag                  complex type is bag or not
812
     *
813
     * @return ResourceProperty
814
     */
815
    public function addComplexProperty(
816
        ResourceType $targetResourceType,
817
        $name,
818
        ResourceComplexType $complexResourceType,
819
        $isBag = false
820
    ) {
821
        if ($targetResourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY
822
            && $targetResourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX
823
        ) {
824
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
825
        }
826
827
        // check that property and resource name don't up and collide - would violate OData spec
828
        if (strtolower($name) == strtolower($targetResourceType->getName())) {
829
            throw new InvalidOperationException(
830
                'Property name must be different from resource name.'
831
            );
832
        }
833
834
        $this->checkInstanceProperty($name, $targetResourceType);
835
836
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
837
        if ($isBag) {
838
            $kind = $kind | ResourcePropertyKind::BAG;
839
        }
840
841
        $resourceProperty = new ResourceProperty($name, null, $kind, $complexResourceType);
842
        $targetResourceType->addProperty($resourceProperty);
843
844
        return $resourceProperty;
845
    }
846
847
    public function createSingleton($name, ResourceType $returnType, $functionName)
848
    {
849
        $msg = null;
850
        if (array_key_exists($name, $this->singletons)) {
851
            $msg = "Singleton name already exists";
852
            throw new \InvalidArgumentException($msg);
853
        }
854
        if (array_key_exists($name, $this->resourceSets)) {
855
            $msg = "Resource set with same name, ". $name. ", exists";
856
            throw new \InvalidArgumentException($msg);
857
        }
858
        $typeName = $returnType->getName();
859
        if (!array_key_exists($typeName, $this->OdataEntityMap)) {
860
            $msg = "Mapping not defined for ".$typeName;
861
            throw new \InvalidArgumentException($msg);
862
        }
863
        $metaReturn = $this->OdataEntityMap[$typeName];
864
        $singleton = $this->metadataManager->createSingleton($name, $metaReturn);
0 ignored issues
show
Bug introduced by
The method createSingleton() does not seem to exist on object<AlgoWeb\ODataMetadata\MetadataManager>.

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

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

Loading history...
865
        assert($singleton->isOK($msg), $msg);
866
        $type = new ResourceFunctionType($functionName, $singleton);
867
        // Since singletons should take no args, enforce it here
868
        assert(0 == count($type->getParms()));
869
        $this->singletons[$name] = $type;
870
    }
871
872
    public function getSingletons()
873
    {
874
        return $this->singletons;
875
    }
876
877
    public function callSingleton($name)
878
    {
879
        if (!array_key_exists($name, $this->singletons)) {
880
            $msg = "Requested singleton does not exist";
881
            throw new \InvalidArgumentException($msg);
882
        }
883
884
        return $this->singletons[$name]->get();
885
    }
886
}
887