Test Setup Failed
Push — master ( 309513...6d83cd )
by Alex
56s
created

SimpleMetadataProvider::createResourceType()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 32
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 1
Metric Value
c 4
b 0
f 1
dl 0
loc 32
rs 8.5806
cc 4
eloc 25
nc 4
nop 3
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 resource set based on the specified resource association set name.
143
     *
144
     * @param string $name Name of the resource assocation set
145
     *
146
     * @return ResourceAssociationSet|null resource association set with the given name if found else NULL
147
     */
148
    public function resolveAssociationSet($name)
149
    {
150
        if (array_key_exists($name, $this->associationSets)) {
151
            return $this->associationSets[$name];
152
        }
153
        return null;
154
    }
155
156
    /*
157
     * Get number of association sets hooked up
158
     */
159
    public function getAssociationCount()
160
    {
161
        return count($this->associationSets);
162
    }
163
164
    /**
165
     * The method must return a collection of all the types derived from
166
     * $resourceType The collection returned should NOT include the type
167
     * passed in as a parameter.
168
     *
169
     * @param ResourceEntityType $resourceType Resource to get derived resource types from
170
     *
171
     * @return ResourceType[]
172
     */
173
    public function getDerivedTypes(ResourceEntityType $resourceType)
174
    {
175
        return [];
176
    }
177
178
    /**
179
     * @param ResourceType $resourceType Resource to check for derived resource types
180
     *
181
     * @return bool true if $resourceType represents an Entity Type which has derived Entity Types, else false
182
     */
183
    public function hasDerivedTypes(ResourceEntityType $resourceType)
184
    {
185
        return false;
186
    }
187
188
    //End Implementation of IMetadataProvider
189
190
    /**
191
     * Gets the ResourceAssociationSet instance for the given source
192
     * association end.
193
     *
194
     * @param ResourceSet      $sourceResourceSet      Resource set
195
     *                                                 of the source
196
     *                                                 association end
197
     * @param ResourceType     $sourceResourceType     Resource type of the source
198
     *                                                 association end
199
     * @param ResourceProperty $targetResourceProperty Resource property of
200
     *                                                 the source
201
     *                                                 association end
202
     *
203
     * @return ResourceAssociationSet|null
204
     */
205
    public function getResourceAssociationSet(
206
        ResourceSet $sourceResourceSet,
207
        ResourceEntityType $sourceResourceType,
208
        ResourceProperty $targetResourceProperty
209
    ) {
210
        //e.g.
211
        //ResourceSet => Representing 'Customers' entity set
212
        //ResourceType => Representing'Customer' entity type
213
        //ResourceProperty => Representing 'Orders' property
214
        //We have created ResourceAssoicationSet while adding
215
        //ResourceSetReference or ResourceReference
216
        //and kept in $this->associationSets
217
        //$metadata->addResourceSetReferenceProperty(
218
        //             $customersEntityType,
219
        //             'Orders',
220
        //             $ordersResourceSet
221
        //             );
222
223
        $targetResourceSet = $targetResourceProperty->getResourceType()->getCustomState();
224
        if (is_null($targetResourceSet)) {
225
            throw new InvalidOperationException(
226
                'Failed to retrieve the custom state from ' . $targetResourceProperty->getResourceType()->getName()
227
            );
228
        }
229
230
        //Customer_Orders_Orders, Order_Customer_Customers
231
        $key = ResourceAssociationSet::keyName(
232
            $sourceResourceType,
233
            $targetResourceProperty->getName(),
234
            $targetResourceSet
235
        );
236
237
        $associationSet = array_key_exists($key, $this->associationSets) ? $this->associationSets[$key] : null;
238
        assert(
239
            null == $associationSet || $associationSet instanceof ResourceAssociationSet,
240
            "Retrieved resource assocation must be either null or an instance of ResourceAssociationSet"
241
        );
242
        return $associationSet;
243
    }
244
245
    /**
246
     * Add an entity type.
247
     *
248
     * @param \ReflectionClass $refClass reflection class of the entity
249
     * @param string $name name of the entity
250
     * @return ResourceType when the name is already in use
251
     *
252
     * @throws InvalidOperationException when the name is already in use
253
     * @internal param string $namespace namespace of the data source
254
     *
255
     */
256
    public function addEntityType(\ReflectionClass $refClass, $name)
257
    {
258
        return $this->createResourceType($refClass, $name, ResourceTypeKind::ENTITY);
259
    }
260
261
    /**
262
     * @param \ReflectionClass $refClass
263
     * @param string $name
264
     * @param $typeKind
265
     * @return ResourceType
266
     * @throws InvalidOperationException
267
     * @internal param null|string $namespace
268
     * @internal param null|ResourceType $baseResourceType
269
     *
270
     */
271
    private function createResourceType(
272
        \ReflectionClass $refClass,
273
        $name,
274
        $typeKind
275
    ) {
276
        if (array_key_exists($name, $this->resourceTypes)) {
277
            throw new InvalidOperationException('Type with same name already added');
278
        }
279
280
        $type = null;
281
        if ($typeKind == ResourceTypeKind::ENTITY) {
282
            list($oet, $entitySet) = $this->metadataManager->addEntityType($name);
283
            assert($oet instanceof TEntityTypeType, "Entity type ".$name. " not successfully added");
284
            $type = new ResourceEntityType($refClass, $oet, $this);
285
            $typeName = $type->getFullName();
286
            $returnName = Str::plural($typeName);
287
            $this->OdataEntityMap[$typeName] = $oet;
288
            $this->typeSetMapping[$name] = $entitySet;
289
            $this->typeSetMapping[$typeName] = $entitySet;
290
            $this->typeSetMapping[$returnName] = $entitySet;
291
        } elseif ($typeKind == ResourceTypeKind::COMPLEX) {
292
            $complex = new TComplexTypeType();
293
            $complex->setName($name);
294
            $type = new ResourceComplexType($refClass, $complex);
295
        }
296
        assert(null != $type, "Type variable must not be null");
297
298
        $this->resourceTypes[$name] = $type;
299
        ksort($this->resourceTypes);
300
301
        return $type;
302
    }
303
304
    /**
305
     * Add a complex type.
306
     *
307
     * @param \ReflectionClass $refClass reflection class of the complex entity type
308
     * @param string $name name of the entity
309
     * @return ResourceType when the name is already in use
310
     *
311
     * @throws InvalidOperationException when the name is already in use
312
     * @internal param string $namespace namespace of the data source
313
     * @internal param ResourceType $baseResourceType base resource type
314
     *
315
     */
316
    public function addComplexType(\ReflectionClass $refClass, $name)
317
    {
318
        return $this->createResourceType($refClass, $name, ResourceTypeKind::COMPLEX);
319
    }
320
321
    /**
322
     * @param string                $name           name of the resource set (now taken from resource type)
323
     * @param ResourceEntityType    $resourceType   resource type
324
     *
325
     * @throws InvalidOperationException
326
     *
327
     * @return ResourceSet
328
     */
329
    public function addResourceSet($name, ResourceEntityType $resourceType)
330
    {
331
        $returnName = Str::plural($resourceType->getFullName());
332
        if (array_key_exists($returnName, $this->resourceSets)) {
333
            throw new InvalidOperationException('Resource Set already added');
334
        }
335
336
        $this->resourceSets[$returnName] = new ResourceSet($returnName, $resourceType);
337
338
        //No support for multiple ResourceSet with same EntityType
339
        //So keeping reference to the 'ResourceSet' with the entity type
340
        $resourceType->setCustomState($this->resourceSets[$returnName]);
341
        ksort($this->resourceSets);
342
343
        return $this->resourceSets[$returnName];
344
    }
345
346
    /**
347
     * To add a Key-primitive property to a resource (Complex/Entity).
348
     *
349
     * @param ResourceType $resourceType resource type to which key property
350
     *                                   is to be added
351
     * @param string       $name         name of the key property
352
     * @param TypeCode     $typeCode     type of the key property
353
     */
354
    public function addKeyProperty($resourceType, $name, $typeCode)
355
    {
356
        $this->_addPrimitivePropertyInternal($resourceType, $name, $typeCode, true);
357
    }
358
359
    /**
360
     * To add a Key/NonKey-primitive property to a resource (complex/entity).
361
     *
362
     * @param ResourceType $resourceType   Resource type
363
     * @param string       $name           name of the property
364
     * @param TypeCode     $typeCode       type of property
365
     * @param bool         $isKey          property is key or not
366
     * @param bool         $isBag          property is bag or not
367
     * @param bool         $isETagProperty property is etag or not
368
     */
369
    private function _addPrimitivePropertyInternal(
370
        $resourceType,
371
        $name,
372
        $typeCode,
373
        $isKey = false,
374
        $isBag = false,
375
        $isETagProperty = false,
376
        $defaultValue = null,
377
        $nullable = false
378
    ) {
379
        $this->checkInstanceProperty($name, $resourceType);
380
381
        // check that property and resource name don't up and collide - would violate OData spec
382
        if (strtolower($name) == strtolower($resourceType->getName())) {
383
            throw new InvalidOperationException(
384
                'Property name must be different from resource name.'
385
            );
386
        }
387
388
        $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...
389
390
        if ($isETagProperty && $isBag) {
391
            throw new InvalidOperationException(
392
                'Only primitve property can be etag property, bag property cannot be etag property.'
393
            );
394
        }
395
396
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
397
        if ($isBag) {
398
            $kind = $kind | ResourcePropertyKind::BAG;
399
        }
400
401
        if ($isETagProperty) {
402
            $kind = $kind | ResourcePropertyKind::ETAG;
403
        }
404
405
        $resourceProperty = new ResourceProperty($name, null, $kind, $primitiveResourceType);
406
        $resourceType->addProperty($resourceProperty);
407
        if (array_key_exists($resourceType->getFullName(), $this->OdataEntityMap)) {
408
            $this->metadataManager->addPropertyToEntityType(
409
                $this->OdataEntityMap[$resourceType->getFullName()],
410
                $name,
411
                $primitiveResourceType->getFullName(),
412
                $defaultValue,
413
                $nullable,
414
                $isKey
415
            );
416
        }
417
    }
418
419
    /**
420
     * @param string $name
421
     * @param ResourceType $resourceType
422
     *
423
     * @throws InvalidOperationException
424
     */
425
    private function checkInstanceProperty($name, ResourceType $resourceType)
426
    {
427
        $instance = $resourceType->getInstanceType();
428
        $hasMagicGetter = $instance instanceof IType || $instance->hasMethod('__get');
429
430
        if (!$hasMagicGetter) {
431
            try {
432
                if ($instance instanceof \ReflectionClass) {
433
                    $instance->getProperty($name);
434
                }
435
            } catch (\ReflectionException $exception) {
436
                throw new InvalidOperationException(
437
                    'Can\'t add a property which does not exist on the instance type.'
438
                );
439
            }
440
        }
441
    }
442
443
    /**
444
     * To add a NonKey-primitive property (Complex/Entity).
445
     *
446
     * @param ResourceType $resourceType resource type to which key property
447
     *                                   is to be added
448
     * @param string $name name of the key property
449
     * @param TypeCode $typeCode type of the key property
450
     * @param bool $isBag property is bag or not
451
     */
452 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...
453
        $resourceType,
454
        $name,
455
        $typeCode,
456
        $isBag = false,
457
        $defaultValue = null,
458
        $nullable = false
459
    ) {
460
        $this->_addPrimitivePropertyInternal(
461
            $resourceType,
462
            $name,
463
            $typeCode,
464
            false,
465
            $isBag,
466
            false,
467
            $defaultValue,
468
            $nullable
469
        );
470
    }
471
472
    /**
473
     * To add a non-key etag property.
474
     *
475
     * @param ResourceType $resourceType resource type to which key property
476
     *                                   is to be added
477
     * @param string $name name of the property
478
     * @param TypeCode $typeCode type of the etag property
479
     */
480 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...
481
    {
482
        $this->_addPrimitivePropertyInternal(
483
            $resourceType,
484
            $name,
485
            $typeCode,
486
            false,
487
            false,
488
            true,
489
            $defaultValue,
490
            $nullable
491
        );
492
    }
493
494
    /**
495
     * To add a resource reference property.
496
     *
497
     * @param ResourceEntityType    $resourceType   The resource type to add the resource
498
     *                                              reference property to
499
     * @param string                $name           The name of the property to add
500
     * @param ResourceSet           $targetResourceSet The resource set the resource reference
501
     *                                               property points to
502
     */
503
    public function addResourceReferenceProperty($resourceType, $name, $targetResourceSet)
504
    {
505
        $this->_addReferencePropertyInternal(
506
            $resourceType,
507
            $name,
508
            $targetResourceSet,
509
            '0..1'
510
        );
511
    }
512
513
    /**
514
     * To add a 1:N resource reference property.
515
     *
516
     * @param ResourceType $sourceResourceType  The resource type to add the resource
517
     *                                          reference property from
518
     * @param ResourceType $targetResourceType  The resource type to add the resource
519
     *                                          reference property to
520
     * @param string $sourceProperty            The name of the property to add, on source type
521
     * @param string $targetProperty            The name of the property to add, on target type
522
     */
523
    public function addResourceReferencePropertyBidirectional(
524
        ResourceType $sourceResourceType,
525
        ResourceType $targetResourceType,
526
        $sourceProperty,
527
        $targetProperty
528
    ) {
529
        $this->_addReferencePropertyInternalBidirectional(
530
            $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...
531
            $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...
532
            $sourceProperty,
533
            $targetProperty,
534
            '*',
535
            '1'
536
        );
537
    }
538
539
    /**
540
     * To add a navigation property (resource set or resource reference)
541
     * to a resource type.
542
     *
543
     * @param ResourceEntityType   $sourceResourceType   The resource type to add
544
     *                                                   the resource reference
545
     *                                                   or resource
546
     *                                                   reference set property to
547
     * @param string               $name                 The name of the
548
     *                                                   property to add
549
     * @param ResourceSet          $targetResourceSet    The resource set the
550
     *                                                   resource reference
551
     *                                                   or reference
552
     *                                                   set property
553
     *                                                   points to
554
     * @param string               $resourceMult         The multiplicity of relation being added
555
     */
556
    private function _addReferencePropertyInternal(
557
        ResourceEntityType $sourceResourceType,
558
        $name,
559
        ResourceSet $targetResourceSet,
560
        $resourceMult
561
    ) {
562
        $allowedMult = ['*', '1', '0..1'];
563
        $this->checkInstanceProperty($name, $sourceResourceType);
564
565
        // check that property and resource name don't up and collide - would violate OData spec
566
        if (strtolower($name) == strtolower($sourceResourceType->getName())) {
567
            throw new InvalidOperationException(
568
                'Property name must be different from resource name.'
569
            );
570
        }
571
        if (!in_array($resourceMult, $allowedMult)) {
572
            throw new InvalidOperationException("Supplied multiplicity ".$resourceMult." not valid");
573
        }
574
575
        $resourcePropertyKind = ('*' == $resourceMult)
576
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
577
            : ResourcePropertyKind::RESOURCE_REFERENCE;
578
        $targetResourceType = $targetResourceSet->getResourceType();
579
        $sourceResourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
580
        $sourceResourceType->addProperty($sourceResourceProperty);
581
582
        //Create instance of AssociationSet for this relationship
583
        $sourceResourceSet = $sourceResourceType->getCustomState();
584
        if (!$sourceResourceSet instanceof ResourceSet) {
585
            throw new InvalidOperationException(
586
                'Failed to retrieve the custom state from '
587
                . $sourceResourceType->getName()
588
            );
589
        }
590
591
        //Customer_Orders_Orders, Order_Customer_Customers
592
        //(source type::name _ source property::name _ target set::name)
593
        $setKey = ResourceAssociationSet::keyName($sourceResourceType, $name, $targetResourceSet);
594
        //$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...
595
        $set = new ResourceAssociationSet(
596
            $setKey,
597
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
598
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, null)
599
        );
600
        $mult = $resourceMult;
601
        $backMult = '*' == $resourceMult ? '*' : '1';
602
        $this->metadataManager->addNavigationPropertyToEntityType(
603
            $this->OdataEntityMap[$sourceResourceType->getFullName()],
604
            $mult,
605
            $name,
606
            $this->OdataEntityMap[$targetResourceType->getFullName()],
607
            $backMult
608
        );
609
        $this->associationSets[$setKey] = $set;
610
    }
611
612
    /**
613
     * To add a navigation property (resource set or resource reference)
614
     * to a resource type.
615
     *
616
     * @param ResourceEntityType   $sourceResourceType   The source resource type to add
617
     *                                                   the resource reference
618
     *                                                   or resource reference set property to
619
     * @param ResourceEntityType   $targetResourceType   The target resource type to add
620
     *                                                   the resource reference
621
     *                                                   or resource reference set property to
622
     * @param string               $sourceProperty       The name of the
623
     *                                                   property to add to source type
624
     * @param string               $targetProperty       The name of the
625
     *                                                   property to add to target type
626
     * @param string               $sourceMultiplicity   The multiplicity at the source end of relation
627
     * @param string               $targetMultiplicity   The multiplicity at the target end of relation
628
     */
629
    private function _addReferencePropertyInternalBidirectional(
630
        ResourceEntityType $sourceResourceType,
631
        ResourceEntityType $targetResourceType,
632
        $sourceProperty,
633
        $targetProperty,
634
        $sourceMultiplicity,
635
        $targetMultiplicity
636
    ) {
637
        if (!is_string($sourceProperty) || !is_string($targetProperty)) {
638
            throw new InvalidOperationException("Source and target properties must both be strings");
639
        }
640
641
        if (!is_string($sourceMultiplicity) || !is_string($targetMultiplicity)) {
642
            throw new InvalidOperationException("Source and target multiplicities must both be strings");
643
        }
644
645
        $this->checkInstanceProperty($sourceProperty, $sourceResourceType);
646
        $this->checkInstanceProperty($targetProperty, $targetResourceType);
647
648
        // check that property and resource name don't up and collide - would violate OData spec
649
        if (strtolower($sourceProperty) == strtolower($sourceResourceType->getName())) {
650
            throw new InvalidOperationException(
651
                'Source property name must be different from source resource name.'
652
            );
653
        }
654
        if (strtolower($targetProperty) == strtolower($targetResourceType->getName())) {
655
            throw new InvalidOperationException(
656
                'Target property name must be different from target resource name.'
657
            );
658
        }
659
660
        //Create instance of AssociationSet for this relationship
661
        $sourceResourceSet = $sourceResourceType->getCustomState();
662
        if (!$sourceResourceSet instanceof ResourceSet) {
663
            throw new InvalidOperationException(
664
                'Failed to retrieve the custom state from '
665
                . $sourceResourceType->getName()
666
            );
667
        }
668
        $targetResourceSet = $targetResourceType->getCustomState();
669
        if (!$targetResourceSet instanceof ResourceSet) {
670
            throw new InvalidOperationException(
671
                'Failed to retrieve the custom state from '
672
                . $targetResourceType->getName()
673
            );
674
        }
675
676
        //Customer_Orders_Orders, Order_Customer_Customers
677
        $fwdSetKey = ResourceAssociationSet::keyName($sourceResourceType, $sourceProperty, $targetResourceSet);
678
        $revSetKey = ResourceAssociationSet::keyName($targetResourceType, $targetProperty, $sourceResourceSet);
679
        if (isset($this->associationSets[$fwdSetKey]) && $this->associationSets[$revSetKey]) {
680
            return;
681
        }
682
        $sourceKind = ('*' == $sourceMultiplicity)
683
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
684
            : ResourcePropertyKind::RESOURCE_REFERENCE;
685
        $targetKind = ('*' == $targetMultiplicity)
686
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
687
            : ResourcePropertyKind::RESOURCE_REFERENCE;
688
689
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, $sourceKind, $targetResourceType);
690
        $sourceResourceType->addProperty($sourceResourceProperty, false);
691
        $targetResourceProperty = new ResourceProperty($targetProperty, null, $targetKind, $sourceResourceType);
692
        $targetResourceType->addProperty($targetResourceProperty, false);
693
694
695
        $fwdSet = new ResourceAssociationSet(
696
            $fwdSetKey,
697
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
698
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty)
699
        );
700
        $revSet = new ResourceAssociationSet(
701
            $revSetKey,
702
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty),
703
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty)
704
        );
705
        $sourceName = $sourceResourceType->getFullName();
706
        $targetName = $targetResourceType->getFullName();
707
        $this->metadataManager->addNavigationPropertyToEntityType(
708
            $this->OdataEntityMap[$sourceName],
709
            $sourceMultiplicity,
710
            $sourceProperty,
711
            $this->OdataEntityMap[$targetName],
712
            $targetMultiplicity,
713
            $targetProperty
714
        );
715
        $this->associationSets[$fwdSetKey] = $fwdSet;
716
        $this->associationSets[$revSetKey] = $revSet;
717
    }
718
719
    /**
720
     * To add a resource set reference property.
721
     *
722
     * @param ResourceEntityType    $resourceType   The resource type to add the
723
     *                                              resource reference set property to
724
     * @param string                $name           The name of the property to add
725
     * @param ResourceSet           $targetResourceSet The resource set the resource
726
     *                                              reference set property points to
727
     */
728
    public function addResourceSetReferenceProperty(ResourceEntityType $resourceType, $name, $targetResourceSet)
729
    {
730
        $this->_addReferencePropertyInternal(
731
            $resourceType,
732
            $name,
733
            $targetResourceSet,
734
            '*'
735
        );
736
    }
737
738
    /**
739
     * To add a M:N resource reference property.
740
     *
741
     * @param ResourceEntityType    $sourceResourceType     The resource type to add the resource
742
     *                                                      reference property from
743
     * @param ResourceEntityType    $targetResourceType     The resource type to add the resource
744
     *                                                      reference property to
745
     * @param string                $sourceProperty         The name of the property to add, on source type
746
     * @param string                $targetProperty         The name of the property to add, on target type
747
     */
748
    public function addResourceSetReferencePropertyBidirectional(
749
        ResourceEntityType $sourceResourceType,
750
        ResourceEntityType $targetResourceType,
751
        $sourceProperty,
752
        $targetProperty
753
    ) {
754
        $this->_addReferencePropertyInternalBidirectional(
755
            $sourceResourceType,
756
            $targetResourceType,
757
            $sourceProperty,
758
            $targetProperty,
759
            '*',
760
            '*'
761
        );
762
    }
763
764
    /**
765
     * To add a 1-1 resource reference.
766
     *
767
     * @param ResourceEntityType    $sourceResourceType     The resource type to add the resource
768
     *                                                      reference property from
769
     * @param ResourceEntityType    $targetResourceType     The resource type to add the resource
770
     *                                                      reference property to
771
     * @param string                $sourceProperty         The name of the property to add, on source type
772
     * @param string                $targetProperty         The name of the property to add, on target type
773
     */
774
    public function addResourceReferenceSinglePropertyBidirectional(
775
        ResourceEntityType $sourceResourceType,
776
        ResourceEntityType $targetResourceType,
777
        $sourceProperty,
778
        $targetProperty
779
    ) {
780
        $this->_addReferencePropertyInternalBidirectional(
781
            $sourceResourceType,
782
            $targetResourceType,
783
            $sourceProperty,
784
            $targetProperty,
785
            '0..1',
786
            '1'
787
        );
788
    }
789
790
    /**
791
     * To add a complex property to entity or complex type.
792
     *
793
     * @param ResourceType          $targetResourceType     The resource type to which the complex property needs to add
794
     * @param string                $name                   name of the complex property
795
     * @param ResourceComplexType   $complexResourceType    complex resource type
796
     * @param bool                  $isBag                  complex type is bag or not
797
     *
798
     * @return ResourceProperty
799
     */
800
    public function addComplexProperty(
801
        ResourceType $targetResourceType,
802
        $name,
803
        ResourceComplexType $complexResourceType,
804
        $isBag = false
805
    ) {
806
        if ($targetResourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY
807
            && $targetResourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX
808
        ) {
809
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
810
        }
811
812
        // check that property and resource name don't up and collide - would violate OData spec
813
        if (strtolower($name) == strtolower($targetResourceType->getName())) {
814
            throw new InvalidOperationException(
815
                'Property name must be different from resource name.'
816
            );
817
        }
818
819
        $this->checkInstanceProperty($name, $targetResourceType);
820
821
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
822
        if ($isBag) {
823
            $kind = $kind | ResourcePropertyKind::BAG;
824
        }
825
826
        $resourceProperty = new ResourceProperty($name, null, $kind, $complexResourceType);
827
        $targetResourceType->addProperty($resourceProperty);
828
829
        return $resourceProperty;
830
    }
831
832
    public function createSingleton($name, ResourceType $returnType, $functionName)
833
    {
834
        $msg = null;
835
        if (array_key_exists($name, $this->singletons)) {
836
            $msg = "Singleton name already exists";
837
            throw new \InvalidArgumentException($msg);
838
        }
839
        $typeName = $returnType->getName();
840
        if (!array_key_exists($typeName, $this->OdataEntityMap)) {
841
            $msg = "Mapping not defined for ".$typeName;
842
            throw new \InvalidArgumentException($msg);
843
        }
844
        $metaReturn = $this->OdataEntityMap[$typeName];
845
        $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...
846
        assert($singleton->isOK($msg), $msg);
847
        $type = new ResourceFunctionType($functionName, $singleton);
848
        // Since singletons should take no args, enforce it here
849
        assert(0 == count($type->getParms()));
850
        $this->singletons[$name] = $type;
851
    }
852
853
    public function getSingletons()
854
    {
855
        return $this->singletons;
856
    }
857
858
    public function callSingleton($name)
859
    {
860
        if (!array_key_exists($name, $this->singletons)) {
861
            $msg = "Requested singleton does not exist";
862
            throw new \InvalidArgumentException($msg);
863
        }
864
865
        return $this->singletons[$name]->get();
866
    }
867
}
868