Test Setup Failed
Pull Request — master (#106)
by Alex
03:33
created

_addReferencePropertyInternalBidirectional()   C

Complexity

Conditions 11
Paths 10

Size

Total Lines 93
Code Lines 66

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 93
rs 5.2653
cc 11
eloc 66
nc 10
nop 6

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace POData\Providers\Metadata;
4
5
use AlgoWeb\ODataMetadata\IsOK;
6
use AlgoWeb\ODataMetadata\MetadataManager;
7
use AlgoWeb\ODataMetadata\MetadataV3\edm\TComplexTypeType;
8
use AlgoWeb\ODataMetadata\MetadataV3\edm\TEntityTypeType;
9
use Illuminate\Support\Str;
10
use POData\Common\InvalidOperationException;
11
use POData\Common\NotImplementedException;
12
use POData\Providers\Metadata\Type\IType;
13
use POData\Providers\Metadata\Type\TypeCode;
14
15
/**
16
 * Class SimpleMetadataProvider.
17
 */
18
class SimpleMetadataProvider implements IMetadataProvider
19
{
20
    public $OdataEntityMap = [];
21
    protected $resourceSets = [];
22
    protected $resourceTypes = [];
23
    protected $associationSets = [];
24
    protected $containerName;
25
    protected $namespaceName;
26
    private $metadataManager;
27
    private $typeSetMapping = [];
28
    protected $singletons = [];
29
30
    /**
31
     * @param string $containerName container name for the datasource
32
     * @param string $namespaceName namespace for the datasource
33
     */
34
    public function __construct($containerName, $namespaceName)
35
    {
36
        $this->containerName = $containerName;
37
        $this->namespaceName = $namespaceName;
38
        $this->metadataManager = new MetadataManager($namespaceName, $containerName);
39
    }
40
41
    //Begin Implementation of IMetadataProvider
42
43
    public function getXML()
44
    {
45
        return $this->metadataManager->getEdmxXML();
46
    }
47
48
    /**
49
     * get the Container name for the data source.
50
     *
51
     * @return string container name
52
     */
53
    public function getContainerName()
54
    {
55
        return $this->containerName;
56
    }
57
58
    /**
59
     * get Namespace name for the data source.
60
     *
61
     * @return string namespace
62
     */
63
    public function getContainerNamespace()
64
    {
65
        return $this->namespaceName;
66
    }
67
68
    /**
69
     * get all entity set information.
70
     *
71
     * @return ResourceSet[]
72
     */
73
    public function getResourceSets($params = null)
74
    {
75
        $parameters = [];
76
        if (is_string($params)) {
77
            $parameters[] = $params;
78
        } elseif (isset($params) && !is_array($params)) {
79
            throw new \ErrorException('Input parameter must be absent, null, string or array');
80
        } else {
81
            $parameters = $params;
82
        }
83
        if (!is_array($parameters) || 0 == count($parameters)) {
84
            return array_values($this->resourceSets);
85
        }
86
        assert(is_array($parameters));
87
        $return = [];
88
        $counter = 0;
89
        foreach ($this->resourceSets as $resource) {
90
            $resName = $resource->getName();
91
            if (in_array($resName, $parameters)) {
92
                $return[] = $resource;
93
                $counter++;
94
            }
95
        }
96
        assert($counter == count($return));
97
98
        return $return;
99
    }
100
101
    /**
102
     * get all resource types in the data source.
103
     *
104
     * @return ResourceType[]
105
     */
106
    public function getTypes()
107
    {
108
        return array_values($this->resourceTypes);
109
    }
110
111
    /**
112
     * get a resource set based on the specified resource set name.
113
     *
114
     * @param string $name Name of the resource set
115
     *
116
     * @return ResourceSet|null resource set with the given name if found else NULL
117
     */
118
    public function resolveResourceSet($name)
119
    {
120
        if (array_key_exists($name, $this->resourceSets)) {
121
            return $this->resourceSets[$name];
122
        }
123
        return null;
124
    }
125
126
    /**
127
     * get a resource type based on the resource type name.
128
     *
129
     * @param string $name Name of the resource type
130
     *
131
     * @return ResourceType|null resource type with the given resource type name if found else NULL
132
     */
133
    public function resolveResourceType($name)
134
    {
135
        if (array_key_exists($name, $this->resourceTypes)) {
136
            return $this->resourceTypes[$name];
137
        }
138
        return null;
139
    }
140
141
    /**
142
     * get a singelton based on the specified singleton name.
143
     *
144
     * @param string $name Name of the resource set
145
     *
146
     * @return ResourceFunctionType|null    singleton with the given name if found else NULL
147
     */
148
    public function resolveSingleton($name)
149
    {
150
        if (array_key_exists($name, $this->singletons)) {
151
            return $this->singletons[$name];
152
        }
153
        return null;
154
    }
155
156
    /**
157
     * get a resource set based on the specified resource association set name.
158
     *
159
     * @param string $name Name of the resource assocation set
160
     *
161
     * @return ResourceAssociationSet|null resource association set with the given name if found else NULL
162
     */
163
    public function resolveAssociationSet($name)
164
    {
165
        if (array_key_exists($name, $this->associationSets)) {
166
            return $this->associationSets[$name];
167
        }
168
        return null;
169
    }
170
171
    /*
172
     * Get number of association sets hooked up
173
     */
174
    public function getAssociationCount()
175
    {
176
        return count($this->associationSets);
177
    }
178
179
    /**
180
     * The method must return a collection of all the types derived from
181
     * $resourceType The collection returned should NOT include the type
182
     * passed in as a parameter.
183
     *
184
     * @param ResourceEntityType $resourceType Resource to get derived resource types from
185
     *
186
     * @return ResourceType[]
187
     */
188
    public function getDerivedTypes(ResourceEntityType $resourceType)
189
    {
190
        return [];
191
    }
192
193
    /**
194
     * @param ResourceType $resourceType Resource to check for derived resource types
195
     *
196
     * @return bool true if $resourceType represents an Entity Type which has derived Entity Types, else false
197
     */
198
    public function hasDerivedTypes(ResourceEntityType $resourceType)
199
    {
200
        return false;
201
    }
202
203
    //End Implementation of IMetadataProvider
204
205
    /**
206
     * Gets the ResourceAssociationSet instance for the given source
207
     * association end.
208
     *
209
     * @param ResourceSet      $sourceResourceSet      Resource set
210
     *                                                 of the source
211
     *                                                 association end
212
     * @param ResourceType     $sourceResourceType     Resource type of the source
213
     *                                                 association end
214
     * @param ResourceProperty $targetResourceProperty Resource property of
215
     *                                                 the source
216
     *                                                 association end
217
     *
218
     * @return ResourceAssociationSet|null
219
     */
220
    public function getResourceAssociationSet(
221
        ResourceSet $sourceResourceSet,
222
        ResourceEntityType $sourceResourceType,
223
        ResourceProperty $targetResourceProperty
224
    ) {
225
        //e.g.
226
        //ResourceSet => Representing 'Customers' entity set
227
        //ResourceType => Representing'Customer' entity type
228
        //ResourceProperty => Representing 'Orders' property
229
        //We have created ResourceAssoicationSet while adding
230
        //ResourceSetReference or ResourceReference
231
        //and kept in $this->associationSets
232
        //$metadata->addResourceSetReferenceProperty(
233
        //             $customersEntityType,
234
        //             'Orders',
235
        //             $ordersResourceSet
236
        //             );
237
238
        $targetResourceSet = $targetResourceProperty->getResourceType()->getCustomState();
239
        if (is_null($targetResourceSet)) {
240
            throw new InvalidOperationException(
241
                'Failed to retrieve the custom state from ' . $targetResourceProperty->getResourceType()->getName()
242
            );
243
        }
244
245
        //Customer_Orders_Orders, Order_Customer_Customers
246
        $key = ResourceAssociationSet::keyName(
247
            $sourceResourceType,
248
            $targetResourceProperty->getName(),
249
            $targetResourceSet
250
        );
251
252
        $associationSet = array_key_exists($key, $this->associationSets) ? $this->associationSets[$key] : null;
253
        assert(
254
            null == $associationSet || $associationSet instanceof ResourceAssociationSet,
255
            "Retrieved resource assocation must be either null or an instance of ResourceAssociationSet"
256
        );
257
        return $associationSet;
258
    }
259
260
    /**
261
     * Add an entity type.
262
     *
263
     * @param \ReflectionClass $refClass reflection class of the entity
264
     * @param string $name name of the entity
265
     * @return ResourceType when the name is already in use
266
     *
267
     * @throws InvalidOperationException when the name is already in use
268
     * @internal param string $namespace namespace of the data source
269
     *
270
     */
271
    public function addEntityType(\ReflectionClass $refClass, $name)
272
    {
273
        return $this->createResourceType($refClass, $name, ResourceTypeKind::ENTITY);
274
    }
275
276
    /**
277
     * @param \ReflectionClass $refClass
278
     * @param string $name
279
     * @param $typeKind
280
     * @return ResourceType
281
     * @throws InvalidOperationException
282
     * @internal param null|string $namespace
283
     * @internal param null|ResourceType $baseResourceType
284
     *
285
     */
286
    private function createResourceType(
287
        \ReflectionClass $refClass,
288
        $name,
289
        $typeKind
290
    ) {
291
        if (array_key_exists($name, $this->resourceTypes)) {
292
            throw new InvalidOperationException('Type with same name already added');
293
        }
294
295
        $type = null;
296
        if ($typeKind == ResourceTypeKind::ENTITY) {
297
            list($oet, $entitySet) = $this->metadataManager->addEntityType($name);
298
            assert($oet instanceof TEntityTypeType, "Entity type ".$name. " not successfully added");
299
            $type = new ResourceEntityType($refClass, $oet, $this);
300
            $typeName = $type->getFullName();
301
            $returnName = Str::plural($typeName);
302
            $this->OdataEntityMap[$typeName] = $oet;
303
            $this->typeSetMapping[$name] = $entitySet;
304
            $this->typeSetMapping[$typeName] = $entitySet;
305
            $this->typeSetMapping[$returnName] = $entitySet;
306
        } elseif ($typeKind == ResourceTypeKind::COMPLEX) {
307
            $complex = new TComplexTypeType();
308
            $complex->setName($name);
309
            $type = new ResourceComplexType($refClass, $complex);
310
        }
311
        assert(null != $type, "Type variable must not be null");
312
313
        $this->resourceTypes[$name] = $type;
314
        ksort($this->resourceTypes);
315
316
        return $type;
317
    }
318
319
    /**
320
     * Add a complex type.
321
     *
322
     * @param \ReflectionClass $refClass reflection class of the complex entity type
323
     * @param string $name name of the entity
324
     * @return ResourceType when the name is already in use
325
     *
326
     * @throws InvalidOperationException when the name is already in use
327
     * @internal param string $namespace namespace of the data source
328
     * @internal param ResourceType $baseResourceType base resource type
329
     *
330
     */
331
    public function addComplexType(\ReflectionClass $refClass, $name)
332
    {
333
        return $this->createResourceType($refClass, $name, ResourceTypeKind::COMPLEX);
334
    }
335
336
    /**
337
     * @param string                $name           name of the resource set (now taken from resource type)
338
     * @param ResourceEntityType    $resourceType   resource type
339
     *
340
     * @throws InvalidOperationException
341
     *
342
     * @return ResourceSet
343
     */
344
    public function addResourceSet($name, ResourceEntityType $resourceType)
345
    {
346
        $returnName = Str::plural($resourceType->getFullName());
347
        if (array_key_exists($returnName, $this->resourceSets)) {
348
            throw new InvalidOperationException('Resource Set already added');
349
        }
350
351
        $this->resourceSets[$returnName] = new ResourceSet($returnName, $resourceType);
352
353
        //No support for multiple ResourceSet with same EntityType
354
        //So keeping reference to the 'ResourceSet' with the entity type
355
        $resourceType->setCustomState($this->resourceSets[$returnName]);
356
        ksort($this->resourceSets);
357
358
        return $this->resourceSets[$returnName];
359
    }
360
361
    /**
362
     * To add a Key-primitive property to a resource (Complex/Entity).
363
     *
364
     * @param ResourceType $resourceType resource type to which key property
365
     *                                   is to be added
366
     * @param string       $name         name of the key property
367
     * @param TypeCode     $typeCode     type of the key property
368
     */
369
    public function addKeyProperty($resourceType, $name, $typeCode)
370
    {
371
        $this->_addPrimitivePropertyInternal($resourceType, $name, $typeCode, true);
372
    }
373
374
    /**
375
     * To add a Key/NonKey-primitive property to a resource (complex/entity).
376
     *
377
     * @param ResourceType $resourceType   Resource type
378
     * @param string       $name           name of the property
379
     * @param TypeCode     $typeCode       type of property
380
     * @param bool         $isKey          property is key or not
381
     * @param bool         $isBag          property is bag or not
382
     * @param bool         $isETagProperty property is etag or not
383
     */
384
    private function _addPrimitivePropertyInternal(
385
        $resourceType,
386
        $name,
387
        $typeCode,
388
        $isKey = false,
389
        $isBag = false,
390
        $isETagProperty = false,
391
        $defaultValue = null,
392
        $nullable = false
393
    ) {
394
        $this->checkInstanceProperty($name, $resourceType);
395
396
        // check that property and resource name don't up and collide - would violate OData spec
397
        if (strtolower($name) == strtolower($resourceType->getName())) {
398
            throw new InvalidOperationException(
399
                'Property name must be different from resource name.'
400
            );
401
        }
402
403
        $primitiveResourceType = ResourceType::getPrimitiveResourceType($typeCode);
0 ignored issues
show
Documentation introduced by
$typeCode is of type object<POData\Providers\Metadata\Type\TypeCode>, but the function expects a object<POData\Providers\...\Type\EdmPrimitiveType>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
404
405
        if ($isETagProperty && $isBag) {
406
            throw new InvalidOperationException(
407
                'Only primitve property can be etag property, bag property cannot be etag property.'
408
            );
409
        }
410
411
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
412
        if ($isBag) {
413
            $kind = $kind | ResourcePropertyKind::BAG;
414
        }
415
416
        if ($isETagProperty) {
417
            $kind = $kind | ResourcePropertyKind::ETAG;
418
        }
419
420
        $resourceProperty = new ResourceProperty($name, null, $kind, $primitiveResourceType);
421
        $resourceType->addProperty($resourceProperty);
422
        if (array_key_exists($resourceType->getFullName(), $this->OdataEntityMap)) {
423
            $this->metadataManager->addPropertyToEntityType(
424
                $this->OdataEntityMap[$resourceType->getFullName()],
425
                $name,
426
                $primitiveResourceType->getFullName(),
427
                $defaultValue,
428
                $nullable,
429
                $isKey
430
            );
431
        }
432
    }
433
434
    /**
435
     * @param string $name
436
     * @param ResourceType $resourceType
437
     *
438
     * @throws InvalidOperationException
439
     */
440
    private function checkInstanceProperty($name, ResourceType $resourceType)
441
    {
442
        $instance = $resourceType->getInstanceType();
443
        $hasMagicGetter = $instance instanceof IType || $instance->hasMethod('__get');
444
445
        if (!$hasMagicGetter) {
446
            try {
447
                if ($instance instanceof \ReflectionClass) {
448
                    $instance->getProperty($name);
449
                }
450
            } catch (\ReflectionException $exception) {
451
                throw new InvalidOperationException(
452
                    'Can\'t add a property which does not exist on the instance type.'
453
                );
454
            }
455
        }
456
    }
457
458
    /**
459
     * To add a NonKey-primitive property (Complex/Entity).
460
     *
461
     * @param ResourceType $resourceType resource type to which key property
462
     *                                   is to be added
463
     * @param string $name name of the key property
464
     * @param TypeCode $typeCode type of the key property
465
     * @param bool $isBag property is bag or not
466
     */
467 View Code Duplication
    public function addPrimitiveProperty(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
468
        $resourceType,
469
        $name,
470
        $typeCode,
471
        $isBag = false,
472
        $defaultValue = null,
473
        $nullable = false
474
    ) {
475
        $this->_addPrimitivePropertyInternal(
476
            $resourceType,
477
            $name,
478
            $typeCode,
479
            false,
480
            $isBag,
481
            false,
482
            $defaultValue,
483
            $nullable
484
        );
485
    }
486
487
    /**
488
     * To add a non-key etag property.
489
     *
490
     * @param ResourceType $resourceType resource type to which key property
491
     *                                   is to be added
492
     * @param string $name name of the property
493
     * @param TypeCode $typeCode type of the etag property
494
     */
495 View Code Duplication
    public function addETagProperty($resourceType, $name, $typeCode, $defaultValue = null, $nullable = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
496
    {
497
        $this->_addPrimitivePropertyInternal(
498
            $resourceType,
499
            $name,
500
            $typeCode,
501
            false,
502
            false,
503
            true,
504
            $defaultValue,
505
            $nullable
506
        );
507
    }
508
509
    /**
510
     * To add a resource reference property.
511
     *
512
     * @param ResourceEntityType    $resourceType   The resource type to add the resource
513
     *                                              reference property to
514
     * @param string                $name           The name of the property to add
515
     * @param ResourceSet           $targetResourceSet The resource set the resource reference
516
     *                                               property points to
517
     */
518
    public function addResourceReferenceProperty($resourceType, $name, $targetResourceSet)
519
    {
520
        $this->_addReferencePropertyInternal(
521
            $resourceType,
522
            $name,
523
            $targetResourceSet,
524
            '0..1'
525
        );
526
    }
527
528
    /**
529
     * To add a 1:N resource reference property.
530
     *
531
     * @param ResourceType $sourceResourceType  The resource type to add the resource
532
     *                                          reference property from
533
     * @param ResourceType $targetResourceType  The resource type to add the resource
534
     *                                          reference property to
535
     * @param string $sourceProperty            The name of the property to add, on source type
536
     * @param string $targetProperty            The name of the property to add, on target type
537
     */
538 View Code Duplication
    public function addResourceReferencePropertyBidirectional(
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...
539
        ResourceEntityType $sourceResourceType,
540
        ResourceEntityType $targetResourceType,
541
        $sourceProperty,
542
        $targetProperty
543
    ) {
544
        $this->_addReferencePropertyInternalBidirectional(
545
            $sourceResourceType,
546
            $targetResourceType,
547
            $sourceProperty,
548
            $targetProperty,
549
            '1',
550
            '*'
551
        );
552
        // verify resource property types are what we expect them to be
553
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
554
        assert(
555
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
556
            "1 side of 1:N relationship not pointing to resource reference"
557
        );
558
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
559
        assert(
560
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
561
            "N side of 1:N relationship not pointing to resource set reference"
562
        );
563
    }
564
565
    /**
566
     * To add a navigation property (resource set or resource reference)
567
     * to a resource type.
568
     *
569
     * @param ResourceEntityType   $sourceResourceType   The resource type to add
570
     *                                                   the resource reference
571
     *                                                   or resource
572
     *                                                   reference set property to
573
     * @param string               $name                 The name of the
574
     *                                                   property to add
575
     * @param ResourceSet          $targetResourceSet    The resource set the
576
     *                                                   resource reference
577
     *                                                   or reference
578
     *                                                   set property
579
     *                                                   points to
580
     * @param string               $resourceMult         The multiplicity of relation being added
581
     */
582
    private function _addReferencePropertyInternal(
583
        ResourceEntityType $sourceResourceType,
584
        $name,
585
        ResourceSet $targetResourceSet,
586
        $resourceMult
587
    ) {
588
        $allowedMult = ['*', '1', '0..1'];
589
        $this->checkInstanceProperty($name, $sourceResourceType);
590
591
        // check that property and resource name don't up and collide - would violate OData spec
592
        if (strtolower($name) == strtolower($sourceResourceType->getName())) {
593
            throw new InvalidOperationException(
594
                'Property name must be different from resource name.'
595
            );
596
        }
597
        if (!in_array($resourceMult, $allowedMult)) {
598
            throw new InvalidOperationException("Supplied multiplicity ".$resourceMult." not valid");
599
        }
600
601
        $resourcePropertyKind = ('*' == $resourceMult)
602
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
603
            : ResourcePropertyKind::RESOURCE_REFERENCE;
604
        $targetResourceType = $targetResourceSet->getResourceType();
605
        $sourceResourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
606
        $sourceResourceType->addProperty($sourceResourceProperty);
607
608
        //Create instance of AssociationSet for this relationship
609
        $sourceResourceSet = $sourceResourceType->getCustomState();
610
        if (!$sourceResourceSet instanceof ResourceSet) {
611
            throw new InvalidOperationException(
612
                'Failed to retrieve the custom state from '
613
                . $sourceResourceType->getName()
614
            );
615
        }
616
617
        //Customer_Orders_Orders, Order_Customer_Customers
618
        //(source type::name _ source property::name _ target set::name)
619
        $setKey = ResourceAssociationSet::keyName($sourceResourceType, $name, $targetResourceSet);
620
        //$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...
621
        $set = new ResourceAssociationSet(
622
            $setKey,
623
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
624
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, null)
625
        );
626
        $mult = $resourceMult;
627
        $backMult = '*' == $resourceMult ? '*' : '1';
628
        $this->metadataManager->addNavigationPropertyToEntityType(
629
            $this->OdataEntityMap[$sourceResourceType->getFullName()],
630
            $mult,
631
            $name,
632
            $this->OdataEntityMap[$targetResourceType->getFullName()],
633
            $backMult
634
        );
635
        $this->associationSets[$setKey] = $set;
636
    }
637
638
    /**
639
     * To add a navigation property (resource set or resource reference)
640
     * to a resource type.
641
     *
642
     * @param ResourceEntityType   $sourceResourceType   The source resource type to add
643
     *                                                   the resource reference
644
     *                                                   or resource reference set property to
645
     * @param ResourceEntityType   $targetResourceType   The target resource type to add
646
     *                                                   the resource reference
647
     *                                                   or resource reference set property to
648
     * @param string               $sourceProperty       The name of the
649
     *                                                   property to add to source type
650
     * @param string               $targetProperty       The name of the
651
     *                                                   property to add to target type
652
     * @param string               $sourceMultiplicity   The multiplicity at the source end of relation
653
     * @param string               $targetMultiplicity   The multiplicity at the target end of relation
654
     */
655
    private function _addReferencePropertyInternalBidirectional(
656
        ResourceEntityType $sourceResourceType,
657
        ResourceEntityType $targetResourceType,
658
        $sourceProperty,
659
        $targetProperty,
660
        $sourceMultiplicity,
661
        $targetMultiplicity
662
    ) {
663
        if (!is_string($sourceProperty) || !is_string($targetProperty)) {
664
            throw new InvalidOperationException("Source and target properties must both be strings");
665
        }
666
667
        $this->checkInstanceProperty($sourceProperty, $sourceResourceType);
668
        $this->checkInstanceProperty($targetProperty, $targetResourceType);
669
670
        // check that property and resource name don't up and collide - would violate OData spec
671
        if (strtolower($sourceProperty) == strtolower($sourceResourceType->getName())) {
672
            throw new InvalidOperationException(
673
                'Source property name must be different from source resource name.'
674
            );
675
        }
676
        if (strtolower($targetProperty) == strtolower($targetResourceType->getName())) {
677
            throw new InvalidOperationException(
678
                'Target property name must be different from target resource name.'
679
            );
680
        }
681
682
        //Create instance of AssociationSet for this relationship
683
        $sourceResourceSet = $sourceResourceType->getCustomState();
684
        if (!$sourceResourceSet instanceof ResourceSet) {
685
            throw new InvalidOperationException(
686
                'Failed to retrieve the custom state from '
687
                . $sourceResourceType->getName()
688
            );
689
        }
690
        $targetResourceSet = $targetResourceType->getCustomState();
691
        if (!$targetResourceSet instanceof ResourceSet) {
692
            throw new InvalidOperationException(
693
                'Failed to retrieve the custom state from '
694
                . $targetResourceType->getName()
695
            );
696
        }
697
698
        //Customer_Orders_Orders, Order_Customer_Customers
699
        $fwdSetKey = ResourceAssociationSet::keyName($sourceResourceType, $sourceProperty, $targetResourceSet);
700
        $revSetKey = ResourceAssociationSet::keyName($targetResourceType, $targetProperty, $sourceResourceSet);
701
        if (isset($this->associationSets[$fwdSetKey]) && $this->associationSets[$revSetKey]) {
702
            return;
703
        }
704
        $sourceKind = ('*' == $sourceMultiplicity)
705
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
706
            : ResourcePropertyKind::RESOURCE_REFERENCE;
707
        $targetKind = ('*' == $targetMultiplicity)
708
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
709
            : ResourcePropertyKind::RESOURCE_REFERENCE;
710
711
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, $sourceKind, $targetResourceType);
712
        assert(
713
            $sourceKind == $sourceResourceProperty->getKind(),
714
            'Resource property kind mismatch between $sourceKind and $sourceResourceProperty'
715
        );
716
        $sourceResourceType->addProperty($sourceResourceProperty, false);
717
        $targetResourceProperty = new ResourceProperty($targetProperty, null, $targetKind, $sourceResourceType);
718
        assert(
719
            $targetKind == $targetResourceProperty->getKind(),
720
            'Resource property kind mismatch between $targetKind and $targetResourceProperty'
721
        );
722
        $targetResourceType->addProperty($targetResourceProperty, false);
723
724
        //TODO: Audit this, figure out how it makes metadata go sproing
725
        $fwdSet = new ResourceAssociationSet(
726
            $fwdSetKey,
727
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
728
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty)
729
        );
730
        $revSet = new ResourceAssociationSet(
731
            $revSetKey,
732
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty),
733
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty)
734
        );
735
        $sourceName = $sourceResourceType->getFullName();
736
        $targetName = $targetResourceType->getFullName();
737
        $this->metadataManager->addNavigationPropertyToEntityType(
738
            $this->OdataEntityMap[$sourceName],
739
            $sourceMultiplicity,
740
            $sourceProperty,
741
            $this->OdataEntityMap[$targetName],
742
            $targetMultiplicity,
743
            $targetProperty
744
        );
745
        $this->associationSets[$fwdSetKey] = $fwdSet;
746
        $this->associationSets[$revSetKey] = $revSet;
747
    }
748
749
    /**
750
     * To add a resource set reference property.
751
     *
752
     * @param ResourceEntityType    $resourceType   The resource type to add the
753
     *                                              resource reference set property to
754
     * @param string                $name           The name of the property to add
755
     * @param ResourceSet           $targetResourceSet The resource set the resource
756
     *                                              reference set property points to
757
     */
758
    public function addResourceSetReferenceProperty(ResourceEntityType $resourceType, $name, $targetResourceSet)
759
    {
760
        $this->_addReferencePropertyInternal(
761
            $resourceType,
762
            $name,
763
            $targetResourceSet,
764
            '*'
765
        );
766
    }
767
768
    /**
769
     * To add a M:N resource reference property.
770
     *
771
     * @param ResourceEntityType    $sourceResourceType     The resource type to add the resource
772
     *                                                      reference property from
773
     * @param ResourceEntityType    $targetResourceType     The resource type to add the resource
774
     *                                                      reference property to
775
     * @param string                $sourceProperty         The name of the property to add, on source type
776
     * @param string                $targetProperty         The name of the property to add, on target type
777
     */
778 View Code Duplication
    public function addResourceSetReferencePropertyBidirectional(
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...
779
        ResourceEntityType $sourceResourceType,
780
        ResourceEntityType $targetResourceType,
781
        $sourceProperty,
782
        $targetProperty
783
    ) {
784
        $this->_addReferencePropertyInternalBidirectional(
785
            $sourceResourceType,
786
            $targetResourceType,
787
            $sourceProperty,
788
            $targetProperty,
789
            '*',
790
            '*'
791
        );
792
        // verify resource property types are what we expect them to be
793
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
794
        assert(
795
            ResourcePropertyKind::RESOURCESET_REFERENCE == $sourceResourceKind,
796
            "M side of M:N relationship not pointing to resource set reference"
797
        );
798
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
799
        assert(
800
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
801
            "N side of M:N relationship not pointing to resource set reference"
802
        );
803
    }
804
805
    /**
806
     * To add a 1-1 resource reference.
807
     *
808
     * @param ResourceEntityType    $sourceResourceType     The resource type to add the resource
809
     *                                                      reference property from
810
     * @param ResourceEntityType    $targetResourceType     The resource type to add the resource
811
     *                                                      reference property to
812
     * @param string                $sourceProperty         The name of the property to add, on source type
813
     * @param string                $targetProperty         The name of the property to add, on target type
814
     */
815 View Code Duplication
    public function addResourceReferenceSinglePropertyBidirectional(
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...
816
        ResourceEntityType $sourceResourceType,
817
        ResourceEntityType $targetResourceType,
818
        $sourceProperty,
819
        $targetProperty
820
    ) {
821
        $this->_addReferencePropertyInternalBidirectional(
822
            $sourceResourceType,
823
            $targetResourceType,
824
            $sourceProperty,
825
            $targetProperty,
826
            '1',
827
            '0..1'
828
        );
829
        // verify resource property types are what we expect them to be
830
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
831
        assert(
832
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
833
            "1 side of 1:1 relationship not pointing to resource reference"
834
        );
835
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
836
        assert(
837
            ResourcePropertyKind::RESOURCE_REFERENCE == $targetResourceKind,
838
            "0..1 side of 1:1 relationship not pointing to resource reference"
839
        );
840
    }
841
842
    /**
843
     * To add a complex property to entity or complex type.
844
     *
845
     * @param ResourceType          $targetResourceType     The resource type to which the complex property needs to add
846
     * @param string                $name                   name of the complex property
847
     * @param ResourceComplexType   $complexResourceType    complex resource type
848
     * @param bool                  $isBag                  complex type is bag or not
849
     *
850
     * @return ResourceProperty
851
     */
852
    public function addComplexProperty(
853
        ResourceType $targetResourceType,
854
        $name,
855
        ResourceComplexType $complexResourceType,
856
        $isBag = false
857
    ) {
858
        if ($targetResourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY
859
            && $targetResourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX
860
        ) {
861
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
862
        }
863
864
        // check that property and resource name don't up and collide - would violate OData spec
865
        if (strtolower($name) == strtolower($targetResourceType->getName())) {
866
            throw new InvalidOperationException(
867
                'Property name must be different from resource name.'
868
            );
869
        }
870
871
        $this->checkInstanceProperty($name, $targetResourceType);
872
873
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
874
        if ($isBag) {
875
            $kind = $kind | ResourcePropertyKind::BAG;
876
        }
877
878
        $resourceProperty = new ResourceProperty($name, null, $kind, $complexResourceType);
879
        $targetResourceType->addProperty($resourceProperty);
880
881
        return $resourceProperty;
882
    }
883
884
    public function createSingleton($name, ResourceType $returnType, $functionName)
885
    {
886
        $msg = null;
887
        if (array_key_exists($name, $this->singletons)) {
888
            $msg = "Singleton name already exists";
889
            throw new \InvalidArgumentException($msg);
890
        }
891
        if (array_key_exists($name, $this->resourceSets)) {
892
            $msg = "Resource set with same name, ". $name. ", exists";
893
            throw new \InvalidArgumentException($msg);
894
        }
895
        $typeName = $returnType->getName();
896
        if (!array_key_exists($typeName, $this->OdataEntityMap)) {
897
            $msg = "Mapping not defined for ".$typeName;
898
            throw new \InvalidArgumentException($msg);
899
        }
900
        $metaReturn = $this->OdataEntityMap[$typeName];
901
        $singleton = $this->metadataManager->createSingleton($name, $metaReturn);
902
        assert($singleton->isOK($msg), $msg);
903
        $type = new ResourceFunctionType($functionName, $singleton, $returnType);
904
        // Since singletons should take no args, enforce it here
905
        assert(0 == count($type->getParms()));
906
        $this->singletons[$name] = $type;
907
    }
908
909
    public function getSingletons()
910
    {
911
        return $this->singletons;
912
    }
913
914
    public function callSingleton($name)
915
    {
916
        if (!array_key_exists($name, $this->singletons)) {
917
            $msg = "Requested singleton does not exist";
918
            throw new \InvalidArgumentException($msg);
919
        }
920
921
        return $this->singletons[$name]->get();
922
    }
923
}
924