Completed
Push — master ( 98373e...8bc8eb )
by Alex
27s queued 11s
created

addReferencePropertyInternalBidirectional()   B

Complexity

Conditions 9
Paths 9

Size

Total Lines 88
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 9
eloc 57
nc 9
nop 6
dl 0
loc 88
rs 7.3826
c 2
b 0
f 0

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

485
        $primitiveResourceType = ResourceType::getPrimitiveResourceType(/** @scrutinizer ignore-type */ $typeCode);
Loading history...
486
487
        $kind = $isKey ? ResourcePropertyKind::PRIMITIVE | ResourcePropertyKind::KEY : ResourcePropertyKind::PRIMITIVE;
488
        if ($isBag) {
489
            $kind = $kind | ResourcePropertyKind::BAG;
490
        }
491
492
        if ($isETagProperty) {
493
            $kind = $kind | ResourcePropertyKind::ETAG;
494
        }
495
496
        $resourceProperty = new ResourceProperty($name, null, /* @scrutinizer ignore-type */$kind, $primitiveResourceType);
497
        $resourceType->addProperty($resourceProperty);
498
        if (array_key_exists($resourceType->getFullName(), $this->oDataEntityMap)) {
499
            $this->metadataManager->addPropertyToEntityType(
500
                $this->oDataEntityMap[$resourceType->getFullName()],
501
                $name,
502
                $primitiveResourceType->getFullName(),
503
                $defaultValue,
504
                $nullable,
505
                $isKey
506
            );
507
        }
508
    }
509
510
    /**
511
     * @param string       $name
512
     * @param ResourceType $resourceType
513
     *
514
     * @throws InvalidOperationException
515
     * @throws \ReflectionException
516
     */
517
    private function checkInstanceProperty($name, ResourceType $resourceType)
518
    {
519
        $instance       = $resourceType->getInstanceType();
520
        $hasMagicGetter = $instance instanceof IType || $instance->hasMethod('__get');
521
        if ($instance instanceof \ReflectionClass) {
522
            $hasMagicGetter |= $instance->isInstance(new \stdClass());
523
        }
524
525
        if (!$hasMagicGetter) {
526
            try {
527
                if ($instance instanceof \ReflectionClass) {
528
                    $instance->getProperty($name);
529
                }
530
            } catch (\ReflectionException $exception) {
531
                throw new InvalidOperationException(
532
                    'Can\'t add a property which does not exist on the instance type.'
533
                );
534
            }
535
        }
536
    }
537
538
    /**
539
     * To add a NonKey-primitive property (Complex/Entity).
540
     *
541
     * @param  ResourceType              $resourceType resource type to which key property
542
     *                                                 is to be added
543
     * @param  string                    $name         name of the key property
544
     * @param  EdmPrimitiveType          $typeCode     type of the key property
545
     * @param  bool                      $isBag        property is bag or not
546
     * @param  null|mixed                $defaultValue
547
     * @param  mixed                     $nullable
548
     * @throws InvalidOperationException
549
     * @throws \ReflectionException
550
     */
551
    public function addPrimitiveProperty(
552
        $resourceType,
553
        $name,
554
        EdmPrimitiveType $typeCode,
555
        $isBag = false,
556
        $defaultValue = null,
557
        $nullable = false
558
    ) {
559
        $this->addPrimitivePropertyInternal(
560
            $resourceType,
561
            $name,
562
            $typeCode,
563
            false,
564
            $isBag,
565
            false,
566
            $defaultValue,
567
            $nullable
568
        );
569
    }
570
571
    /**
572
     * To add a non-key etag property.
573
     *
574
     * @param  ResourceType              $resourceType resource type to which key property
575
     *                                                 is to be added
576
     * @param  string                    $name         name of the property
577
     * @param  EdmPrimitiveType          $typeCode     type of the etag property
578
     * @param  null|mixed                $defaultValue
579
     * @param  mixed                     $nullable
580
     * @throws InvalidOperationException
581
     * @throws \ReflectionException
582
     */
583
    public function addETagProperty($resourceType, $name, EdmPrimitiveType $typeCode, $defaultValue = null, $nullable = false)
584
    {
585
        $this->addPrimitivePropertyInternal(
586
            $resourceType,
587
            $name,
588
            $typeCode,
589
            false,
590
            false,
591
            true,
592
            $defaultValue,
593
            $nullable
594
        );
595
    }
596
597
    /**
598
     * To add a resource reference property.
599
     *
600
     * @param  ResourceEntityType        $resourceType      The resource type to add the resource
601
     *                                                      reference property to
602
     * @param  string                    $name              The name of the property to add
603
     * @param  ResourceSet               $targetResourceSet The resource set the resource reference
604
     *                                                      property points to
605
     * @param  mixed                     $flip
606
     * @param  mixed                     $many
607
     * @param  ResourceEntityType|null   $concreteType      Underlying concrete resource reference type, if set
608
     * @throws InvalidOperationException
609
     * @throws \ReflectionException
610
     */
611
    public function addResourceReferenceProperty(
612
        ResourceEntityType $resourceType,
613
        $name,
614
        ResourceSet $targetResourceSet,
615
        $flip = false,
616
        $many = false,
617
        ResourceEntityType $concreteType = null
618
    ) {
619
        $this->addReferencePropertyInternal(
620
            $resourceType,
621
            $name,
622
            $targetResourceSet,
623
            $flip ? '0..1' : '1',
624
            $many,
625
            $concreteType
626
        );
627
    }
628
629
    /**
630
     * To add a 1:N resource reference property.
631
     *
632
     * @param  ResourceEntityType        $sourceResourceType The resource type to add the resource
633
     *                                                       reference property from
634
     * @param  ResourceEntityType        $targetResourceType The resource type to add the resource
635
     *                                                       reference property to
636
     * @param  string                    $sourceProperty     The name of the property to add, on source type
637
     * @param  string                    $targetProperty     The name of the property to add, on target type
638
     * @param  bool                      $nullable           Is singleton side of relation nullable?
639
     * @throws InvalidOperationException
640
     * @throws \ReflectionException
641
     */
642
    public function addResourceReferencePropertyBidirectional(
643
        ResourceEntityType $sourceResourceType,
644
        ResourceEntityType $targetResourceType,
645
        $sourceProperty,
646
        $targetProperty,
647
        $nullable = false
648
    ) {
649
        $this->addReferencePropertyInternalBidirectional(
650
            $sourceResourceType,
651
            $targetResourceType,
652
            $sourceProperty,
653
            $targetProperty,
654
            '*',
655
            true === $nullable ? '0..1' : '1'
656
        );
657
        // verify resource property types are what we expect them to be
658
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
659
        assert(
660
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
661
            '1 side of 1:N relationship not pointing to resource reference'
662
        );
663
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
664
        assert(
665
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
666
            'N side of 1:N relationship not pointing to resource set reference'
667
        );
668
    }
669
670
    /**
671
     * To add a navigation property (resource set or resource reference)
672
     * to a resource type.
673
     *
674
     * @param ResourceEntityType $sourceResourceType The resource type to add the resource reference
675
     *                                               or resource reference set property to
676
     * @param string             $name               The name of the property to add
677
     * @param ResourceSet        $targetResourceSet  The resource set the
678
     *                                               resource reference or reference
679
     *                                               set property points to
680
     * @param string             $resourceMult       The multiplicity of relation being added
681
     * @param mixed              $many
682
     *
683
     * @param  ResourceEntityType|null   $concreteType
684
     * @throws InvalidOperationException
685
     * @throws \ReflectionException
686
     */
687
    private function addReferencePropertyInternal(
688
        ResourceEntityType $sourceResourceType,
689
        string $name,
690
        ResourceSet $targetResourceSet,
691
        string $resourceMult,
692
        $many = false,
693
        ResourceEntityType $concreteType = null
694
    ) {
695
        $allowedMult   = ['*', '1', '0..1'];
696
        $backMultArray = [ '*' => '*', '1' => '0..1', '0..1' => '1'];
697
        $this->checkInstanceProperty($name, $sourceResourceType);
698
699
        // check that property and resource name don't up and collide - would violate OData spec
700
        if (strtolower($name) == strtolower($sourceResourceType->getName())) {
701
            throw new InvalidOperationException(
702
                'Property name must be different from resource name.'
703
            );
704
        }
705
        assert(in_array($resourceMult, $allowedMult), 'Supplied multiplicity ' . $resourceMult . ' not valid');
706
707
        $resourcePropertyKind = ('*' == $resourceMult)
708
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
709
            : ResourcePropertyKind::RESOURCE_REFERENCE;
710
        $targetResourceType     = $targetResourceSet->getResourceType();
711
        $sourceResourceProperty = new ResourceProperty($name, null, $resourcePropertyKind, $targetResourceType);
0 ignored issues
show
Bug introduced by
$resourcePropertyKind of type integer is incompatible with the type POData\Providers\Metadata\ResourcePropertyKind expected by parameter $kind of POData\Providers\Metadat...Property::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

711
        $sourceResourceProperty = new ResourceProperty($name, null, /** @scrutinizer ignore-type */ $resourcePropertyKind, $targetResourceType);
Loading history...
712
        $sourceResourceType->addProperty($sourceResourceProperty);
713
714
        //Create instance of AssociationSet for this relationship
715
        $sourceResourceSet = $sourceResourceType->getCustomState();
716
        if (!$sourceResourceSet instanceof ResourceSet) {
717
            throw new InvalidOperationException(
718
                'Failed to retrieve the custom state from '
719
                . $sourceResourceType->getName()
720
            );
721
        }
722
723
        //Customer_Orders_Orders, Order_Customer_Customers
724
        //(source type::name _ source property::name _ target set::name)
725
        $setKey = ResourceAssociationSet::keyName($sourceResourceType, $name, $targetResourceSet);
726
        //$setKey = $sourceResourceType->getName() . '_' . $name . '_' . $targetResourceType->getName();
727
        $set = new ResourceAssociationSet(
728
            $setKey,
729
            new ResourceAssociationSetEnd(
730
                $sourceResourceSet,
731
                $sourceResourceType,
732
                $sourceResourceProperty
733
            ),
734
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, null, $concreteType)
735
        );
736
        $mult     = $resourceMult;
737
        $backMult = $many ? '*' : $backMultArray[$resourceMult];
738
        if (false === $many && '*' == $backMult && '*' == $resourceMult) {
739
            $backMult = '1';
740
        }
741
        $this->getMetadataManager()->addNavigationPropertyToEntityType(
742
            $this->oDataEntityMap[$sourceResourceType->getFullName()],
743
            $mult,
744
            $name,
745
            $this->oDataEntityMap[$targetResourceType->getFullName()],
746
            $backMult
747
        );
748
        $this->associationSets[$setKey] = $set;
749
    }
750
751
    /**
752
     * To add a navigation property (resource set or resource reference)
753
     * to a resource type.
754
     *
755
     * @param ResourceEntityType $sourceResourceType The source resource type to add
756
     *                                               the resource reference
757
     *                                               or resource reference set property to
758
     * @param ResourceEntityType $targetResourceType The target resource type to add
759
     *                                               the resource reference
760
     *                                               or resource reference set property to
761
     * @param string             $sourceProperty     The name of the
762
     *                                               property to add to source type
763
     * @param string             $targetProperty     The name of the
764
     *                                               property to add to target type
765
     * @param string             $sourceMultiplicity The multiplicity at the source end of relation
766
     * @param string             $targetMultiplicity The multiplicity at the target end of relation
767
     *
768
     * @throws InvalidOperationException
769
     * @throws \ReflectionException
770
     */
771
    private function addReferencePropertyInternalBidirectional(
772
        ResourceEntityType $sourceResourceType,
773
        ResourceEntityType $targetResourceType,
774
        string $sourceProperty,
775
        string $targetProperty,
776
        string $sourceMultiplicity,
777
        string $targetMultiplicity
778
    ) {
779
        $this->checkInstanceProperty($sourceProperty, $sourceResourceType);
780
        $this->checkInstanceProperty($targetProperty, $targetResourceType);
781
782
        // check that property and resource name don't up and collide - would violate OData spec
783
        if (strtolower($sourceProperty) == strtolower($sourceResourceType->getName())) {
784
            throw new InvalidOperationException(
785
                'Source property name must be different from source resource name.'
786
            );
787
        }
788
        if (strtolower($targetProperty) == strtolower($targetResourceType->getName())) {
789
            throw new InvalidOperationException(
790
                'Target property name must be different from target resource name.'
791
            );
792
        }
793
794
        //Create instance of AssociationSet for this relationship
795
        $sourceResourceSet = $sourceResourceType->getCustomState();
796
        if (!$sourceResourceSet instanceof ResourceSet) {
797
            throw new InvalidOperationException(
798
                'Failed to retrieve the custom state from '
799
                . $sourceResourceType->getName()
800
            );
801
        }
802
        $targetResourceSet = $targetResourceType->getCustomState();
803
        if (!$targetResourceSet instanceof ResourceSet) {
804
            throw new InvalidOperationException(
805
                'Failed to retrieve the custom state from '
806
                . $targetResourceType->getName()
807
            );
808
        }
809
810
        //Customer_Orders_Orders, Order_Customer_Customers
811
        $fwdSetKey = ResourceAssociationSet::keyName($sourceResourceType, $sourceProperty, $targetResourceSet);
812
        $revSetKey = ResourceAssociationSet::keyName($targetResourceType, $targetProperty, $sourceResourceSet);
813
        if (isset($this->associationSets[$fwdSetKey]) && isset($this->associationSets[$revSetKey])) {
814
            return;
815
        }
816
        $sourceKind = ('*' == $sourceMultiplicity)
817
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
818
            : ResourcePropertyKind::RESOURCE_REFERENCE;
819
        $targetKind = ('*' == $targetMultiplicity)
820
            ? ResourcePropertyKind::RESOURCESET_REFERENCE
821
            : ResourcePropertyKind::RESOURCE_REFERENCE;
822
823
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, $targetKind, $targetResourceType);
0 ignored issues
show
Bug introduced by
$targetKind of type integer is incompatible with the type POData\Providers\Metadata\ResourcePropertyKind expected by parameter $kind of POData\Providers\Metadat...Property::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

823
        $sourceResourceProperty = new ResourceProperty($sourceProperty, null, /** @scrutinizer ignore-type */ $targetKind, $targetResourceType);
Loading history...
824
        assert(
825
            $targetKind == $sourceResourceProperty->getKind(),
826
            'Resource property kind mismatch between $targetKind and $sourceResourceProperty'
827
        );
828
        $sourceResourceType->addProperty($sourceResourceProperty, false);
829
        $targetResourceProperty = new ResourceProperty($targetProperty, null, $sourceKind, $sourceResourceType);
830
        assert(
831
            $sourceKind == $targetResourceProperty->getKind(),
832
            'Resource property kind mismatch between $sourceKind and $targetResourceProperty'
833
        );
834
        $targetResourceType->addProperty($targetResourceProperty, false);
835
836
        //TODO: Audit this, figure out how it makes metadata go sproing
837
        $fwdSet = new ResourceAssociationSet(
838
            $fwdSetKey,
839
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty),
840
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty)
841
        );
842
        $revSet = new ResourceAssociationSet(
843
            $revSetKey,
844
            new ResourceAssociationSetEnd($targetResourceSet, $targetResourceType, $targetResourceProperty),
845
            new ResourceAssociationSetEnd($sourceResourceSet, $sourceResourceType, $sourceResourceProperty)
846
        );
847
        $sourceName = $sourceResourceType->getFullName();
848
        $targetName = $targetResourceType->getFullName();
849
        $this->getMetadataManager()->addNavigationPropertyToEntityType(
850
            $this->oDataEntityMap[$sourceName],
851
            $sourceMultiplicity,
852
            $sourceProperty,
853
            $this->oDataEntityMap[$targetName],
854
            $targetMultiplicity,
855
            $targetProperty
856
        );
857
        $this->associationSets[$fwdSetKey] = $fwdSet;
858
        $this->associationSets[$revSetKey] = $revSet;
859
    }
860
861
    /**
862
     * To add a resource set reference property.
863
     *
864
     * @param  ResourceEntityType        $resourceType      The resource type to add the
865
     *                                                      resource reference set property to
866
     * @param  string                    $name              The name of the property to add
867
     * @param  ResourceSet               $targetResourceSet The resource set the resource
868
     *                                                      reference set property points to
869
     * @param  ResourceEntityType|null   $concreteType      Underlying concrete resource type, if set
870
     * @param  mixed                     $single
871
     * @throws InvalidOperationException
872
     * @throws \ReflectionException
873
     */
874
    public function addResourceSetReferenceProperty(
875
        ResourceEntityType $resourceType,
876
        $name,
877
        ResourceSet $targetResourceSet,
878
        ResourceEntityType $concreteType = null,
879
        $single = false
880
    ) {
881
        $this->addReferencePropertyInternal(
882
            $resourceType,
883
            $name,
884
            $targetResourceSet,
885
            '*',
886
            (true === $single) ? false : null,
887
            $concreteType
888
        );
889
    }
890
891
    /**
892
     * To add a M:N resource reference property.
893
     *
894
     * @param  ResourceEntityType        $sourceResourceType The resource type to add the resource
895
     *                                                       reference property from
896
     * @param  ResourceEntityType        $targetResourceType The resource type to add the resource
897
     *                                                       reference property to
898
     * @param  string                    $sourceProperty     The name of the property to add, on source type
899
     * @param  string                    $targetProperty     The name of the property to add, on target type
900
     * @throws InvalidOperationException
901
     * @throws \ReflectionException
902
     */
903
    public function addResourceSetReferencePropertyBidirectional(
904
        ResourceEntityType $sourceResourceType,
905
        ResourceEntityType $targetResourceType,
906
        $sourceProperty,
907
        $targetProperty
908
    ) {
909
        $this->addReferencePropertyInternalBidirectional(
910
            $sourceResourceType,
911
            $targetResourceType,
912
            $sourceProperty,
913
            $targetProperty,
914
            '*',
915
            '*'
916
        );
917
        // verify resource property types are what we expect them to be
918
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
919
        assert(
920
            ResourcePropertyKind::RESOURCESET_REFERENCE == $sourceResourceKind,
921
            'M side of M:N relationship not pointing to resource set reference'
922
        );
923
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
924
        assert(
925
            ResourcePropertyKind::RESOURCESET_REFERENCE == $targetResourceKind,
926
            'N side of M:N relationship not pointing to resource set reference'
927
        );
928
    }
929
930
    /**
931
     * To add a 1-1 resource reference.
932
     *
933
     * @param  ResourceEntityType        $sourceResourceType The resource type to add the resource
934
     *                                                       reference property from
935
     * @param  ResourceEntityType        $targetResourceType The resource type to add the resource
936
     *                                                       reference property to
937
     * @param  string                    $sourceProperty     The name of the property to add, on source type
938
     * @param  string                    $targetProperty     The name of the property to add, on target type
939
     * @throws InvalidOperationException
940
     * @throws \ReflectionException
941
     */
942
    public function addResourceReferenceSinglePropertyBidirectional(
943
        ResourceEntityType $sourceResourceType,
944
        ResourceEntityType $targetResourceType,
945
        $sourceProperty,
946
        $targetProperty
947
    ) {
948
        $this->addReferencePropertyInternalBidirectional(
949
            $sourceResourceType,
950
            $targetResourceType,
951
            $sourceProperty,
952
            $targetProperty,
953
            '1',
954
            '0..1'
955
        );
956
        // verify resource property types are what we expect them to be
957
        $sourceResourceKind = $sourceResourceType->resolveProperty($sourceProperty)->getKind();
958
        assert(
959
            ResourcePropertyKind::RESOURCE_REFERENCE == $sourceResourceKind,
960
            '1 side of 1:1 relationship not pointing to resource reference'
961
        );
962
        $targetResourceKind = $targetResourceType->resolveProperty($targetProperty)->getKind();
963
        assert(
964
            ResourcePropertyKind::RESOURCE_REFERENCE == $targetResourceKind,
965
            '0..1 side of 1:1 relationship not pointing to resource reference'
966
        );
967
    }
968
969
    /**
970
     * To add a complex property to entity or complex type.
971
     *
972
     * @param ResourceType        $targetResourceType  The resource type to which the complex property needs to add
973
     * @param string              $name                name of the complex property
974
     * @param ResourceComplexType $complexResourceType complex resource type
975
     * @param bool                $isBag               complex type is bag or not
976
     *
977
     * @throws InvalidOperationException
978
     * @throws \ReflectionException
979
     * @return ResourceProperty
980
     */
981
    public function addComplexProperty(
982
        ResourceType $targetResourceType,
983
        $name,
984
        ResourceComplexType $complexResourceType,
985
        $isBag = false
986
    ) {
987
        if ($targetResourceType->getResourceTypeKind() != ResourceTypeKind::ENTITY()
988
            && $targetResourceType->getResourceTypeKind() != ResourceTypeKind::COMPLEX()
989
        ) {
990
            throw new InvalidOperationException('Complex property can be added to an entity or another complex type');
991
        }
992
993
        // check that property and resource name don't up and collide - would violate OData spec
994
        if (strtolower($name) == strtolower($targetResourceType->getName())) {
995
            throw new InvalidOperationException(
996
                'Property name must be different from resource name.'
997
            );
998
        }
999
1000
        $this->checkInstanceProperty($name, $targetResourceType);
1001
1002
        $kind = ResourcePropertyKind::COMPLEX_TYPE;
1003
        if ($isBag) {
1004
            $kind = $kind | ResourcePropertyKind::BAG;
1005
        }
1006
1007
        $resourceProperty = new ResourceProperty(
1008
            $name,
1009
            null,
1010
            /* @scrutinizer ignore-type */
1011
            $kind,
1012
            $complexResourceType
1013
        );
1014
        $targetResourceType->addProperty($resourceProperty);
1015
1016
        return $resourceProperty;
1017
    }
1018
1019
    /**
1020
     * @param  string       $name
1021
     * @param  ResourceType $returnType
1022
     * @param  array|string $functionName
1023
     * @return mixed|void
1024
     */
1025
    public function createSingleton($name, ResourceType $returnType, $functionName)
1026
    {
1027
        $msg = null;
1028
        if (array_key_exists($name, $this->singletons)) {
1029
            $msg = 'Singleton name already exists';
1030
            throw new \InvalidArgumentException($msg);
1031
        }
1032
        if (array_key_exists($name, $this->resourceSets)) {
1033
            $msg = 'Resource set with same name, ' . $name . ', exists';
1034
            throw new \InvalidArgumentException($msg);
1035
        }
1036
        $typeName = $returnType->getFullName();
1037
        if (!array_key_exists($typeName, $this->oDataEntityMap)) {
1038
            $msg = 'Mapping not defined for ' . $typeName;
1039
            throw new \InvalidArgumentException($msg);
1040
        }
1041
        $metaReturn   = $this->oDataEntityMap[$typeName];
1042
        $anonymousSet = $this->typeSetMapping[$typeName];
1043
        $singleton    = $this->getMetadataManager()->createSingleton($name, $metaReturn, $anonymousSet);
1044
        assert($singleton->isOK($msg), $msg);
1045
        $type = new ResourceFunctionType($functionName, $singleton, $returnType);
1046
        // Since singletons should take no args, enforce it here
1047
        assert(0 == count($type->getParms()));
1048
        $this->singletons[$name] = $type;
1049
    }
1050
1051
    /**
1052
     * @return array
1053
     */
1054
    public function getSingletons()
1055
    {
1056
        return $this->singletons;
1057
    }
1058
1059
    /**
1060
     * @param  string $name
1061
     * @return mixed
1062
     */
1063
    public function callSingleton($name)
1064
    {
1065
        if (!array_key_exists($name, $this->singletons)) {
1066
            $msg = 'Requested singleton does not exist';
1067
            throw new \InvalidArgumentException($msg);
1068
        }
1069
1070
        return $this->singletons[$name]->get();
1071
    }
1072
}
1073