Failed Conditions
Pull Request — master (#50)
by Alex
04:03
created

ModelHelpers::FindVocabularyAnnotations()   D

Complexity

Conditions 20
Paths 12

Size

Total Lines 68
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 20
eloc 37
c 1
b 0
f 0
nc 12
nop 4
dl 0
loc 68
rs 4.1666

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace AlgoWeb\ODataMetadata\Helpers;
6
7
use AlgoWeb\ODataMetadata\Asserts;
8
use AlgoWeb\ODataMetadata\Csdl\Internal\Serialization\Helpers\AssociationAnnotations;
9
use AlgoWeb\ODataMetadata\Csdl\Internal\Serialization\Helpers\AssociationSetAnnotations;
10
use AlgoWeb\ODataMetadata\CsdlConstants;
11
use AlgoWeb\ODataMetadata\EdmConstants;
12
use AlgoWeb\ODataMetadata\EdmUtil;
13
use AlgoWeb\ODataMetadata\Helpers\Interfaces\IModelHelpers;
14
use AlgoWeb\ODataMetadata\Interfaces\Annotations\IDirectValueAnnotationsManager;
15
use AlgoWeb\ODataMetadata\Interfaces\Annotations\IVocabularyAnnotation;
16
use AlgoWeb\ODataMetadata\Interfaces\IEdmElement;
17
use AlgoWeb\ODataMetadata\Interfaces\IEntityContainer;
18
use AlgoWeb\ODataMetadata\Interfaces\IEntitySet;
19
use AlgoWeb\ODataMetadata\Interfaces\IFunction;
20
use AlgoWeb\ODataMetadata\Interfaces\IModel;
21
use AlgoWeb\ODataMetadata\Interfaces\INavigationProperty;
22
use AlgoWeb\ODataMetadata\Interfaces\ISchemaElement;
23
use AlgoWeb\ODataMetadata\Interfaces\ISchemaType;
24
use AlgoWeb\ODataMetadata\Interfaces\IStructuredType;
25
use AlgoWeb\ODataMetadata\Interfaces\ITerm;
26
use AlgoWeb\ODataMetadata\Interfaces\IValueTerm;
27
use AlgoWeb\ODataMetadata\Interfaces\IVocabularyAnnotatable;
28
use AlgoWeb\ODataMetadata\Internal\RegistrationHelper;
29
use AlgoWeb\ODataMetadata\Version;
30
use SplObjectStorage;
31
32
/**
33
 * Trait ModelHelpers.
34
 * @package AlgoWeb\ODataMetadata\Helpers
35
 */
36
trait ModelHelpers
37
{
38
    public function GetNamespaceAliases(): array
39
    {
40
        /** @var IModel $this */
41
        /** @var array|null $result */
42
        $result = $this->GetAnnotationValue(
43
            'array',
44
            $this,
0 ignored issues
show
Bug introduced by
$this of type AlgoWeb\ODataMetadata\Helpers\ModelHelpers is incompatible with the type AlgoWeb\ODataMetadata\Interfaces\IEdmElement expected by parameter $element of AlgoWeb\ODataMetadata\He...s::GetAnnotationValue(). ( Ignorable by Annotation )

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

44
            /** @scrutinizer ignore-type */ $this,
Loading history...
45
            EdmConstants::InternalUri,
46
            CsdlConstants::NamespaceAliasAnnotation
47
        );
48
        return $result ?? [];
49
    }
50
51
    // This internal method exists so we can get a consistent view of the mappings through the entire serialization
52
    // process. Otherwise, changes to the dictionary during serialization would result in an invalid or inconsistent
53
    // output.
54
55
    public function GetAnnotationValue(
56
        string $typeof,
57
        IEdmElement $element,
58
        string $namespaceName = null,
59
        string $localName = null
60
    ) {
61
        $namespaceName = $namespaceName ?? EdmConstants::InternalUri;
62
        $localName     = $localName ?? Helpers::classNameToLocalName($typeof);
63
        return Helpers::AnnotationValue(
64
            $typeof,
65
            $this->getDirectValueAnnotationsManager()->getAnnotationValue(
66
                $element,
67
                $namespaceName,
68
                $localName
69
            )
70
        );
71
    }
72
73
    abstract public function getDirectValueAnnotationsManager(): IDirectValueAnnotationsManager;
74
75
    /**
76
     * Searches for an entity container with the given name in this model and all referenced models and returns null
77
     * if no such entity container exists.
78
     *
79
     * @param  string           $qualifiedName the qualified name of the entity container being found
80
     * @return IEntityContainer the requested entity container, or null if no such entity container exists
81
     */
82
    public function FindEntityContainer(string $qualifiedName): ?IEntityContainer
83
    {
84
        EdmUtil::checkArgumentNull($qualifiedName, "$qualifiedName");
85
86
        return $this->FindAcrossModels(
87
            $qualifiedName,
88
            self::EntityContainerFinder(),
89
            [RegistrationHelper::class, 'CreateAmbiguousEntityContainerBinding']
90
        );
91
    }
92
93
    private function FindAcrossModels(string $qualifiedName, callable $finder, callable $ambiguousCreator)
94
    {
95
        $model = $this;
96
        Asserts::assertSignatureMatches(
97
            function (/** @scrutinizer ignore-unused */IModel $model, /** @scrutinizer ignore-unused */string $qualifiedName) {
98
            },
99
            $finder,
100
            '$finder'
101
        );
102
        Asserts::assertSignatureMatches(
103
            function (/** @scrutinizer ignore-unused */$candidate, /** @scrutinizer ignore-unused */$fromReference) {
104
            },
105
            $ambiguousCreator,
106
            '$ambiguousCreator'
107
        );
108
        $candidate = $finder($model, $qualifiedName);
109
        foreach ($model->getReferencedModels() as $reference) {
110
            $fromReference = $finder($reference, $qualifiedName);
111
            if ($fromReference !== null) {
112
                $candidate = $candidate === null ? $fromReference : $ambiguousCreator($candidate, $fromReference);
113
            }
114
        }
115
        return $candidate;
116
    }
117
118
    /**
119
     * @return IModel[] gets the collection of models referred to by this model
120
     */
121
    abstract public function getReferencedModels(): array;
122
123
    public static function EntityContainerFinder(): callable
124
    {
125
        return function (IModel $model, string $qualifiedName): ?IEntityContainer {
126
            return $model->findDeclaredEntityContainer($qualifiedName);
127
        };
128
    }
129
130
    /**
131
     * Searches for a value term with the given name in this model and all referenced models and returns null if no
132
     * such value term exists.
133
     *
134
     * @param  string     $qualifiedName the qualified name of the value term being found
135
     * @return IValueTerm the requested value term, or null if no such value term exists
136
     */
137
    public function FindValueTerm(string $qualifiedName): ?IValueTerm
138
    {
139
        EdmUtil::checkArgumentNull($qualifiedName, 'qualifiedName');
140
141
        return $this->FindAcrossModels(
142
            $qualifiedName,
143
            self::ValueTermFinder(),
144
            [RegistrationHelper::class, 'CreateAmbiguousValueTermBinding']
145
        );
146
    }
147
148
    private static function ValueTermFinder(): callable
149
    {
150
        return function (IModel $model, string $qualifiedName): ?IValueTerm {
151
            return $model->findDeclaredValueTerm($qualifiedName);
152
        };
153
    }
154
155
    /**
156
     * Searches for functions with the given name in this model and all referenced models and returns an empty
157
     * enumerable if no such functions exist.
158
     *
159
     * @param  string      $qualifiedName the qualified name of the functions being found
160
     * @return IFunction[] the requested functions
161
     */
162
    public function FindFunctions(string $qualifiedName): array
163
    {
164
        EdmUtil::checkArgumentNull($qualifiedName, 'qualifiedName');
165
166
        return $this->FindAcrossModels($qualifiedName, self::FunctionsFinder(), self::mergeFunctions()) ?? [];
167
    }
168
169
    private static function FunctionsFinder(): callable
170
    {
171
        return function (IModel $model, string $qualifiedName): array {
172
            return $model->findDeclaredFunctions($qualifiedName);
173
        };
174
    }
175
176
    private static function mergeFunctions(): callable
177
    {
178
        return function (array $f1, array $f2): array {
179
            return array_merge($f1, $f2);
180
        };
181
    }
182
183
    /**
184
     * Gets the value for the EDM version of the Model.
185
     *
186
     * @return Version the version
187
     */
188
    public function GetEdmVersion(): ?Version
189
    {
190
        /** @var IModel $this */
191
        return $this->GetAnnotationValue(
192
            Version::class,
193
            $this,
194
            EdmConstants::InternalUri,
195
            EdmConstants::EdmVersionAnnotation
196
        );
197
    }
198
199
    /**
200
     * Sets a value of EDM version attribute of the Model.
201
     *
202
     * @param Version $version the version
203
     */
204
    public function SetEdmVersion(Version $version)
205
    {
206
        /** @var IModel $this */
207
        $this->SetAnnotationValue($this, EdmConstants::InternalUri, EdmConstants::EdmVersionAnnotation, $version);
208
    }
209
210
    /**
211
     * Sets an annotation value for an EDM element. If the value is null, no annotation is added and an existing
212
     * annotation with the same name is removed.
213
     *
214
     * @param IEdmElement  $element       the annotated element
215
     * @param string       $namespaceName namespace that the annotation belongs to
216
     * @param string       $localName     name of the annotation within the namespace
217
     * @param mixed|object $value         value of the new annotation
218
     */
219
    public function SetAnnotationValue(IEdmElement $element, string $namespaceName, string $localName, $value)
220
    {
221
        $this->getDirectValueAnnotationsManager()->setAnnotationValue($element, $namespaceName, $localName, $value);
222
    }
223
224
    /**
225
     * Gets the value for the EDMX version of the model.
226
     *
227
     * @return Version the version
228
     */
229
    public function GetEdmxVersion(): ?Version
230
    {
231
        /** @var IModel $this */
232
        return $this->GetAnnotationValue(
233
            Version::class,
234
            $this,
235
            EdmConstants::InternalUri,
236
            CsdlConstants::EdmxVersionAnnotation
237
        );
238
    }
239
240
    /**
241
     *  Sets a value of EDMX version attribute of the model.
242
     *
243
     * @param Version $version the version
244
     */
245
    public function SetEdmxVersion(Version $version): void
246
    {
247
        /** @var IModel $this */
248
        $this->SetAnnotationValue($this, EdmConstants::InternalUri, CsdlConstants::EdmxVersionAnnotation, $version);
249
    }
250
251
    /**
252
     * Sets a value for the DataServiceVersion attribute in an EDMX artifact.
253
     *
254
     * @param Version $version the value of the attribute
255
     */
256
    public function SetDataServiceVersion(Version $version): void
257
    {
258
        /** @var IModel $this */
259
        $this->SetAnnotationValue($this, EdmConstants::InternalUri, EdmConstants::DataServiceVersion, $version);
260
    }
261
262
    /**
263
     * Gets the value for the DataServiceVersion attribute used during EDMX serialization.
264
     *
265
     * @return Version value of the attribute
266
     */
267
    public function GetDataServiceVersion(): ?Version
268
    {
269
        /** @var IModel $this */
270
        return $this->GetAnnotationValue(
271
            Version::class,
272
            $this,
273
            EdmConstants::InternalUri,
274
            EdmConstants::DataServiceVersion
275
        );
276
    }
277
278
    /**
279
     * Sets a value for the MaxDataServiceVersion attribute in an EDMX artifact.
280
     *
281
     * @param Version $version the value of the attribute
282
     */
283
    public function SetMaxDataServiceVersion(Version $version): void
284
    {
285
        /** @var IModel $this */
286
        $this->SetAnnotationValue($this, EdmConstants::InternalUri, EdmConstants::MaxDataServiceVersion, $version);
287
    }
288
289
    /**
290
     * Gets the value for the MaxDataServiceVersion attribute used during EDMX serialization.
291
     *
292
     * @return Version value of the attribute
293
     */
294
    public function GetMaxDataServiceVersion(): ?Version
295
    {
296
        /** @var IModel $this */
297
        return $this->GetAnnotationValue(
298
            Version::class,
299
            $this,
300
            EdmConstants::InternalUri,
301
            EdmConstants::MaxDataServiceVersion
302
        );
303
    }
304
305
    /**
306
     * Sets an annotation on the IEdmModel to notify the serializer of preferred prefix mappings for xml namespaces.
307
     *
308
     * @param array $mappings xmlNamespaceManage containing mappings between namespace prefixes and xml namespaces
309
     */
310
    public function SetNamespacePrefixMappings(array $mappings): void
311
    {
312
        /** @var IModel $this */
313
        $this->SetAnnotationValue(
314
            $this,
315
            EdmConstants::InternalUri,
316
            CsdlConstants::NamespacePrefixAnnotation,
317
            $mappings
318
        );
319
    }
320
321
    /**
322
     * Gets the preferred prefix mappings for xml namespaces from an IEdmModel.
323
     *
324
     * @return array namespace prefixes that exist on the model
325
     */
326
    public function GetNamespacePrefixMappings(): array
327
    {
328
        /** @var IModel $this */
329
        /** @var array|null $result */
330
        $result = $this->GetAnnotationValue(
331
            'array',
332
            $this,
0 ignored issues
show
Bug introduced by
$this of type AlgoWeb\ODataMetadata\Helpers\ModelHelpers is incompatible with the type AlgoWeb\ODataMetadata\Interfaces\IEdmElement expected by parameter $element of AlgoWeb\ODataMetadata\He...s::GetAnnotationValue(). ( Ignorable by Annotation )

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

332
            /** @scrutinizer ignore-type */ $this,
Loading history...
333
            EdmConstants::InternalUri,
334
            CsdlConstants::NamespacePrefixAnnotation
335
        );
336
337
        return $result ?? [];
338
    }
339
340
    /**
341
     * Sets the name used for the association end serialized for a navigation property.
342
     *
343
     * @param INavigationProperty $property    the navigation property
344
     * @param string              $association the association end name
345
     */
346
    public function SetAssociationEndName(INavigationProperty $property, string $association): void
347
    {
348
        $this->SetAnnotationValue(
349
            $property,
350
            EdmConstants::InternalUri,
351
            CsdlConstants::AssociationEndNameAnnotation,
352
            $association
353
        );
354
    }
355
356
    /**
357
     * Gets the name used for the association end serialized for a navigation property.
358
     *
359
     * @param  INavigationProperty $property the navigation property
360
     * @return string              the association end name
361
     */
362
    public function GetAssociationEndName(INavigationProperty $property): string
363
    {
364
        $property->PopulateCaches();
365
        /** @var string|null $result */
366
        $result = $this->GetAnnotationValue(
367
            'string',
368
            $property,
369
            EdmConstants::InternalUri,
370
            CsdlConstants::AssociationEndNameAnnotation
371
        );
372
373
        return $result ?? ($property->getPartner()->getName() ?? '');
374
    }
375
376
    /**
377
     * Gets the fully-qualified name used for the association serialized for a navigation property.
378
     *
379
     * @param  INavigationProperty $property the navigation property
380
     * @return string              the fully-qualified association name
381
     */
382
    public function GetAssociationFullName(INavigationProperty $property): string
383
    {
384
        $property->PopulateCaches();
385
        return $this->GetAssociationNamespace($property) . '.' . $this->GetAssociationName($property);
386
    }
387
388
    /**
389
     * Gets the namespace used for the association serialized for a navigation property.
390
     *
391
     * @param  INavigationProperty $property the navigation property
392
     * @return string              the association namespace
393
     */
394
    public function GetAssociationNamespace(INavigationProperty $property): string
395
    {
396
        $model = $this;
397
        assert($model instanceof IModel);
398
399
        $property->PopulateCaches();
400
        $associationNamespace = $model->GetAnnotationValue(
401
            '?string',
402
            $property,
403
            EdmConstants::InternalUri,
404
            CsdlConstants::AssociationNamespaceAnnotation
405
        );
406
        if ($associationNamespace == null) {
407
            $associationNamespace = $property->GetPrimary()->DeclaringEntityType()->getNamespace();
408
        }
409
410
        return $associationNamespace;
411
    }
412
413
    /**
414
     * Gets the name used for the association serialized for a navigation property.
415
     *
416
     * @param  INavigationProperty $property the navigation property
417
     * @return string              the association name
418
     */
419
    public function GetAssociationName(INavigationProperty $property): string
420
    {
421
        $model = $this;
422
        assert($model instanceof IModel);
423
424
        $property->PopulateCaches();
425
        $associationName = $model->GetAnnotationValue(
426
            '?string',
427
            $property,
428
            EdmConstants::InternalUri,
429
            CsdlConstants::AssociationNameAnnotation
430
        );
431
        if ($associationName == null) {
432
            $fromPrincipal = $property->GetPrimary();
433
            $toPrincipal   = $fromPrincipal->getPartner();
434
435
            $associationName =
436
                Helpers::GetQualifiedAndEscapedPropertyName($toPrincipal) .
437
                Helpers::AssociationNameEscapeChar .
438
                Helpers::GetQualifiedAndEscapedPropertyName($fromPrincipal);
439
        }
440
441
        return $associationName;
442
    }
443
444
    public function FindType(string $qualifiedName): ?ISchemaType
445
    {
446
        $findTypeMethod = self::findTypec();
447
        /** @var IModel $this */
448
        return Helpers::FindAcrossModels(
449
            $this,
450
            $qualifiedName,
451
            $findTypeMethod,
452
            [RegistrationHelper::class, 'CreateAmbiguousTypeBinding']
453
        );
454
    }
455
456
    private static function findTypec(): callable
457
    {
458
        return function (IModel $model, string $qualifiedName): ?ISchemaType {
459
            return $model->findDeclaredType($qualifiedName);
460
        };
461
    }
462
463
    /**
464
     * Sets the name used for the association serialized for a navigation property.
465
     *
466
     * @param INavigationProperty $property        the navigation property
467
     * @param string              $associationName the association name
468
     */
469
    public function SetAssociationName(INavigationProperty $property, string $associationName): void
470
    {
471
        $model = $this;
472
        assert($model instanceof IModel);
473
474
        $model->SetAnnotationValue(
475
            $property,
476
            EdmConstants::InternalUri,
477
            CsdlConstants::AssociationNameAnnotation,
478
            $associationName
479
        );
480
    }
481
482
    /**
483
     * Sets the namespace used for the association serialized for a navigation property.
484
     *
485
     * @param INavigationProperty $property             the navigation property
486
     * @param string              $associationNamespace the association namespace
487
     */
488
    public function SetAssociationNamespace(INavigationProperty $property, string $associationNamespace): void
489
    {
490
        $model = $this;
491
        assert($model instanceof IModel);
492
493
        $model->SetAnnotationValue(
494
            $property,
495
            EdmConstants::InternalUri,
496
            CsdlConstants::AssociationNamespaceAnnotation,
497
            $associationNamespace
498
        );
499
    }
500
501
    /**
502
     * Sets the name used for the association set serialized for a navigation property of an entity set.
503
     *
504
     * @param IEntitySet          $entitySet      The entity set
505
     * @param INavigationProperty $property       the navigation property
506
     * @param string              $associationSet the association set name
507
     */
508
    public function SetAssociationSetName(
509
        IEntitySet $entitySet,
510
        INavigationProperty $property,
511
        string $associationSet
512
    ): void {
513
        $model = $this;
514
        assert($model instanceof IModel);
515
        $navigationPropertyMappings = $model->GetAnnotationValue(
516
            'SplObjectStorage',
517
            $entitySet,
518
            EdmConstants::InternalUri,
519
            CsdlConstants::AssociationSetNameAnnotation
520
        );
521
        if ($navigationPropertyMappings == null) {
522
            $navigationPropertyMappings = new SplObjectStorage();
523
            $model->SetAnnotationValue(
524
                $entitySet,
525
                EdmConstants::InternalUri,
526
                CsdlConstants::AssociationSetNameAnnotation,
527
                $navigationPropertyMappings
528
            );
529
        }
530
531
        $navigationPropertyMappings->offsetSet($property, $associationSet);
532
    }
533
534
    /**
535
     * Gets the name used for the association set serialized for a navigation property of an entity set.
536
     *
537
     * @param  IEntitySet          $entitySet the entity set
538
     * @param  INavigationProperty $property  the navigation property
539
     * @return string              the association set name
540
     */
541
    public function GetAssociationSetName(IEntitySet $entitySet, INavigationProperty $property): string
542
    {
543
        $model = $this;
544
        assert($model instanceof IModel);
545
546
        $navigationPropertyMappings = $model->GetAnnotationValue(
547
            SplObjectStorage::class,
548
            $entitySet,
549
            EdmConstants::InternalUri,
550
            CsdlConstants::AssociationSetNameAnnotation
551
        );
552
        assert($navigationPropertyMappings instanceof SplObjectStorage || $navigationPropertyMappings === null);
553
        if ($navigationPropertyMappings !== null && $navigationPropertyMappings->offsetExists($property)) {
554
            $associationSetName = $navigationPropertyMappings->offsetGet($property);
555
        } else {
556
            $associationSetName = $model->GetAssociationName($property) . 'Set';
557
        }
558
559
        return $associationSetName;
560
    }
561
562
    /**
563
     * Gets the annotations associated with the association serialized for a navigation target of an entity set.
564
     *
565
     * @param IEntitySet          $entitySet       the entity set
566
     * @param INavigationProperty $property        the navigation property
567
     * @param iterable            $annotations     the association set annotations
568
     * @param iterable            $end1Annotations the annotations for association set end 1
569
     * @param iterable            $end2Annotations the annotations for association set end 2
570
     */
571
    public function GetAssociationSetAnnotations(
572
        IEntitySet $entitySet,
573
        INavigationProperty $property,
574
        iterable &$annotations = [],
575
        iterable &$end1Annotations = [],
576
        iterable &$end2Annotations = []
577
    ): void {
578
        /** @var SplObjectStorage $navigationPropertyMappings */
579
        $navigationPropertyMappings = $this->GetAnnotationValue(
580
            SplObjectStorage::class,
581
            $entitySet,
582
            EdmConstants::InternalUri,
583
            CsdlConstants::AssociationSetAnnotationsAnnotation
584
        );
585
        if ($navigationPropertyMappings != null && $navigationPropertyMappings->offsetExists($property)) {
586
            /** @var AssociationSetAnnotations $associationSetAnnotations */
587
            $associationSetAnnotations = $navigationPropertyMappings[$property];
588
            $annotations               = $associationSetAnnotations->Annotations ?? [];
589
            $end1Annotations           = $associationSetAnnotations->End1Annotations ?? [];
590
            $end2Annotations           = $associationSetAnnotations->End2Annotations ?? [];
591
        } else {
592
            $empty           = [];
593
            $annotations     = $empty;
594
            $end1Annotations = $empty;
595
            $end2Annotations = $empty;
596
        }
597
    }
598
599
    /**
600
     * Gets the annotations associated with the association serialized for a navigation property.
601
     *
602
     * @param INavigationProperty $property              the navigation property
603
     * @param iterable            $annotations           the association annotations
604
     * @param iterable            $end1Annotations       the annotations for association end 1
605
     * @param iterable            $end2Annotations       the annotations for association end 2
606
     * @param iterable            $constraintAnnotations the annotations for the referential constraint
607
     */
608
    public function GetAssociationAnnotations(
609
        INavigationProperty $property,
610
        iterable &$annotations = [],
611
        iterable &$end1Annotations = [],
612
        iterable &$end2Annotations = [],
613
        iterable &$constraintAnnotations = []
614
    ) {
615
        $property->PopulateCaches();
616
        $associationAnnotations = $this->GetAnnotationValue(
617
            AssociationAnnotations::class,
618
            $property,
619
            EdmConstants::InternalUri,
620
            CsdlConstants::AssociationAnnotationsAnnotation
621
        );
622
        if ($associationAnnotations != null) {
623
            $annotations           = $associationAnnotations->Annotations ?? [];
624
            $end1Annotations       = $associationAnnotations->End1Annotations ?? [];
625
            $end2Annotations       = $associationAnnotations->End2Annotations ?? [];
626
            $constraintAnnotations = $associationAnnotations->ConstraintAnnotations ?? [];
627
        } else {
628
            $empty                 = [];
629
            $annotations           = $empty;
630
            $end1Annotations       = $empty;
631
            $end2Annotations       = $empty;
632
            $constraintAnnotations = $empty;
633
        }
634
    }
635
636
    /**
637
     * Finds a list of types that derive from the supplied type directly or indirectly, and across models.
638
     *
639
     * @param  IStructuredType $baseType the base type that derived types are being searched for
640
     * @return array           a list of types that derive from the type
641
     */
642
    public function FindAllDerivedTypes(IStructuredType $baseType): array
643
    {
644
        $result = [];
645
        if ($baseType instanceof ISchemaElement) {
646
            $this->DerivedFrom($baseType, new SplObjectStorage(), $result);
647
        }
648
649
        return $result;
650
    }
651
652
    private function DerivedFrom(IStructuredType $baseType, SplObjectStorage $visited, array &$derivedTypes): void
653
    {
654
        if (!$visited->offsetExists($this)) {
655
            $visited->offsetSet($this, true);
656
            $candidates = $this->findDirectlyDerivedTypes($baseType);
657
            if ($candidates != null && count($candidates) > 0) {
658
                foreach ($candidates as $derivedType) {
659
                    $derivedTypes[] = $derivedType;
660
                    $this->DerivedFrom($derivedType, $visited, $derivedTypes);
661
                }
662
            }
663
664
            foreach ($this->getReferencedModels() as $referenced) {
665
                $candidates = $referenced->findDirectlyDerivedTypes($baseType);
666
                if ($candidates != null && count($candidates) > 0) {
667
                    foreach ($candidates as $derivedType) {
668
                        $derivedTypes[] = $derivedType;
669
                        $this->DerivedFrom($derivedType, $visited, $derivedTypes);
670
                    }
671
                }
672
            }
673
        }
674
    }
675
676
    /**
677
     * Gets an annotatable element's vocabulary annotations that bind a particular term.
678
     *
679
     * @param IVocabularyAnnotatable $element  Element to check for annotations.
680
     * @param ITerm|string $term Term to search for. OR Name of the term to search for.
681
     * @param string|null $qualifier Qualifier to apply.
682
     * @param string|null $type Type of the annotation being returned.
683
     * @return iterable|IVocabularyAnnotation[] Annotations attached to the element by this model or by models
684
     * referenced by this model that bind the term with the given qualifier.
685
     */
686
    public function FindVocabularyAnnotations(IVocabularyAnnotatable $element, $term = null, string $qualifier = null, string $type = null): iterable
687
    {
688
        assert($term instanceof ITerm || is_string($term), '$term should be a string or instanceof iTerm');
689
        if (null === $term) {
0 ignored issues
show
introduced by
The condition null === $term is always false.
Loading history...
690
            assert(null === $qualifier);
691
            assert(null === $type);
692
            $result = $this->FindVocabularyAnnotationsIncludingInheritedAnnotations($element);
693
            foreach ($this->getReferencedModels() as $referencedModel) {
694
                $result = array_merge(
695
                    $result,
696
                    $referencedModel->FindVocabularyAnnotationsIncludingInheritedAnnotations($element)
697
                );
698
            }
699
            return $result;
700
        }
701
        if (is_string($term)) {
702
            $termName = $term;
703
            // Look up annotations on the element by name. There's no particular advantage in searching for a term first.
704
            $name = null;
705
            $namespaceName = null;
706
707
            if (EdmUtil::TryGetNamespaceNameFromQualifiedName($termName, $namespaceName, $name)) {
708
                /**
709
                 * @var IVocabularyAnnotation $annotation
710
                 */
711
                foreach ($this->FindVocabularyAnnotations($element) as $annotation) {
712
                    if (null !== $type && !is_a($annotation, $type)) {
713
                        continue;
714
                    }
715
                    $annotationTerm = $annotation->getTerm();
716
                    if ($annotationTerm->getNamespace() === $namespaceName &&
717
                        $annotationTerm->getName() === $name &&
718
                        (
719
                            null === $qualifier ||
720
                            $qualifier == $annotation->getQualifier()
721
                        )
722
                    ) {
723
                        yield $annotation;
0 ignored issues
show
Bug Best Practice introduced by
The expression yield $annotation returns the type Generator which is incompatible with the documented return type AlgoWeb\ODataMetadata\In...ryAnnotation[]|iterable.
Loading history...
724
                    }
725
                }
726
            }
727
        } else {
728
            assert($term instanceof ITerm);
729
730
            $result = [];
731
            /**
732
             * @var IVocabularyAnnotation $annotation
733
             */
734
            foreach ($this->FindVocabularyAnnotations($element) as $annotation) {
735
                if (null !== $type && !is_a($annotation, $type)) {
736
                    continue;
737
                }
738
739
                if ($annotation->getTerm() == $term &&
740
                    (
741
                        null === $qualifier ||
742
                        $qualifier == $annotation->getQualifier()
743
                    )
744
                ) {
745
                    if (null === $result) {
746
                        $result = [];
747
                    }
748
749
                    $result[] = $annotation;
750
                }
751
            }
752
753
            return $result;
754
        }
755
    }
756
757
    /**
758
     * Gets an annotatable element's vocabulary annotations defined in a specific model and models referenced by
759
     * that model.
760
     * @param IVocabularyAnnotatable $element Element to check for annotations.
761
     * @return IVocabularyAnnotation[] Annotations attached to the element (or, if the element is a type, to its base
762
     * types) by this model or by models referenced by this model.
763
     */
764
    public function FindVocabularyAnnotationsIncludingInheritedAnnotations(IVocabularyAnnotatable $element): array
765
    {
766
        /**
767
         * @var IVocabularyAnnotation[] $result
768
         */
769
        $result = $this->FindDeclaredVocabularyAnnotations($element);
0 ignored issues
show
Bug introduced by
It seems like FindDeclaredVocabularyAnnotations() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

769
        /** @scrutinizer ignore-call */ 
770
        $result = $this->FindDeclaredVocabularyAnnotations($element);
Loading history...
770
771
        if ($element instanceof IStructuredType) {
772
            $typeElement = $element;
773
            assert($typeElement instanceof IStructuredType);
774
            $typeElement = $typeElement->getBaseType();
775
            while (null !== $typeElement) {
776
                if ($typeElement instanceof IVocabularyAnnotatable) {
777
                    $result = array_merge($result, $this->FindDeclaredVocabularyAnnotations($typeElement));
778
                }
779
780
                $typeElement = $typeElement->getBaseType();
781
            }
782
        }
783
784
        return $result;
785
    }
786
787
    /**
788
     * Finds a list of types that derive directly from the supplied type.
789
     *
790
     * @param  IStructuredType   $baseType the base type that derived types are being searched for
791
     * @return IStructuredType[] a list of types from this model that derive directly from the given type
792
     */
793
    abstract public function findDirectlyDerivedTypes(IStructuredType $baseType): array;
794
}
795