Test Setup Failed
Pull Request — master (#80)
by Alex
03:53
created

SimpleMetadataProvider   C

Complexity

Total Complexity 71

Size/Duplication

Total Lines 735
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7

Importance

Changes 8
Bugs 0 Features 1
Metric Value
wmc 71
c 8
b 0
f 1
lcom 2
cbo 7
dl 0
loc 735
rs 5

29 Methods

Rating   Name   Duplication   Size   Complexity  
A getXML() 0 4 1
A getContainerName() 0 4 1
A getContainerNamespace() 0 4 1
C getResourceSets() 0 27 8
A getTypes() 0 4 1
A resolveResourceSet() 0 7 2
A resolveResourceType() 0 6 2
A resolveAssociationSet() 0 7 2
A getDerivedTypes() 0 4 1
A hasDerivedTypes() 0 4 1
A addEntityType() 0 4 1
A __construct() 0 6 1
B getResourceAssociationSet() 0 39 4
A createResourceType() 0 22 3
A addComplexType() 0 4 1
A addResourceSet() 0 14 2
A addKeyProperty() 0 4 1
C _addPrimitivePropertyInternal() 0 40 8
B checkInstanceProperty() 0 17 5
A addPrimitiveProperty() 0 4 1
A addETagProperty() 0 4 1
A addResourceReferenceProperty() 0 9 1
A addResourceSetReferencePropertyBidirectional() 0 15 1
A addResourceReferencePropertyBidirectional() 0 15 1
B _addReferencePropertyInternal() 0 47 4
A addResourceSetReferenceProperty() 0 9 1
A addResourceReferenceSinglePropertyBidirectional() 0 15 1
B addComplexProperty() 0 27 5
C _addReferencePropertyInternalBidirectional() 0 76 9

How to fix   Complexity   

Complex Class

Complex classes like SimpleMetadataProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SimpleMetadataProvider, and based on these observations, apply Extract Interface, too.

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