Issues (126)

src/Helpers/ModelHelpers.php (2 issues)

Labels
Severity
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
    use ModelHelpersVocabularyAnnotation;
39
40
    public function getNamespaceAliases(): array
41
    {
42
        /** @var IModel $this */
43
        /** @var array|null $result */
44
        $result = $this->getAnnotationValue(
45
            'array',
46
            $this,
0 ignored issues
show
$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

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

334
            /** @scrutinizer ignore-type */ $this,
Loading history...
335
            EdmConstants::InternalUri,
336
            CsdlConstants::NamespacePrefixAnnotation
337
        );
338
339
        return $result ?? [];
340
    }
341
342
    /**
343
     * Sets the name used for the association end serialized for a navigation property.
344
     *
345
     * @param INavigationProperty $property    the navigation property
346
     * @param string              $association the association end name
347
     */
348
    public function setAssociationEndName(INavigationProperty $property, string $association): void
349
    {
350
        $this->setAnnotationValue(
351
            $property,
352
            EdmConstants::InternalUri,
353
            CsdlConstants::AssociationEndNameAnnotation,
354
            $association
355
        );
356
    }
357
358
    /**
359
     * Gets the name used for the association end serialized for a navigation property.
360
     *
361
     * @param  INavigationProperty $property the navigation property
362
     * @return string              the association end name
363
     */
364
    public function getAssociationEndName(INavigationProperty $property): string
365
    {
366
        $property->populateCaches();
367
        /** @var string|null $result */
368
        $result = $this->getAnnotationValue(
369
            'string',
370
            $property,
371
            EdmConstants::InternalUri,
372
            CsdlConstants::AssociationEndNameAnnotation
373
        );
374
375
        return $result ?? ($property->getPartner()->getName() ?? '');
376
    }
377
378
    /**
379
     * Gets the fully-qualified name used for the association serialized for a navigation property.
380
     *
381
     * @param  INavigationProperty $property the navigation property
382
     * @return string              the fully-qualified association name
383
     */
384
    public function getAssociationFullName(INavigationProperty $property): string
385
    {
386
        $property->populateCaches();
387
        return $this->getAssociationNamespace($property) . '.' . $this->getAssociationName($property);
388
    }
389
390
    /**
391
     * Gets the namespace used for the association serialized for a navigation property.
392
     *
393
     * @param  INavigationProperty $property the navigation property
394
     * @return string              the association namespace
395
     */
396
    public function getAssociationNamespace(INavigationProperty $property): string
397
    {
398
        $model = $this;
399
        assert($model instanceof IModel);
400
401
        $property->populateCaches();
402
        $associationNamespace = $model->getAnnotationValue(
403
            '?string',
404
            $property,
405
            EdmConstants::InternalUri,
406
            CsdlConstants::AssociationNamespaceAnnotation
407
        );
408
        if ($associationNamespace == null) {
409
            $associationNamespace = $property->getPrimary()->declaringEntityType()->getNamespace();
410
        }
411
412
        return $associationNamespace;
413
    }
414
415
    /**
416
     * Gets the name used for the association serialized for a navigation property.
417
     *
418
     * @param  INavigationProperty $property the navigation property
419
     * @return string              the association name
420
     */
421
    public function getAssociationName(INavigationProperty $property): string
422
    {
423
        $model = $this;
424
        assert($model instanceof IModel);
425
426
        $property->populateCaches();
427
        $associationName = $model->getAnnotationValue(
428
            '?string',
429
            $property,
430
            EdmConstants::InternalUri,
431
            CsdlConstants::AssociationNameAnnotation
432
        );
433
        if ($associationName == null) {
434
            $fromPrincipal = $property->getPrimary();
435
            $toPrincipal   = $fromPrincipal->getPartner();
436
437
            $associationName =
438
                Helpers::getQualifiedAndEscapedPropertyName($toPrincipal) .
439
                Helpers::AssociationNameEscapeChar .
440
                Helpers::getQualifiedAndEscapedPropertyName($fromPrincipal);
441
        }
442
443
        return $associationName;
444
    }
445
446
    public function findType(string $qualifiedName): ?ISchemaType
447
    {
448
        $findTypeMethod = self::findTypec();
449
        /** @var IModel $this */
450
        return Helpers::findAcrossModels(
451
            $this,
452
            $qualifiedName,
453
            $findTypeMethod,
454
            [RegistrationHelper::class, 'CreateAmbiguousTypeBinding']
455
        );
456
    }
457
458
    private static function findTypec(): callable
459
    {
460
        return function (IModel $model, string $qualifiedName): ?ISchemaType {
461
            return $model->findDeclaredType($qualifiedName);
462
        };
463
    }
464
465
    /**
466
     * Sets the name used for the association serialized for a navigation property.
467
     *
468
     * @param INavigationProperty $property        the navigation property
469
     * @param string              $associationName the association name
470
     */
471
    public function setAssociationName(INavigationProperty $property, string $associationName): void
472
    {
473
        $model = $this;
474
        assert($model instanceof IModel);
475
476
        $model->setAnnotationValue(
477
            $property,
478
            EdmConstants::InternalUri,
479
            CsdlConstants::AssociationNameAnnotation,
480
            $associationName
481
        );
482
    }
483
484
    /**
485
     * Sets the namespace used for the association serialized for a navigation property.
486
     *
487
     * @param INavigationProperty $property             the navigation property
488
     * @param string              $associationNamespace the association namespace
489
     */
490
    public function setAssociationNamespace(INavigationProperty $property, string $associationNamespace): void
491
    {
492
        $model = $this;
493
        assert($model instanceof IModel);
494
495
        $model->setAnnotationValue(
496
            $property,
497
            EdmConstants::InternalUri,
498
            CsdlConstants::AssociationNamespaceAnnotation,
499
            $associationNamespace
500
        );
501
    }
502
503
    /**
504
     * Sets the name used for the association set serialized for a navigation property of an entity set.
505
     *
506
     * @param IEntitySet          $entitySet      The entity set
507
     * @param INavigationProperty $property       the navigation property
508
     * @param string              $associationSet the association set name
509
     */
510
    public function setAssociationSetName(
511
        IEntitySet $entitySet,
512
        INavigationProperty $property,
513
        string $associationSet
514
    ): void {
515
        $model = $this;
516
        assert($model instanceof IModel);
517
        $navigationPropertyMappings = $model->getAnnotationValue(
518
            'SplObjectStorage',
519
            $entitySet,
520
            EdmConstants::InternalUri,
521
            CsdlConstants::AssociationSetNameAnnotation
522
        );
523
        if ($navigationPropertyMappings == null) {
524
            $navigationPropertyMappings = new SplObjectStorage();
525
            $model->setAnnotationValue(
526
                $entitySet,
527
                EdmConstants::InternalUri,
528
                CsdlConstants::AssociationSetNameAnnotation,
529
                $navigationPropertyMappings
530
            );
531
        }
532
533
        $navigationPropertyMappings->offsetSet($property, $associationSet);
534
    }
535
536
    /**
537
     * Gets the name used for the association set serialized for a navigation property of an entity set.
538
     *
539
     * @param  IEntitySet          $entitySet the entity set
540
     * @param  INavigationProperty $property  the navigation property
541
     * @return string              the association set name
542
     */
543
    public function getAssociationSetName(IEntitySet $entitySet, INavigationProperty $property): string
544
    {
545
        $model = $this;
546
        assert($model instanceof IModel);
547
548
        $navigationPropertyMappings = $model->getAnnotationValue(
549
            SplObjectStorage::class,
550
            $entitySet,
551
            EdmConstants::InternalUri,
552
            CsdlConstants::AssociationSetNameAnnotation
553
        );
554
        assert($navigationPropertyMappings instanceof SplObjectStorage || $navigationPropertyMappings === null);
555
        if ($navigationPropertyMappings !== null && $navigationPropertyMappings->offsetExists($property)) {
556
            $associationSetName = $navigationPropertyMappings->offsetGet($property);
557
        } else {
558
            $associationSetName = $model->getAssociationName($property) . 'Set';
559
        }
560
561
        return $associationSetName;
562
    }
563
564
    /**
565
     * Gets the annotations associated with the association serialized for a navigation target of an entity set.
566
     *
567
     * @param IEntitySet          $entitySet       the entity set
568
     * @param INavigationProperty $property        the navigation property
569
     * @param iterable            $annotations     the association set annotations
570
     * @param iterable            $end1Annotations the annotations for association set end 1
571
     * @param iterable            $end2Annotations the annotations for association set end 2
572
     */
573
    public function getAssociationSetAnnotations(
574
        IEntitySet $entitySet,
575
        INavigationProperty $property,
576
        iterable &$annotations = [],
577
        iterable &$end1Annotations = [],
578
        iterable &$end2Annotations = []
579
    ): void {
580
        /** @var SplObjectStorage $navigationPropertyMappings */
581
        $navigationPropertyMappings = $this->getAnnotationValue(
582
            SplObjectStorage::class,
583
            $entitySet,
584
            EdmConstants::InternalUri,
585
            CsdlConstants::AssociationSetAnnotationsAnnotation
586
        );
587
        if ($navigationPropertyMappings != null && $navigationPropertyMappings->offsetExists($property)) {
588
            /** @var AssociationSetAnnotations $associationSetAnnotations */
589
            $associationSetAnnotations = $navigationPropertyMappings[$property];
590
            $annotations               = $associationSetAnnotations->Annotations ?? [];
591
            $end1Annotations           = $associationSetAnnotations->End1Annotations ?? [];
592
            $end2Annotations           = $associationSetAnnotations->End2Annotations ?? [];
593
        } else {
594
            $empty           = [];
595
            $annotations     = $empty;
596
            $end1Annotations = $empty;
597
            $end2Annotations = $empty;
598
        }
599
    }
600
601
    /**
602
     * Gets the annotations associated with the association serialized for a navigation property.
603
     *
604
     * @param INavigationProperty $property              the navigation property
605
     * @param iterable            $annotations           the association annotations
606
     * @param iterable            $end1Annotations       the annotations for association end 1
607
     * @param iterable            $end2Annotations       the annotations for association end 2
608
     * @param iterable            $constraintAnnotations the annotations for the referential constraint
609
     */
610
    public function getAssociationAnnotations(
611
        INavigationProperty $property,
612
        iterable &$annotations = [],
613
        iterable &$end1Annotations = [],
614
        iterable &$end2Annotations = [],
615
        iterable &$constraintAnnotations = []
616
    ) {
617
        $property->populateCaches();
618
        $associationAnnotations = $this->getAnnotationValue(
619
            AssociationAnnotations::class,
620
            $property,
621
            EdmConstants::InternalUri,
622
            CsdlConstants::AssociationAnnotationsAnnotation
623
        );
624
        if ($associationAnnotations != null) {
625
            $annotations           = $associationAnnotations->Annotations ?? [];
626
            $end1Annotations       = $associationAnnotations->End1Annotations ?? [];
627
            $end2Annotations       = $associationAnnotations->End2Annotations ?? [];
628
            $constraintAnnotations = $associationAnnotations->ConstraintAnnotations ?? [];
629
        } else {
630
            $empty                 = [];
631
            $annotations           = $empty;
632
            $end1Annotations       = $empty;
633
            $end2Annotations       = $empty;
634
            $constraintAnnotations = $empty;
635
        }
636
    }
637
638
    /**
639
     * Finds a list of types that derive from the supplied type directly or indirectly, and across models.
640
     *
641
     * @param  IStructuredType $baseType the base type that derived types are being searched for
642
     * @return array           a list of types that derive from the type
643
     */
644
    public function findAllDerivedTypes(IStructuredType $baseType): array
645
    {
646
        $result = [];
647
        if ($baseType instanceof ISchemaElement) {
648
            $this->derivedFrom($baseType, new SplObjectStorage(), $result);
649
        }
650
651
        return $result;
652
    }
653
654
    private function derivedFrom(IStructuredType $baseType, SplObjectStorage $visited, array &$derivedTypes): void
655
    {
656
        if (!$visited->offsetExists($this)) {
657
            $visited->offsetSet($this, true);
658
            $candidates = $this->findDirectlyDerivedTypes($baseType);
659
            if ($candidates != null && count($candidates) > 0) {
660
                foreach ($candidates as $derivedType) {
661
                    $derivedTypes[] = $derivedType;
662
                    $this->derivedFrom($derivedType, $visited, $derivedTypes);
663
                }
664
            }
665
666
            foreach ($this->getReferencedModels() as $referenced) {
667
                $candidates = $referenced->findDirectlyDerivedTypes($baseType);
668
                if ($candidates != null && count($candidates) > 0) {
669
                    foreach ($candidates as $derivedType) {
670
                        $derivedTypes[] = $derivedType;
671
                        $this->derivedFrom($derivedType, $visited, $derivedTypes);
672
                    }
673
                }
674
            }
675
        }
676
    }
677
678
    /**
679
     * Finds a list of types that derive directly from the supplied type.
680
     *
681
     * @param  IStructuredType   $baseType the base type that derived types are being searched for
682
     * @return IStructuredType[] a list of types from this model that derive directly from the given type
683
     */
684
    abstract public function findDirectlyDerivedTypes(IStructuredType $baseType): array;
685
}
686