Completed
Push — master ( ac4bb4...07d9eb )
by Alex
04:33
created

addReferencePropertyInternal()   C

Complexity

Conditions 8
Paths 11

Size

Total Lines 63
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 63
rs 6.8825
cc 8
eloc 44
nc 11
nop 6

How to fix   Long Method   

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

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
377
    {
378
        $returnName = Str::plural($resourceType->getFullName());
379
        if (array_key_exists($returnName, $this->resourceSets)) {
380
            throw new InvalidOperationException('Resource Set already added');
381
        }
382
383
        $this->resourceSets[$returnName] = new ResourceSet($returnName, $resourceType);
384
385
        //No support for multiple ResourceSet with same EntityType
386
        //So keeping reference to the 'ResourceSet' with the entity type
387
        $resourceType->setCustomState($this->resourceSets[$returnName]);
388
        ksort($this->resourceSets);
389
390
        return $this->resourceSets[$returnName];
391
    }
392
393
    /**
394
     * To add a Key-primitive property to a resource (Complex/Entity).
395
     *
396
     * @param ResourceType $resourceType resource type to which key property
397
     *                                   is to be added
398
     * @param string       $name         name of the key property
399
     * @param TypeCode     $typeCode     type of the key property
400
     */
401
    public function addKeyProperty($resourceType, $name, $typeCode)
402
    {
403
        $this->addPrimitivePropertyInternal($resourceType, $name, $typeCode, true);
404
    }
405
406
    /**
407
     * To add a Key/NonKey-primitive property to a resource (complex/entity).
408
     *
409
     * @param ResourceType $resourceType   Resource type
410
     * @param string       $name           name of the property
411
     * @param TypeCode     $typeCode       type of property
412
     * @param bool         $isKey          property is key or not
413
     * @param bool         $isBag          property is bag or not
414
     * @param bool         $isETagProperty property is etag or not
415
     * @param null|mixed   $defaultValue
416
     * @param mixed        $nullable
417
     *
418
     * @throws InvalidOperationException
419
     */
420
    private function addPrimitivePropertyInternal(
421
        $resourceType,
422
        $name,
423
        $typeCode,
424
        $isKey = false,
425
        $isBag = false,
426
        $isETagProperty = false,
427
        $defaultValue = null,
428
        $nullable = false
429
    ) {
430
        if ($isETagProperty && $isBag) {
431
            throw new InvalidOperationException(
432
                'Only primitive property can be etag property, bag property cannot be etag property.'
433
            );
434
        }
435
436
        $this->checkInstanceProperty($name, $resourceType);
437
438
        // check that property and resource name don't up and collide - would violate OData spec
439
        if (strtolower($name) == strtolower($resourceType->getName())) {
440
            throw new InvalidOperationException(
441
                'Property name must be different from resource name.'
442
            );
443
        }
444
445
        $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...
446
447
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
448
        if ($isBag) {
449
            $kind = $kind | ResourcePropertyKind::BAG;
450
        }
451
452
        if ($isETagProperty) {
453
            $kind = $kind | ResourcePropertyKind::ETAG;
454
        }
455
456
        $resourceProperty = new ResourceProperty($name, null, $kind, $primitiveResourceType);
457
        $resourceType->addProperty($resourceProperty);
458
        if (array_key_exists($resourceType->getFullName(), $this->oDataEntityMap)) {
459
            $this->metadataManager->addPropertyToEntityType(
460
                $this->oDataEntityMap[$resourceType->getFullName()],
461
                $name,
462
                $primitiveResourceType->getFullName(),
463
                $defaultValue,
464
                $nullable,
465
                $isKey
466
            );
467
        }
468
    }
469
470
    /**
471
     * @param string       $name
472
     * @param ResourceType $resourceType
473
     *
474
     * @throws InvalidOperationException
475
     */
476
    private function checkInstanceProperty($name, ResourceType $resourceType)
477
    {
478
        $instance = $resourceType->getInstanceType();
479
        $hasMagicGetter = $instance instanceof IType || $instance->hasMethod('__get');
480
        if ($instance instanceof \ReflectionClass) {
481
            $hasMagicGetter |= $instance->isInstance(new \stdClass);
482
        }
483
484
        if (!$hasMagicGetter) {
485
            try {
486
                if ($instance instanceof \ReflectionClass) {
487
                    $instance->getProperty($name);
488
                }
489
            } catch (\ReflectionException $exception) {
490
                throw new InvalidOperationException(
491
                    'Can\'t add a property which does not exist on the instance type.'
492
                );
493
            }
494
        }
495
    }
496
497
    /**
498
     * To add a NonKey-primitive property (Complex/Entity).
499
     *
500
     * @param ResourceType $resourceType resource type to which key property
501
     *                                   is to be added
502
     * @param string       $name         name of the key property
503
     * @param TypeCode     $typeCode     type of the key property
504
     * @param bool         $isBag        property is bag or not
505
     * @param null|mixed   $defaultValue
506
     * @param mixed        $nullable
507
     */
508 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...
509
        $resourceType,
510
        $name,
511
        $typeCode,
512
        $isBag = false,
513
        $defaultValue = null,
514
        $nullable = false
515
    ) {
516
        $this->addPrimitivePropertyInternal(
517
            $resourceType,
518
            $name,
519
            $typeCode,
520
            false,
521
            $isBag,
522
            false,
523
            $defaultValue,
524
            $nullable
525
        );
526
    }
527
528
    /**
529
     * To add a non-key etag property.
530
     *
531
     * @param ResourceType $resourceType resource type to which key property
532
     *                                   is to be added
533
     * @param string       $name         name of the property
534
     * @param TypeCode     $typeCode     type of the etag property
535
     * @param null|mixed   $defaultValue
536
     * @param mixed        $nullable
537
     */
538 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...
539
    {
540
        $this->addPrimitivePropertyInternal(
541
            $resourceType,
542
            $name,
543
            $typeCode,
544
            false,
545
            false,
546
            true,
547
            $defaultValue,
548
            $nullable
549
        );
550
    }
551
552
    /**
553
     * To add a resource reference property.
554
     *
555
     * @param ResourceEntityType        $resourceType       The resource type to add the resource
556
     *                                                      reference property to
557
     * @param string                    $name               The name of the property to add
558
     * @param ResourceSet               $targetResourceSet  The resource set the resource reference
559
     *                                                      property points to
560
     * @param mixed                     $flip
561
     * @param mixed                     $many
562
     * @param ResourceEntityType|null   $concreteType       Underlying concrete resource reference type, if set
563
     */
564
    public function addResourceReferenceProperty(
565
        ResourceEntityType $resourceType,
566
        $name,
567
        ResourceSet $targetResourceSet,
568
        $flip = false,
569
        $many = false,
570
        ResourceEntityType $concreteType = null
571
    ) {
572
        $this->addReferencePropertyInternal(
573
            $resourceType,
574
            $name,
575
            $targetResourceSet,
576
            $flip ? '0..1' : '1',
577
            $many,
578
            $concreteType
579
        );
580
    }
581
582
    /**
583
     * To add a 1:N resource reference property.
584
     *
585
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
586
     *                                               reference property from
587
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
588
     *                                               reference property to
589
     * @param string             $sourceProperty     The name of the property to add, on source type
590
     * @param string             $targetProperty     The name of the property to add, on target type
591
     */
592 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...
593
        ResourceEntityType $sourceResourceType,
594
        ResourceEntityType $targetResourceType,
595
        $sourceProperty,
596
        $targetProperty
597
    ) {
598
        $this->addReferencePropertyInternalBidirectional(
599
            $sourceResourceType,
600
            $targetResourceType,
601
            $sourceProperty,
602
            $targetProperty,
603
            '*',
604
            '1'
605
        );
606
        // verify resource property types are what we expect them to be
607
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
608
        assert(
609
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
610
            '1 side of 1:N relationship not pointing to resource reference'
611
        );
612
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
613
        assert(
614
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
615
            'N side of 1:N relationship not pointing to resource set reference'
616
        );
617
    }
618
619
    /**
620
     * To add a navigation property (resource set or resource reference)
621
     * to a resource type.
622
     *
623
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource reference
624
     *                                               or resource reference set property to
625
     * @param string             $name               The name of the property to add
626
     * @param ResourceSet        $targetResourceSet  The resource set the
627
     *                                               resource reference or reference
628
     *                                               set property points to
629
     * @param string             $resourceMult       The multiplicity of relation being added
630
     * @param mixed              $many
631
     *
632
     * @throws InvalidOperationException
633
     */
634
    private function addReferencePropertyInternal(
635
        ResourceEntityType $sourceResourceType,
636
        $name,
637
        ResourceSet $targetResourceSet,
638
        $resourceMult,
639
        $many = false,
640
        ResourceEntityType $concreteType = null
641
    ) {
642
        $allowedMult = ['*', '1', '0..1'];
643
        $backMultArray = [ '*' => '*', '1' => '0..1', '0..1' => '1'];
644
        $this->checkInstanceProperty($name, $sourceResourceType);
645
646
        // check that property and resource name don't up and collide - would violate OData spec
647
        if (strtolower($name) == strtolower($sourceResourceType->getName())) {
648
            throw new InvalidOperationException(
649
                'Property name must be different from resource name.'
650
            );
651
        }
652
        assert(in_array($resourceMult, $allowedMult), 'Supplied multiplicity ' . $resourceMult . ' not valid');
653
654
        $resourcePropertyKind = ('*' == $resourceMult)
655
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
656
            : ResourcePropertyKind::RESOURCE_REFERENCE;
657
        $targetResourceType = $targetResourceSet->getResourceType();
658
        $sourceResourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
659
        $sourceResourceType->addProperty($sourceResourceProperty);
660
661
        //Create instance of AssociationSet for this relationship
662
        $sourceResourceSet = $sourceResourceType->getCustomState();
663
        if (!$sourceResourceSet instanceof ResourceSet) {
664
            throw new InvalidOperationException(
665
                'Failed to retrieve the custom state from '
666
                . $sourceResourceType->getName()
667
            );
668
        }
669
670
        //Customer_Orders_Orders, Order_Customer_Customers
671
        //(source type::name _ source property::name _ target set::name)
672
        $setKey = ResourceAssociationSet::keyName($sourceResourceType, $name, $targetResourceSet);
673
        //$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...
674
        $set = new ResourceAssociationSet(
675
            $setKey,
676
            new ResourceAssociationSetEnd(
677
                $sourceResourceSet,
678
                $sourceResourceType,
679
                $sourceResourceProperty
680
            ),
681
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, null, $concreteType)
682
        );
683
        $mult = $resourceMult;
684
        $backMult = $many ? '*' : $backMultArray[$resourceMult];
685
        if (false === $many && '*' == $backMult && '*' == $resourceMult) {
686
            $backMult = '1';
687
        }
688
        $this->getMetadataManager()->addNavigationPropertyToEntityType(
689
            $this->oDataEntityMap[$sourceResourceType->getFullName()],
690
            $mult,
691
            $name,
692
            $this->oDataEntityMap[$targetResourceType->getFullName()],
693
            $backMult
694
        );
695
        $this->associationSets[$setKey] = $set;
696
    }
697
698
    /**
699
     * To add a navigation property (resource set or resource reference)
700
     * to a resource type.
701
     *
702
     * @param ResourceEntityType $sourceResourceType The source resource type to add
703
     *                                               the resource reference
704
     *                                               or resource reference set property to
705
     * @param ResourceEntityType $targetResourceType The target resource type to add
706
     *                                               the resource reference
707
     *                                               or resource reference set property to
708
     * @param string             $sourceProperty     The name of the
709
     *                                               property to add to source type
710
     * @param string             $targetProperty     The name of the
711
     *                                               property to add to target type
712
     * @param string             $sourceMultiplicity The multiplicity at the source end of relation
713
     * @param string             $targetMultiplicity The multiplicity at the target end of relation
714
     *
715
     * @throws InvalidOperationException
716
     */
717
    private function addReferencePropertyInternalBidirectional(
718
        ResourceEntityType $sourceResourceType,
719
        ResourceEntityType $targetResourceType,
720
        $sourceProperty,
721
        $targetProperty,
722
        $sourceMultiplicity,
723
        $targetMultiplicity
724
    ) {
725
        if (!is_string($sourceProperty) || !is_string($targetProperty)) {
726
            throw new InvalidOperationException('Source and target properties must both be strings');
727
        }
728
729
        $this->checkInstanceProperty($sourceProperty, $sourceResourceType);
730
        $this->checkInstanceProperty($targetProperty, $targetResourceType);
731
732
        // check that property and resource name don't up and collide - would violate OData spec
733
        if (strtolower($sourceProperty) == strtolower($sourceResourceType->getName())) {
734
            throw new InvalidOperationException(
735
                'Source property name must be different from source resource name.'
736
            );
737
        }
738
        if (strtolower($targetProperty) == strtolower($targetResourceType->getName())) {
739
            throw new InvalidOperationException(
740
                'Target property name must be different from target resource name.'
741
            );
742
        }
743
744
        //Create instance of AssociationSet for this relationship
745
        $sourceResourceSet = $sourceResourceType->getCustomState();
746
        if (!$sourceResourceSet instanceof ResourceSet) {
747
            throw new InvalidOperationException(
748
                'Failed to retrieve the custom state from '
749
                . $sourceResourceType->getName()
750
            );
751
        }
752
        $targetResourceSet = $targetResourceType->getCustomState();
753
        if (!$targetResourceSet instanceof ResourceSet) {
754
            throw new InvalidOperationException(
755
                'Failed to retrieve the custom state from '
756
                . $targetResourceType->getName()
757
            );
758
        }
759
760
        //Customer_Orders_Orders, Order_Customer_Customers
761
        $fwdSetKey = ResourceAssociationSet::keyName($sourceResourceType, $sourceProperty, $targetResourceSet);
762
        $revSetKey = ResourceAssociationSet::keyName($targetResourceType, $targetProperty, $sourceResourceSet);
763
        if (isset($this->associationSets[$fwdSetKey]) && isset($this->associationSets[$revSetKey])) {
764
            return;
765
        }
766
        $sourceKind = ('*' == $sourceMultiplicity)
767
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
768
            : ResourcePropertyKind::RESOURCE_REFERENCE;
769
        $targetKind = ('*' == $targetMultiplicity)
770
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
771
            : ResourcePropertyKind::RESOURCE_REFERENCE;
772
773
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, $targetKind, $targetResourceType);
774
        assert(
775
            $targetKind == $sourceResourceProperty->getKind(),
776
            'Resource property kind mismatch between $targetKind and $sourceResourceProperty'
777
        );
778
        $sourceResourceType->addProperty($sourceResourceProperty, false);
779
        $targetResourceProperty = new ResourceProperty($targetProperty, null, $sourceKind, $sourceResourceType);
780
        assert(
781
            $sourceKind == $targetResourceProperty->getKind(),
782
            'Resource property kind mismatch between $sourceKind and $targetResourceProperty'
783
        );
784
        $targetResourceType->addProperty($targetResourceProperty, false);
785
786
        //TODO: Audit this, figure out how it makes metadata go sproing
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
787
        $fwdSet = new ResourceAssociationSet(
788
            $fwdSetKey,
789
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
790
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty)
791
        );
792
        $revSet = new ResourceAssociationSet(
793
            $revSetKey,
794
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty),
795
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty)
796
        );
797
        $sourceName = $sourceResourceType->getFullName();
798
        $targetName = $targetResourceType->getFullName();
799
        $this->getMetadataManager()->addNavigationPropertyToEntityType(
800
            $this->oDataEntityMap[$sourceName],
801
            $sourceMultiplicity,
802
            $sourceProperty,
803
            $this->oDataEntityMap[$targetName],
804
            $targetMultiplicity,
805
            $targetProperty
806
        );
807
        $this->associationSets[$fwdSetKey] = $fwdSet;
808
        $this->associationSets[$revSetKey] = $revSet;
809
    }
810
811
    /**
812
     * To add a resource set reference property.
813
     *
814
     * @param ResourceEntityType        $resourceType       The resource type to add the
815
     *                                                      resource reference set property to
816
     * @param string                    $name               The name of the property to add
817
     * @param ResourceSet               $targetResourceSet  The resource set the resource
818
     *                                                      reference set property points to
819
     * @param ResourceEntityType|null   $concreteType       Underlying concrete resource type, if set
820
     */
821
    public function addResourceSetReferenceProperty(
822
        ResourceEntityType $resourceType,
823
        $name,
824
        ResourceSet $targetResourceSet,
825
        ResourceEntityType $concreteType = null,
826
        $single = false
827
    ) {
828
        $this->addReferencePropertyInternal(
829
            $resourceType,
830
            $name,
831
            $targetResourceSet,
832
            '*',
833
            (true === $single) ? false : null,
834
            $concreteType
835
        );
836
    }
837
838
    /**
839
     * To add a M:N resource reference property.
840
     *
841
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
842
     *                                               reference property from
843
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
844
     *                                               reference property to
845
     * @param string             $sourceProperty     The name of the property to add, on source type
846
     * @param string             $targetProperty     The name of the property to add, on target type
847
     */
848 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...
849
        ResourceEntityType $sourceResourceType,
850
        ResourceEntityType $targetResourceType,
851
        $sourceProperty,
852
        $targetProperty
853
    ) {
854
        $this->addReferencePropertyInternalBidirectional(
855
            $sourceResourceType,
856
            $targetResourceType,
857
            $sourceProperty,
858
            $targetProperty,
859
            '*',
860
            '*'
861
        );
862
        // verify resource property types are what we expect them to be
863
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
864
        assert(
865
            ResourcePropertyKind::RESOURCESET_REFERENCE == $sourceResourceKind,
866
            'M side of M:N relationship not pointing to resource set reference'
867
        );
868
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
869
        assert(
870
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
871
            'N side of M:N relationship not pointing to resource set reference'
872
        );
873
    }
874
875
    /**
876
     * To add a 1-1 resource reference.
877
     *
878
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource
879
     *                                               reference property from
880
     * @param ResourceEntityType $targetResourceType The resource type to add the resource
881
     *                                               reference property to
882
     * @param string             $sourceProperty     The name of the property to add, on source type
883
     * @param string             $targetProperty     The name of the property to add, on target type
884
     */
885 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...
886
        ResourceEntityType $sourceResourceType,
887
        ResourceEntityType $targetResourceType,
888
        $sourceProperty,
889
        $targetProperty
890
    ) {
891
        $this->addReferencePropertyInternalBidirectional(
892
            $sourceResourceType,
893
            $targetResourceType,
894
            $sourceProperty,
895
            $targetProperty,
896
            '1',
897
            '0..1'
898
        );
899
        // verify resource property types are what we expect them to be
900
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
901
        assert(
902
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
903
            '1 side of 1:1 relationship not pointing to resource reference'
904
        );
905
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
906
        assert(
907
            ResourcePropertyKind::RESOURCE_REFERENCE == $targetResourceKind,
908
            '0..1 side of 1:1 relationship not pointing to resource reference'
909
        );
910
    }
911
912
    /**
913
     * To add a complex property to entity or complex type.
914
     *
915
     * @param ResourceType        $targetResourceType  The resource type to which the complex property needs to add
916
     * @param string              $name                name of the complex property
917
     * @param ResourceComplexType $complexResourceType complex resource type
918
     * @param bool                $isBag               complex type is bag or not
919
     *
920
     * @throws InvalidOperationException
921
     * @return ResourceProperty
922
     */
923
    public function addComplexProperty(
924
        ResourceType $targetResourceType,
925
        $name,
926
        ResourceComplexType $complexResourceType,
927
        $isBag = false
928
    ) {
929
        if ($targetResourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY()
930
            && $targetResourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX()
931
        ) {
932
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
933
        }
934
935
        // check that property and resource name don't up and collide - would violate OData spec
936
        if (strtolower($name) == strtolower($targetResourceType->getName())) {
937
            throw new InvalidOperationException(
938
                'Property name must be different from resource name.'
939
            );
940
        }
941
942
        $this->checkInstanceProperty($name, $targetResourceType);
943
944
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
945
        if ($isBag) {
946
            $kind = $kind | ResourcePropertyKind::BAG;
947
        }
948
949
        $resourceProperty = new ResourceProperty($name, null, $kind, $complexResourceType);
950
        $targetResourceType->addProperty($resourceProperty);
951
952
        return $resourceProperty;
953
    }
954
955
    public function createSingleton($name, ResourceType $returnType, $functionName)
956
    {
957
        $msg = null;
958
        if (array_key_exists($name, $this->singletons)) {
959
            $msg = 'Singleton name already exists';
960
            throw new \InvalidArgumentException($msg);
961
        }
962
        if (array_key_exists($name, $this->resourceSets)) {
963
            $msg = 'Resource set with same name, ' . $name . ', exists';
964
            throw new \InvalidArgumentException($msg);
965
        }
966
        $typeName = $returnType->getName();
967
        if (!array_key_exists($typeName, $this->oDataEntityMap)) {
968
            $msg = 'Mapping not defined for ' . $typeName;
969
            throw new \InvalidArgumentException($msg);
970
        }
971
        $metaReturn = $this->oDataEntityMap[$typeName];
972
        $anonymousSet = $this->typeSetMapping[$typeName];
973
        $singleton = $this->getMetadataManager()->createSingleton($name, $metaReturn, $anonymousSet);
974
        assert($singleton->isOK($msg), $msg);
975
        $type = new ResourceFunctionType($functionName, $singleton, $returnType);
976
        // Since singletons should take no args, enforce it here
977
        assert(0 == count($type->getParms()));
978
        $this->singletons[$name] = $type;
979
    }
980
981
    public function getSingletons()
982
    {
983
        return $this->singletons;
984
    }
985
986
    public function callSingleton($name)
987
    {
988
        if (!array_key_exists($name, $this->singletons)) {
989
            $msg = 'Requested singleton does not exist';
990
            throw new \InvalidArgumentException($msg);
991
        }
992
993
        return $this->singletons[$name]->get();
994
    }
995
}
996