Passed
Pull Request — master (#233)
by Alex
06:28
created

addPrimitivePropertyInternal()   B

Complexity

Conditions 8
Paths 18

Size

Total Lines 46
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 23
nc 18
nop 8
dl 0
loc 46
rs 8.4444
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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