Test Setup Failed
Push — master ( 0a3e88...820bc9 )
by Christopher
04:41
created

SimpleMetadataProvider::createResourceType()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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