Test Setup Failed
Pull Request — master (#90)
by Alex
04:26
created

SimpleMetadataProvider::addComplexType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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