EdmModelCsdlSerializationVisitor::visitEdmSchema()   B
last analyzed

Complexity

Conditions 11
Paths 120

Size

Total Lines 53
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 34
nc 120
nop 2
dl 0
loc 53
rs 7.15
c 0
b 0
f 0

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\Csdl\Internal\Serialization;
6
7
use AlgoWeb\ODataMetadata\CsdlConstants;
8
use AlgoWeb\ODataMetadata\EdmConstants;
9
use AlgoWeb\ODataMetadata\EdmModelVisitor;
10
use AlgoWeb\ODataMetadata\EdmUtil;
11
use AlgoWeb\ODataMetadata\Enums\ExpressionKind;
12
use AlgoWeb\ODataMetadata\Enums\OnDeleteAction;
13
use AlgoWeb\ODataMetadata\Enums\TermKind;
14
use AlgoWeb\ODataMetadata\Exception\InvalidOperationException;
15
use AlgoWeb\ODataMetadata\Exception\NotSupportedException;
16
use AlgoWeb\ODataMetadata\Interfaces\Annotations\IDirectValueAnnotation;
17
use AlgoWeb\ODataMetadata\Interfaces\Annotations\IPropertyValueBinding;
18
use AlgoWeb\ODataMetadata\Interfaces\Annotations\ITypeAnnotation;
19
use AlgoWeb\ODataMetadata\Interfaces\Annotations\IValueAnnotation;
20
use AlgoWeb\ODataMetadata\Interfaces\Annotations\IVocabularyAnnotation;
21
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IApplyExpression;
22
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IAssertTypeExpression;
23
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IBinaryConstantExpression;
24
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IBooleanConstantExpression;
25
use AlgoWeb\ODataMetadata\Interfaces\Expressions\ICollectionExpression;
26
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IDateTimeConstantExpression;
27
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IDateTimeOffsetConstantExpression;
28
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IDecimalConstantExpression;
29
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IEntitySetReferenceExpression;
30
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IEnumMemberReferenceExpression;
31
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IExpression;
32
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IFloatingConstantExpression;
33
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IFunctionReferenceExpression;
34
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IGuidConstantExpression;
35
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IIfExpression;
36
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IIntegerConstantExpression;
37
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IIsTypeExpression;
38
use AlgoWeb\ODataMetadata\Interfaces\Expressions\ILabeledExpression;
39
use AlgoWeb\ODataMetadata\Interfaces\Expressions\INullExpression;
40
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IParameterReferenceExpression;
41
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IPathExpression;
42
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IPropertyReferenceExpression;
43
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IRecordExpression;
44
use AlgoWeb\ODataMetadata\Interfaces\Expressions\IStringConstantExpression;
45
use AlgoWeb\ODataMetadata\Interfaces\Expressions\RecordExpression\IPropertyConstructor;
46
use AlgoWeb\ODataMetadata\Interfaces\IBinaryTypeReference;
47
use AlgoWeb\ODataMetadata\Interfaces\ICollectionType;
48
use AlgoWeb\ODataMetadata\Interfaces\IComplexType;
49
use AlgoWeb\ODataMetadata\Interfaces\IDecimalTypeReference;
50
use AlgoWeb\ODataMetadata\Interfaces\IDocumentation;
51
use AlgoWeb\ODataMetadata\Interfaces\IEdmElement;
52
use AlgoWeb\ODataMetadata\Interfaces\IEntityContainer;
53
use AlgoWeb\ODataMetadata\Interfaces\IEntitySet;
54
use AlgoWeb\ODataMetadata\Interfaces\IEntityType;
55
use AlgoWeb\ODataMetadata\Interfaces\IEnumMember;
56
use AlgoWeb\ODataMetadata\Interfaces\IEnumType;
57
use AlgoWeb\ODataMetadata\Interfaces\IFunction;
58
use AlgoWeb\ODataMetadata\Interfaces\IFunctionImport;
59
use AlgoWeb\ODataMetadata\Interfaces\IFunctionParameter;
60
use AlgoWeb\ODataMetadata\Interfaces\IModel;
61
use AlgoWeb\ODataMetadata\Interfaces\INavigationProperty;
62
use AlgoWeb\ODataMetadata\Interfaces\IRowType;
63
use AlgoWeb\ODataMetadata\Interfaces\ISchemaElement;
64
use AlgoWeb\ODataMetadata\Interfaces\ISpatialTypeReference;
65
use AlgoWeb\ODataMetadata\Interfaces\IStringTypeReference;
66
use AlgoWeb\ODataMetadata\Interfaces\IStructuralProperty;
67
use AlgoWeb\ODataMetadata\Interfaces\ITemporalTypeReference;
68
use AlgoWeb\ODataMetadata\Interfaces\ITypeReference;
69
use AlgoWeb\ODataMetadata\Interfaces\IValueTerm;
70
use AlgoWeb\ODataMetadata\Interfaces\IVocabularyAnnotatable;
71
use AlgoWeb\ODataMetadata\Interfaces\Values\IValue;
72
use AlgoWeb\ODataMetadata\StringConst;
73
use AlgoWeb\ODataMetadata\Structure\Tuple;
74
use AlgoWeb\ODataMetadata\Version;
75
use ArrayObject;
76
use XMLWriter;
77
78
class EdmModelCsdlSerializationVisitor extends EdmModelVisitor
79
{
80
    /**
81
     * @var Version
82
     */
83
    private $edmVersion;
84
    /**
85
     * @var IEdmModelCsdlSchemaWriter
86
     */
87
    private $schemaWriter;
88
    /**
89
     * @var INavigationProperty[]
90
     */
91
    private $navigationProperties = [];
92
    /**
93
     * @var array<string, INavigationProperty[]>|array
94
     */
95
    private $associations = [];
96
    /**
97
     * @var array<string, array<IEntitySet|INavigationProperty>[]>
98
     */
99
    private $associationSets = [];
100
    /**
101
     * @var array<string, string>
102
     */
103
    private $namespaceAliasMappings = [];
104
105
    public function __construct(
106
        IModel $model,
107
        XMLWriter $xmlWriter,
108
        Version $edmVersion,
109
        IEdmModelCsdlSchemaWriter $schemaWriter = null
110
    ) {
111
        parent::__construct($model);
112
        $this->edmVersion             = $edmVersion;
113
        $this->namespaceAliasMappings = $model->getNamespaceAliases();
114
        $this->schemaWriter           = $schemaWriter ??
115
                                        new EdmModelCsdlSchemaWriter(
116
                                            $model,
117
                                            $this->namespaceAliasMappings,
118
                                            $this->edmVersion,
119
                                            $xmlWriter
120
                                        );
121
    }
122
123
    /**
124
     * @param  EdmSchema             $element
125
     * @param  array                 $mappings
126
     * @throws \ReflectionException
127
     * @throws NotSupportedException
128
     */
129
    public function visitEdmSchema(EdmSchema $element, array $mappings): void
130
    {
131
        $alias = null;
132
        if ($this->namespaceAliasMappings != null) {
133
            $alias = array_key_exists($element->getNamespace(), $this->namespaceAliasMappings) ?
134
                $this->namespaceAliasMappings[$element->getNamespace()] :
135
                null;
136
        }
137
138
        $this->schemaWriter->writeSchemaElementHeader($element, $alias, $mappings);
139
        foreach ($element->getUsedNamespaces() as $usingNamespace) {
140
            if ($usingNamespace != $element->getUsedNamespaces()) {
141
                if ($this->namespaceAliasMappings != null &&
142
                    array_key_exists($usingNamespace, $this->namespaceAliasMappings)) {
143
                    $this->schemaWriter->writeNamespaceUsingElement(
144
                        $usingNamespace,
145
                        $this->namespaceAliasMappings[$usingNamespace]
146
                    );
147
                }
148
            }
149
        }
150
151
        $this->visitSchemaElements($element->getSchemaElements());
152
        foreach ($element->getAssociationNavigationProperties() as $navigationProperty) {
153
            $associationName = $this->model->getAssociationFullName($navigationProperty);
154
            if (!array_key_exists($associationName, $this->associations)) {
155
                $handledNavigationProperties          = [];
156
                $this->associations[$associationName] = $handledNavigationProperties;
157
            }
158
            $self   = $this;
159
            $shared = array_filter(
160
                $this->associations[$associationName],
161
                function (INavigationProperty $np) use ($self, $navigationProperty) {
162
                    return $self->sharesAssociation($np, $navigationProperty);
163
                }
164
            );
165
            // This prevents us from losing associations if they share the same name.
166
            if (!count($shared) > 0) {
167
                $this->associations[$associationName][] = $navigationProperty;
168
                $this->associations[$associationName][] = $navigationProperty->getPartner();
169
                $this->processAssociation($navigationProperty);
170
            }
171
        }
172
173
        // EntityContainers are excluded from the EdmSchema.SchemaElements property so they can be forced to the end.
174
        $this->visitCollection($element->getEntityContainers(), [$this, 'processEntityContainer']);
175
        foreach ($element->getAnnotations() as $annotationsForTargetKey => $annotationsForTarget) {
176
            $this->schemaWriter->writeAnnotationsElementHeader($annotationsForTargetKey);
177
            $this->visitVocabularyAnnotations($annotationsForTarget);
178
            $this->schemaWriter->writeEndElement();
179
        }
180
181
        $this->schemaWriter->writeEndElement();
182
    }
183
184
    /**
185
     * @param  IEntityContainer      $element
186
     * @throws \ReflectionException
187
     * @throws NotSupportedException
188
     */
189
    protected function processEntityContainer(IEntityContainer $element): void
190
    {
191
        $this->beginElement($element, [$this->schemaWriter, 'writeEntityContainerElementHeader']);
192
        parent::processEntityContainer($element);
193
194
        /** @var IEntitySet $entitySet */
195
        foreach ($element->entitySets() as $entitySet) {
196
            foreach ($entitySet->getNavigationTargets() as $mapping) {
197
                $associationSetName = $this->model->getAssociationFullName($mapping->getNavigationProperty());
198
                if (!isset($this->associationSets[$associationSetName])) {
199
                    $handledAssociationSets                     = [];
200
                    $this->associationSets[$associationSetName] = $handledAssociationSets;
201
                }
202
                $self = $this;
203
                $any  = array_filter(
204
                    $this->associationSets[$associationSetName],
205
                    function (Tuple $set) use ($self, $entitySet, $mapping) {
206
                        return $self->sharesAssociationSet(
207
                            $set->getItem1(),
208
                            $set->getItem2(),
209
                            $entitySet,
210
                            $mapping->getNavigationProperty()
211
                        );
212
                    }
213
                );
214
                // This prevents us from losing association sets if they share the same name.
215
                if (!count($any) > 0) {
216
                    $this->associationSets[$associationSetName][] =
217
                        new Tuple($entitySet, $mapping->getNavigationProperty());
218
                    $this->associationSets[$associationSetName][] =
219
                        new Tuple($mapping->getTargetEntitySet(), $mapping->getNavigationProperty()->getPartner());
220
221
                    $this->processAssociationSet($entitySet, $mapping->getNavigationProperty());
222
                }
223
            }
224
        }
225
226
        $this->associationSets = [];
227
228
        $this->finishElement($element);
229
    }
230
231
    /**
232
     * @param  IEntitySet            $element
233
     * @throws NotSupportedException
234
     */
235
    protected function processEntitySet(IEntitySet $element): void
236
    {
237
        $this->beginElement($element, [$this->schemaWriter, 'writeEntitySetElementHeader']);
238
        parent::processEntitySet($element);
239
        $this->finishElement($element);
240
    }
241
242
    /**
243
     * @param  IEntityType           $element
244
     * @throws NotSupportedException
245
     * @throws \ReflectionException
246
     */
247
    protected function processEntityType(IEntityType $element): void
248
    {
249
        $this->beginElement($element, [$this->schemaWriter, 'writeEntityTypeElementHeader']);
250
        if (null !== $element->getDeclaredKey() &&
251
            count($element->getDeclaredKey()) > 0 &&
252
            null === $element->getBaseType()) {
253
            $this->visitEntityTypeDeclaredKey($element->getDeclaredKey());
254
        }
255
256
        $this->visitProperties($element->declaredStructuralProperties());
257
        $this->visitProperties($element->declaredNavigationProperties());
258
        $this->finishElement($element);
259
    }
260
261
    /**
262
     * @param  IStructuralProperty   $element
263
     * @throws NotSupportedException
264
     */
265
    protected function processStructuralProperty(IStructuralProperty $element): void
266
    {
267
        EdmUtil::checkArgumentNull($element->getType(), 'element->getType');
268
        $inlineType = self::isInlineType($element->getType());
269
        $this->beginElement($element, function (IStructuralProperty $t) use ($inlineType) {
270
            $this->schemaWriter->writeStructuralPropertyElementHeader($t, $inlineType);
271
        }, function (IStructuralProperty $e) use ($inlineType) {
272
            EdmUtil::checkArgumentNull($e->getType(), 'e->getType');
273
            $this->processFacets($e->getType(), $inlineType);
274
        });
275
        if (!$inlineType) {
276
            $this->visitTypeReference($element->getType());
277
        }
278
279
        $this->finishElement($element);
280
    }
281
282
    /**
283
     * @param  IBinaryTypeReference $element
284
     * @throws \ReflectionException
285
     */
286
    protected function processBinaryTypeReference(IBinaryTypeReference $element): void
287
    {
288
        $this->schemaWriter->writeBinaryTypeAttributes($element);
289
    }
290
291
    /**
292
     * @param  IDecimalTypeReference $element
293
     * @throws \ReflectionException
294
     */
295
    protected function processDecimalTypeReference(IDecimalTypeReference $element): void
296
    {
297
        $this->schemaWriter->writeDecimalTypeAttributes($element);
298
    }
299
300
    /**
301
     * @param  ISpatialTypeReference $element
302
     * @throws \ReflectionException
303
     */
304
    protected function processSpatialTypeReference(ISpatialTypeReference $element): void
305
    {
306
        $this->schemaWriter->writeSpatialTypeAttributes($element);
307
    }
308
309
    /**
310
     * @param  IStringTypeReference $element
311
     * @throws \ReflectionException
312
     */
313
    protected function processStringTypeReference(IStringTypeReference $element): void
314
    {
315
        $this->schemaWriter->writeStringTypeAttributes($element);
316
    }
317
318
    /**
319
     * @param  ITemporalTypeReference $element
320
     * @throws \ReflectionException
321
     */
322
    protected function processTemporalTypeReference(ITemporalTypeReference $element): void
323
    {
324
        $this->schemaWriter->writeTemporalTypeAttributes($element);
325
    }
326
327
    /**
328
     * @param  INavigationProperty   $element
329
     * @throws NotSupportedException
330
     */
331
    protected function processNavigationProperty(INavigationProperty $element): void
332
    {
333
        $this->beginElement($element, [$this->schemaWriter, 'writeNavigationPropertyElementHeader']);
334
        $this->finishElement($element);
335
        $this->navigationProperties[] = $element;
336
    }
337
338
    /**
339
     * @param  IComplexType          $element
340
     * @throws NotSupportedException
341
     */
342
    protected function processComplexType(IComplexType $element): void
343
    {
344
        $this->beginElement($element, [$this->schemaWriter, 'writeComplexTypeElementHeader']);
345
        parent::processComplexType($element);
346
        $this->finishElement($element);
347
    }
348
349
    /**
350
     * @param  IEnumType             $element
351
     * @throws NotSupportedException
352
     */
353
    protected function processEnumType(IEnumType $element): void
354
    {
355
        $this->beginElement($element, [$this->schemaWriter, 'writeEnumTypeElementHeader']);
356
        parent::processEnumType($element);
357
        $this->finishElement($element);
358
    }
359
360
    /**
361
     * @param  IEnumMember           $element
362
     * @throws NotSupportedException
363
     */
364
    protected function processEnumMember(IEnumMember $element): void
365
    {
366
        $this->beginElement($element, [$this->schemaWriter, 'writeEnumMemberElementHeader']);
367
        $this->finishElement($element);
368
    }
369
370
    /**
371
     * @param  IValueTerm            $term
372
     * @throws NotSupportedException
373
     */
374
    protected function processValueTerm(IValueTerm $term): void
375
    {
376
        $inlineType = null !== $term->getType() && self::isInlineType($term->getType());
377
        $this->beginElement($term, function (IValueTerm $t) use ($inlineType) {
378
            $this->schemaWriter->writeValueTermElementHeader($t, $inlineType);
379
        }, function (IValueTerm $e) use ($inlineType) {
380
            EdmUtil::checkArgumentNull($e->getType(), 'e->getType');
381
            $this->processFacets($e->getType(), $inlineType);
382
        });
383
        if (!$inlineType) {
384
            if (null !== $term->getType()) {
385
                $this->visitTypeReference($term->getType());
386
            }
387
        }
388
389
        $this->finishElement($term);
390
    }
391
392
    /**
393
     * @param  IFunction             $element
394
     * @throws NotSupportedException
395
     */
396
    protected function processFunction(IFunction $element): void
397
    {
398
        if (null !== $element->getReturnType()) {
399
            $inlineReturnType = self::isInlineType($element->getReturnType());
400
            $this->beginElement($element, function (IFunction $f) use ($inlineReturnType) {
401
                $this->schemaWriter->writeFunctionElementHeader($f, $inlineReturnType);
402
            }, function (IFunction $f) use ($inlineReturnType) {
403
                EdmUtil::checkArgumentNull($f->getReturnType(), 'f->getReturnType');
404
                $this->processFacets($f->getReturnType(), $inlineReturnType);
405
            });
406
            if (!$inlineReturnType) {
407
                $this->schemaWriter->writeReturnTypeElementHeader();
408
                $this->visitTypeReference($element->getReturnType());
409
                $this->schemaWriter->writeEndElement();
410
            }
411
        } else {
412
            $this->beginElement($element, function (IFunction $t) {
413
                $this->schemaWriter->writeFunctionElementHeader($t, false /*Inline ReturnType*/);
414
            });
415
        }
416
417
        if (null !== $element->getDefiningExpression()) {
418
            $this->schemaWriter->writeDefiningExpressionElement($element->getDefiningExpression());
419
        }
420
421
        $this->visitFunctionParameters($element->getParameters());
422
        $this->finishElement($element);
423
    }
424
425
    /**
426
     * @param  IFunctionParameter    $element
427
     * @throws NotSupportedException
428
     */
429
    protected function processFunctionParameter(IFunctionParameter $element): void
430
    {
431
        $inlineType = self::isInlineType($element->getType());
432
        $this->beginElement(
433
            $element,
434
            function (IFunctionParameter $t) use ($inlineType) {
435
                $this->schemaWriter->writeFunctionParameterElementHeader($t, $inlineType);
436
            },
437
            function (IFunctionParameter $e) use ($inlineType) {
438
                $this->processFacets($e->getType(), $inlineType);
439
            }
440
        );
441
        if (!$inlineType) {
442
            $this->visitTypeReference($element->getType());
443
        }
444
445
        $this->finishElement($element);
446
    }
447
448
    /**
449
     * @param  ICollectionType       $element
450
     * @throws NotSupportedException
451
     */
452
    protected function processCollectionType(ICollectionType $element): void
453
    {
454
        EdmUtil::checkArgumentNull($element->getElementType(), 'element->getElementType');
455
        $inlineType = self::isInlineType($element->getElementType());
456
        $this->beginElement(
457
            $element,
458
            function (ICollectionType $t) use ($inlineType) {
459
                $this->schemaWriter->writeCollectionTypeElementHeader($t, $inlineType);
460
            },
461
            function (ICollectionType $e) use ($inlineType) {
462
                EdmUtil::checkArgumentNull($e->getElementType(), 'e->getElementType');
463
                $this->processFacets($e->getElementType(), $inlineType);
464
            }
465
        );
466
        if (!$inlineType) {
467
            $this->visitTypeReference($element->getElementType());
468
        }
469
470
        $this->finishElement($element);
471
    }
472
473
    protected function processRowType(IRowType $element): void
474
    {
475
        $this->schemaWriter->writeRowTypeElementHeader();
476
        parent::processRowType($element);
477
        $this->schemaWriter->writeEndElement();
478
    }
479
480
    /**
481
     * @param  IFunctionImport       $functionImport
482
     * @throws NotSupportedException
483
     */
484
    protected function processFunctionImport(IFunctionImport $functionImport): void
485
    {
486
        if (null !== $functionImport->getReturnType() && !self::isInlineType($functionImport->getReturnType())) {
487
            throw new InvalidOperationException(
488
                StringConst::Serializer_NonInlineFunctionImportReturnType(
489
                    $functionImport->getContainer()->fullName() . '/' . $functionImport->getName()
490
                )
491
            );
492
        }
493
494
        $this->beginElement($functionImport, [$this->schemaWriter, 'writeFunctionImportElementHeader']);
495
        $this->visitFunctionParameters($functionImport->getParameters());
496
        $this->finishElement($functionImport);
497
    }
498
499
    #region Vocabulary Annotations
500
501
    /**
502
     * @param  IValueAnnotation      $annotation
503
     * @throws NotSupportedException
504
     */
505
    protected function processValueAnnotation(IValueAnnotation $annotation): void
506
    {
507
        $isInline = self::isInlineExpression($annotation->getValue());
508
        $this->beginElement($annotation, function ($t) use ($isInline) {
509
            $this->schemaWriter->writeValueAnnotationElementHeader($t, $isInline);
510
        });
511
        if (!$isInline) {
512
            parent::processValueAnnotation($annotation);
513
        }
514
515
        $this->finishElement($annotation);
516
    }
517
518
    /**
519
     * @param  ITypeAnnotation       $annotation
520
     * @throws NotSupportedException
521
     */
522
    protected function processTypeAnnotation(ITypeAnnotation $annotation): void
523
    {
524
        $this->beginElement($annotation, [$this->schemaWriter, 'writeTypeAnnotationElementHeader']);
525
        parent::processTypeAnnotation($annotation);
526
        $this->finishElement($annotation);
527
    }
528
529
    /**
530
     * @param  IPropertyValueBinding $binding
531
     * @throws NotSupportedException
532
     */
533
    protected function processPropertyValueBinding(IPropertyValueBinding $binding): void
534
    {
535
        $isInline = self::isInlineExpression($binding->getValue());
536
        $this->beginElement($binding, function ($t) use ($isInline) {
537
            $this->schemaWriter->writePropertyValueElementHeader($t, $isInline);
538
        });
539
        if (!$isInline) {
540
            parent::processPropertyValueBinding($binding);
541
        }
542
543
        $this->finishElement($binding);
544
    }
545
546
    #endregion
547
548
    #region Expressions
549
550
    protected function processStringConstantExpression(IStringConstantExpression $expression): void
551
    {
552
        $this->schemaWriter->writeStringConstantExpressionElement($expression);
553
    }
554
555
    protected function processBinaryConstantExpression(IBinaryConstantExpression $expression): void
556
    {
557
        $this->schemaWriter->writeBinaryConstantExpressionElement($expression);
558
    }
559
560
    /**
561
     * @param  IRecordExpression     $expression
562
     * @throws NotSupportedException
563
     */
564
    protected function processRecordExpression(IRecordExpression $expression): void
565
    {
566
        $this->beginElement($expression, [$this->schemaWriter, 'writeRecordExpressionElementHeader']);
567
        $this->visitPropertyConstructors($expression->getProperties());
568
        $this->finishElement($expression);
569
    }
570
571
    /**
572
     * @param  ILabeledExpression    $element
573
     * @throws NotSupportedException
574
     */
575
    protected function processLabeledExpression(ILabeledExpression $element): void
576
    {
577
        if (null === $element->getName()) {
578
            parent::processLabeledExpression($element);
579
        } else {
580
            $this->beginElement($element, [$this->schemaWriter, 'writeLabeledElementHeader']);
581
            parent::processLabeledExpression($element);
582
            $this->finishElement($element);
583
        }
584
    }
585
586
    /**
587
     * @param  IPropertyConstructor  $constructor
588
     * @throws NotSupportedException
589
     */
590
    protected function processPropertyConstructor(IPropertyConstructor $constructor): void
591
    {
592
        EdmUtil::checkArgumentNull($constructor->getValue(), 'constructor->getValue');
593
        $isInline = self::isInlineExpression($constructor->getValue());
594
        $this->beginElement($constructor, function ($t) use ($isInline) {
595
            $this->schemaWriter->writePropertyConstructorElementHeader($t, $isInline);
596
        });
597
        if (!$isInline) {
598
            parent::processPropertyConstructor($constructor);
599
        }
600
601
        $this->finishElement($constructor);
602
    }
603
604
    /**
605
     * @param  IPropertyReferenceExpression $expression
606
     * @throws NotSupportedException
607
     */
608
    protected function processPropertyReferenceExpression(IPropertyReferenceExpression $expression): void
609
    {
610
        $this->beginElement($expression, [$this->schemaWriter, 'writePropertyReferenceExpressionElementHeader']);
611
        if ($expression->getBase()!= null) {
612
            $this->visitExpression($expression->getBase());
613
        }
614
615
        $this->finishElement($expression);
616
    }
617
618
    /**
619
     * @param IPathExpression $expression
620
     */
621
    protected function processPathExpression(IPathExpression $expression): void
622
    {
623
        $this->schemaWriter->writePathExpressionElement($expression);
624
    }
625
626
    /**
627
     * @param  IParameterReferenceExpression $expression
628
     * @throws \ReflectionException
629
     */
630
    protected function processParameterReferenceExpression(IParameterReferenceExpression $expression): void
631
    {
632
        $this->schemaWriter->writeParameterReferenceExpressionElement($expression);
633
    }
634
635
    /**
636
     * @param  ICollectionExpression $expression
637
     * @throws NotSupportedException
638
     */
639
    protected function processCollectionExpression(ICollectionExpression $expression): void
640
    {
641
        $this->beginElement($expression, [$this->schemaWriter, 'writeCollectionExpressionElementHeader']);
642
        $this->visitExpressions($expression->getElements());
643
        $this->finishElement($expression);
644
    }
645
646
    /**
647
     * @param  IIsTypeExpression     $expression
648
     * @throws NotSupportedException
649
     */
650
    protected function processIsTypeExpression(IIsTypeExpression $expression): void
651
    {
652
        $inlineType = self::isInlineType($expression->getType());
653
        $this->beginElement($expression, function (IIsTypeExpression $t) use ($inlineType) {
654
            $this->schemaWriter->writeIsTypeExpressionElementHeader($t, $inlineType);
655
        }, function (IIsTypeExpression $e) use ($inlineType) {
656
            $this->processFacets($e->getType(), $inlineType);
657
        });
658
        if (!$inlineType) {
659
            $this->visitTypeReference($expression->getType());
660
        }
661
662
        $this->visitExpression($expression->getOperand());
663
        $this->finishElement($expression);
664
    }
665
666
    protected function processIntegerConstantExpression(IIntegerConstantExpression $expression): void
667
    {
668
        $this->schemaWriter->writeIntegerConstantExpressionElement($expression);
669
    }
670
671
    /**
672
     * @param  IIfExpression         $expression
673
     * @throws NotSupportedException
674
     */
675
    protected function processIfExpression(IIfExpression $expression): void
676
    {
677
        $this->beginElement($expression, [$this->schemaWriter, 'writeIfExpressionElementHeader']);
678
        parent::processIfExpression($expression);
679
        $this->finishElement($expression);
680
    }
681
682
    /**
683
     * @param  IFunctionReferenceExpression $expression
684
     * @throws \ReflectionException
685
     */
686
    protected function processFunctionReferenceExpression(IFunctionReferenceExpression $expression): void
687
    {
688
        $this->schemaWriter->writeFunctionReferenceExpressionElement($expression);
689
    }
690
691
    /**
692
     * @param  IApplyExpression      $expression
693
     * @throws NotSupportedException
694
     */
695
    protected function processFunctionApplicationExpression(IApplyExpression $expression): void
696
    {
697
        $isFunction = $expression->getAppliedFunction()->getExpressionKind() == ExpressionKind::FunctionReference();
698
        $this->beginElement($expression, function ($e) use ($isFunction) {
699
            $this->schemaWriter->writeFunctionApplicationElementHeader($e, $isFunction);
700
        });
701
        if (!$isFunction) {
702
            EdmUtil::checkArgumentNull($expression->getAppliedFunction(), 'expression->getAppliedFunction');
703
            $this->visitExpression($expression->getAppliedFunction());
704
        }
705
706
        $this->visitExpressions($expression->getArguments());
707
        $this->finishElement($expression);
708
    }
709
710
    protected function processFloatingConstantExpression(IFloatingConstantExpression $expression): void
711
    {
712
        $this->schemaWriter->writeFloatingConstantExpressionElement($expression);
713
    }
714
715
    protected function processGuidConstantExpression(IGuidConstantExpression $expression): void
716
    {
717
        $this->schemaWriter->writeGuidConstantExpressionElement($expression);
718
    }
719
720
    /**
721
     * @param  IEnumMemberReferenceExpression $expression
722
     * @throws \ReflectionException
723
     */
724
    protected function processEnumMemberReferenceExpression(IEnumMemberReferenceExpression $expression): void
725
    {
726
        $this->schemaWriter->writeEnumMemberReferenceExpressionElement($expression);
727
    }
728
729
    /**
730
     * @param  IEntitySetReferenceExpression $expression
731
     * @throws \ReflectionException
732
     */
733
    protected function processEntitySetReferenceExpression(IEntitySetReferenceExpression $expression): void
734
    {
735
        $this->schemaWriter->writeEntitySetReferenceExpressionElement($expression);
736
    }
737
738
    protected function processDecimalConstantExpression(IDecimalConstantExpression $expression): void
739
    {
740
        $this->schemaWriter->writeDecimalConstantExpressionElement($expression);
741
    }
742
743
    protected function processDateTimeConstantExpression(IDateTimeConstantExpression $expression): void
744
    {
745
        $this->schemaWriter->writeDateTimeConstantExpressionElement($expression);
746
    }
747
748
    protected function processDateTimeOffsetConstantExpression(IDateTimeOffsetConstantExpression $expression): void
749
    {
750
        $this->schemaWriter->writeDateTimeOffsetConstantExpressionElement($expression);
751
    }
752
753
    protected function processBooleanConstantExpression(IBooleanConstantExpression $expression): void
754
    {
755
        $this->schemaWriter->writeBooleanConstantExpressionElement($expression);
756
    }
757
758
    protected function processNullConstantExpression(INullExpression $expression): void
759
    {
760
        $this->schemaWriter->writeNullConstantExpressionElement($expression);
761
    }
762
763
    /**
764
     * @param  IAssertTypeExpression $expression
765
     * @throws NotSupportedException
766
     */
767
    protected function processAssertTypeExpression(IAssertTypeExpression $expression): void
768
    {
769
        $inlineType = self::isInlineType($expression->getType());
770
        $this->beginElement($expression, function (IAssertTypeExpression $t) use ($inlineType) {
771
            $this->schemaWriter->writeAssertTypeExpressionElementHeader($t, $inlineType);
772
        }, function (IAssertTypeExpression $e) use ($inlineType) {
773
            $this->processFacets($e->getType(), $inlineType);
774
        });
775
        if (!$inlineType) {
776
            $this->visitTypeReference($expression->getType());
777
        }
778
779
        $this->visitExpression($expression->getOperand());
780
        $this->finishElement($expression);
781
    }
782
783
    #endregion
784
785
    private static function isInlineType(ITypeReference $reference): bool
786
    {
787
        if ($reference->getDefinition() instanceof ISchemaElement || $reference->isEntityReference()) {
788
            return true;
789
        } elseif ($reference->isCollection()) {
790
            $def = $reference->asCollection()->collectionDefinition()->getElementType()->getDefinition();
791
            return $def instanceof ISchemaElement;
792
        }
793
794
        return false;
795
    }
796
797
    private static function isInlineExpression(IExpression $expression): bool
798
    {
799
        return $expression->getExpressionKind()->isAnyOf(
800
            ExpressionKind::BinaryConstant(),
801
            ExpressionKind::BooleanConstant(),
802
            ExpressionKind::DateTimeConstant(),
803
            ExpressionKind::DateTimeOffsetConstant(),
804
            ExpressionKind::DecimalConstant(),
805
            ExpressionKind::FloatingConstant(),
806
            ExpressionKind::GuidConstant(),
807
            ExpressionKind::IntegerConstant(),
808
            ExpressionKind::Path(),
809
            ExpressionKind::StringConstant(),
810
            ExpressionKind::TimeConstant()
811
        );
812
    }
813
814
    /**
815
     * @param  iterable|IDirectValueAnnotation[] $annotations
816
     * @throws NotSupportedException
817
     */
818
    private function processAnnotations(iterable $annotations): void
819
    {
820
        $this->visitAttributeAnnotations($annotations);
821
        foreach ($annotations as $annotation) {
822
            if ($annotation->getNamespaceUri() == EdmConstants::DocumentationUri &&
823
                $annotation->getName() == EdmConstants::DocumentationAnnotation) {
824
                $value = $annotation->getValue();
825
                assert($value instanceof IDocumentation);
826
                $this->processEdmDocumentation($value);
827
            }
828
        }
829
    }
830
831
    /**
832
     * @param  INavigationProperty   $element
833
     * @throws \ReflectionException
834
     * @throws NotSupportedException
835
     */
836
    private function processAssociation(INavigationProperty $element): void
837
    {
838
        $end1 = $element->getPrimary();
839
        $end2 = $end1->getPartner();
840
        /** @var IDirectValueAnnotation[] $associationAnnotations */
841
        $associationAnnotations = [];
842
        /** @var IDirectValueAnnotation[] $end1Annotations */
843
        $end1Annotations = [];
844
        /** @var IDirectValueAnnotation[] $end2Annotations */
845
        $end2Annotations = [];
846
        /** @var IDirectValueAnnotation[] $constraintAnnotations */
847
        $constraintAnnotations = [];
848
        $this->model->getAssociationAnnotations(
849
            $element,
850
            $associationAnnotations,
851
            $end1Annotations,
852
            $end2Annotations,
853
            $constraintAnnotations
854
        );
855
856
        $this->schemaWriter->writeAssociationElementHeader($end1);
857
        $this->processAnnotations($associationAnnotations);
858
859
        $this->processAssociationEnd($end1, $end1 === $element ? $end1Annotations : $end2Annotations);
860
        $this->processAssociationEnd($end2, $end1 === $element ? $end2Annotations : $end1Annotations);
861
        $this->processReferentialConstraint($end1, $constraintAnnotations);
862
863
        $this->visitPrimitiveElementAnnotations($associationAnnotations);
864
        $this->schemaWriter->writeEndElement();
865
    }
866
867
    /**
868
     * @param  INavigationProperty               $element
869
     * @param  iterable|IDirectValueAnnotation[] $annotations
870
     * @throws \ReflectionException
871
     * @throws NotSupportedException
872
     */
873
    private function processAssociationEnd(INavigationProperty $element, iterable $annotations): void
874
    {
875
        $this->schemaWriter->writeAssociationEndElementHeader($element);
876
        $this->processAnnotations($annotations);
877
878
        if ($element->getOnDelete() != OnDeleteAction::None()) {
879
            $this->schemaWriter->writeOperationActionElement(CsdlConstants::Element_OnDelete, $element->getOnDelete());
880
        }
881
882
        $this->visitPrimitiveElementAnnotations($annotations);
883
        $this->schemaWriter->writeEndElement();
884
    }
885
886
    /**
887
     * @param  INavigationProperty               $element
888
     * @param  iterable|IDirectValueAnnotation[] $annotations
889
     * @throws \ReflectionException
890
     * @throws NotSupportedException
891
     */
892
    private function processReferentialConstraint(INavigationProperty $element, iterable $annotations): void
893
    {
894
        if ($element->getDependentProperties() !== null) {
895
            $principalElement = $element->getPartner();
896
        } elseif ($element->getPartner()->getDependentProperties() !== null) {
897
            $principalElement = $element;
898
        } else {
899
            return;
900
        }
901
902
        $this->schemaWriter->writeReferentialConstraintElementHeader($principalElement);
903
        $this->processAnnotations($annotations);
904
        $this->schemaWriter->writeReferentialConstraintPrincipalEndElementHeader($principalElement);
905
        $dType = $principalElement->getDeclaringType();
906
        assert($dType instanceof IEntityType);
907
        EdmUtil::checkArgumentNull($dType->key(), 'principalElement->getDeclaringType->Key');
908
        $this->visitPropertyRefs($dType->key());
909
        $this->schemaWriter->writeEndElement();
910
        $this->schemaWriter->writeReferentialConstraintDependentEndElementHeader($principalElement->getPartner());
911
        EdmUtil::checkArgumentNull(
912
            $principalElement->getPartner()->getDependentProperties(),
913
            'principalElement->getPartner->getDependentProperties'
914
        );
915
        $this->visitPropertyRefs($principalElement->getPartner()->getDependentProperties());
916
        $this->schemaWriter->writeEndElement();
917
        $this->visitPrimitiveElementAnnotations($annotations);
918
        $this->schemaWriter->writeEndElement();
919
    }
920
921
    /**
922
     * @param  IEntitySet            $entitySet
923
     * @param  INavigationProperty   $property
924
     * @throws \ReflectionException
925
     * @throws NotSupportedException
926
     */
927
    private function processAssociationSet(IEntitySet $entitySet, INavigationProperty $property): void
928
    {
929
        /** @var IDirectValueAnnotation[] $associationSetAnnotations */
930
        $associationSetAnnotations = [];
931
        /** @var IDirectValueAnnotation[] $end1Annotations */
932
        $end1Annotations = [];
933
        /** @var IDirectValueAnnotation[] $end2Annotations */
934
        $end2Annotations = [];
935
        $this->model->getAssociationSetAnnotations(
936
            $entitySet,
937
            $property,
938
            $associationSetAnnotations,
939
            $end1Annotations,
940
            $end2Annotations
941
        );
942
943
        $this->schemaWriter->writeAssociationSetElementHeader($entitySet, $property);
944
        $this->processAnnotations($associationSetAnnotations);
945
946
        $this->processAssociationSetEnd($entitySet, $property, $end1Annotations);
947
948
        $otherEntitySet = $entitySet->findNavigationTarget($property);
949
        if ($otherEntitySet != null) {
950
            $this->processAssociationSetEnd($otherEntitySet, $property->getPartner(), $end2Annotations);
951
        }
952
953
        $this->visitPrimitiveElementAnnotations($associationSetAnnotations);
954
        $this->schemaWriter->writeEndElement();
955
    }
956
957
    /**
958
     * @param  IEntitySet                        $entitySet
959
     * @param  INavigationProperty               $property
960
     * @param  iterable|IDirectValueAnnotation[] $annotations
961
     * @throws \ReflectionException
962
     * @throws NotSupportedException
963
     */
964
    private function processAssociationSetEnd(
965
        IEntitySet $entitySet,
966
        INavigationProperty $property,
967
        iterable $annotations
968
    ): void {
969
        $this->schemaWriter->writeAssociationSetEndElementHeader($entitySet, $property);
970
        $this->processAnnotations($annotations);
971
        $this->visitPrimitiveElementAnnotations($annotations);
972
        $this->schemaWriter->writeEndElement();
973
    }
974
975
    /**
976
     * @param  ITypeReference       $element
977
     * @param  bool                 $inlineType
978
     * @throws \ReflectionException
979
     */
980
    private function processFacets(ITypeReference $element, bool $inlineType): void
981
    {
982
        if ($element != null) {
983
            if ($element->isEntityReference()) {
984
                // No facets get serialized for an entity reference.
985
                return;
986
            }
987
988
            if ($inlineType) {
989
                if ($element->typeKind()->isCollection()) {
990
                    $collectionElement = $element->asCollection();
991
                    $type              = $collectionElement->collectionDefinition()->getElementType();
992
                    EdmUtil::checkArgumentNull($type, 'ProcessFacets - $type');
993
                    $this->schemaWriter->writeNullableAttribute($type);
994
                    $this->visitTypeReference($type);
995
                } else {
996
                    $this->schemaWriter->writeNullableAttribute($element);
997
                    $this->visitTypeReference($element);
998
                }
999
            }
1000
        }
1001
    }
1002
1003
    /**
1004
     * @param  iterable|IStructuralProperty[] $keyProperties
1005
     * @throws \ReflectionException
1006
     */
1007
    private function visitEntityTypeDeclaredKey(iterable $keyProperties): void
1008
    {
1009
        $this->schemaWriter->writeDeclaredKeyPropertiesElementHeader();
1010
        $this->visitPropertyRefs($keyProperties);
1011
        $this->schemaWriter->writeEndElement();
1012
    }
1013
1014
    /**
1015
     * @param  iterable|IStructuralProperty[] $properties
1016
     * @throws \ReflectionException
1017
     */
1018
    private function visitPropertyRefs(iterable $properties): void
1019
    {
1020
        foreach ($properties as $property) {
1021
            $this->schemaWriter->writePropertyRefElement($property);
1022
        }
1023
    }
1024
1025
    /**
1026
     * @param  iterable|IDirectValueAnnotation[] $annotations
1027
     * @throws NotSupportedException
1028
     */
1029
    private function visitAttributeAnnotations(iterable $annotations): void
1030
    {
1031
        foreach ($annotations as $annotation) {
1032
            if ($annotation->getNamespaceUri() != EdmConstants::InternalUri) {
1033
                $edmValue = $annotation->getValue();
1034
                if ($edmValue instanceof IValue) {
1035
                    if (!$edmValue->isSerializedAsElement($this->model)) {
1036
                        if ($edmValue->getType()->typeKind()->isPrimitive()) {
1037
                            $this->processAttributeAnnotation($annotation);
1038
                        }
1039
                    }
1040
                }
1041
            }
1042
        }
1043
    }
1044
    /**
1045
     * @param iterable|IDirectValueAnnotation[] $annotations
1046
     */
1047
    private function visitPrimitiveElementAnnotations(iterable $annotations): void
1048
    {
1049
        foreach ($annotations as $annotation) {
1050
            if ($annotation->getNamespaceUri() != EdmConstants::InternalUri) {
1051
                $edmValue = $annotation->getValue();
1052
                if ($edmValue instanceof IValue) {
1053
                    if (!$edmValue->isSerializedAsElement($this->model)) {
1054
                        if ($edmValue->getType()->typeKind()->isPrimitive()) {
1055
                            $this->processElementAnnotation($annotation);
1056
                        }
1057
                    }
1058
                }
1059
            }
1060
        }
1061
    }
1062
1063
    /**
1064
     * @param  IDirectValueAnnotation $annotation
1065
     * @throws NotSupportedException
1066
     */
1067
    private function processAttributeAnnotation(IDirectValueAnnotation $annotation): void
1068
    {
1069
        $this->schemaWriter->writeAnnotationStringAttribute($annotation);
1070
    }
1071
1072
    /**
1073
     * @param IDirectValueAnnotation $annotation
1074
     */
1075
    private function processElementAnnotation(IDirectValueAnnotation $annotation): void
1076
    {
1077
        $this->schemaWriter->writeAnnotationStringElement($annotation);
1078
    }
1079
1080
    /**
1081
     * @param  iterable|IVocabularyAnnotation[] $annotations
1082
     * @throws NotSupportedException
1083
     */
1084
    private function visitElementVocabularyAnnotations(iterable $annotations): void
1085
    {
1086
        foreach ($annotations as $annotation) {
1087
            switch ($annotation->getTerm()->getTermKind()) {
1088
                case TermKind::Type():
1089
                    assert($annotation instanceof  ITypeAnnotation);
1090
                    $this->processTypeAnnotation($annotation);
1091
                    break;
1092
                case TermKind::Value():
1093
                    assert($annotation instanceof  IValueAnnotation);
1094
1095
                    $this->processValueAnnotation($annotation);
1096
                    break;
1097
                case TermKind::None():
1098
                    $this->processVocabularyAnnotation($annotation);
1099
                    break;
1100
                default:
1101
                    throw new InvalidOperationException(
1102
                        StringConst::UnknownEnumVal_TermKind($annotation->getTerm()->getTermKind()->getKey())
1103
                    );
1104
            }
1105
        }
1106
    }
1107
1108
    /**
1109
     * @param  IEdmElement           $element
1110
     * @param  callable              $elementHeaderWriter
1111
     * @param  callable              ...$additionalAttributeWriters
1112
     * @throws NotSupportedException
1113
     */
1114
    private function beginElement(
1115
        IEdmElement $element,
1116
        callable $elementHeaderWriter,
1117
        callable ...$additionalAttributeWriters
1118
    ) {
1119
        $elementHeaderWriter($element);
1120
        if ($additionalAttributeWriters != null) {
1121
            foreach ($additionalAttributeWriters as $action) {
1122
                $action($element);
1123
            }
1124
        }
1125
1126
        $this->visitAttributeAnnotations(
1127
            $this->model->getDirectValueAnnotationsManager()->getDirectValueAnnotations($element)
1128
        );
1129
        $documentation = $this->model->getAnnotationValue(
1130
            IDocumentation::class,
1131
            $element,
1132
            EdmConstants::DocumentationUri,
1133
            EdmConstants::DocumentationAnnotation
1134
        );
1135
        if ($documentation != null) {
1136
            assert($documentation instanceof IDocumentation);
1137
            $this->processEdmDocumentation($documentation);
1138
        }
1139
    }
1140
1141
    /**
1142
     * @param  IEdmElement           $element
1143
     * @throws NotSupportedException
1144
     */
1145
    private function finishElement(IEdmElement $element): void
1146
    {
1147
        $this->visitPrimitiveElementAnnotations(
1148
            $this->model->getDirectValueAnnotationsManager()->getDirectValueAnnotations($element)
1149
        );
1150
        $vocabularyAnnotatableElement = $element;
1151
        if ($vocabularyAnnotatableElement instanceof IVocabularyAnnotatable) {
1152
            $self = $this;
1153
            $this->visitElementVocabularyAnnotations(
1154
                array_filter(
1155
                    $this->model->findDeclaredVocabularyAnnotations($vocabularyAnnotatableElement),
1156
                    function (IVocabularyAnnotation $a) use ($self) {
1157
                        return $a->isInline($self->model);
1158
                    }
1159
                )
1160
            );
1161
        }
1162
1163
        $this->schemaWriter->writeEndElement();
1164
    }
1165
1166
    private function processEdmDocumentation(IDocumentation $element): void
1167
    {
1168
        $this->schemaWriter->writeDocumentationElement($element);
1169
    }
1170
1171
    private function sharesAssociation(INavigationProperty $thisNavprop, INavigationProperty $thatNavprop): bool
1172
    {
1173
        if ($thisNavprop === $thatNavprop) {
1174
            return true;
1175
        }
1176
1177
        if ($this->model->getAssociationName($thisNavprop) != $this->model->getAssociationName($thatNavprop)) {
1178
            return false;
1179
        }
1180
1181
        $thisPrimary = $thisNavprop->getPrimary();
1182
        $thatPrimary = $thatNavprop->getPrimary();
1183
        if (!$this->sharesEnd($thisPrimary, $thatPrimary)) {
1184
            return false;
1185
        }
1186
1187
        $thisDependent = $thisPrimary->getPartner();
1188
        $thatDependent = $thatPrimary->getPartner();
1189
        if (!$this->sharesEnd($thisDependent, $thatDependent)) {
1190
            return false;
1191
        }
1192
        $thisDeclaringType = $thisPrimary->getDeclaringType();
1193
        $thatDeclaringType = $thisPrimary->getDeclaringType();
1194
        assert($thisDeclaringType instanceof IEntityType);
1195
        assert($thatDeclaringType instanceof IEntityType);
1196
        $thisPrincipalProperties = $thisDeclaringType->key();
1197
        $thatPrincipalProperties = $thatDeclaringType->key();
1198
        if (!$this->sharesReferentialConstraintEnd($thisPrincipalProperties, $thatPrincipalProperties)) {
1199
            return false;
1200
        }
1201
1202
        $thisDependentProperties = $thisDependent->getDependentProperties();
1203
        $thatDependentProperties = $thisDependent->getDependentProperties();
1204
        if ($thisDependentProperties != null &&
1205
            $thatDependentProperties != null &&
1206
            !$this->sharesReferentialConstraintEnd($thisDependentProperties, $thatDependentProperties)) {
1207
            return false;
1208
        }
1209
1210
        $thisAssociationAnnotations =[];
1211
        $thisEnd1Annotations        = [];
1212
        $thisEnd2Annotations        =[];
1213
        $thisConstraintAnnotations  =[];
1214
        $this->model->getAssociationAnnotations(
1215
            $thisPrimary,
1216
            $thisAssociationAnnotations,
1217
            $thisEnd1Annotations,
1218
            $thisEnd2Annotations,
1219
            $thisConstraintAnnotations
1220
        );
1221
1222
        $thatAssociationAnnotations = [];
1223
        $thatEnd1Annotations        = [];
1224
        $thatEnd2Annotations        = [];
1225
        $thatConstraintAnnotations  =[];
1226
        $this->model->getAssociationAnnotations(
1227
            $thatPrimary,
1228
            $thatAssociationAnnotations,
1229
            $thatEnd1Annotations,
1230
            $thatEnd2Annotations,
1231
            $thatConstraintAnnotations
1232
        );
1233
1234
        if (!($thisAssociationAnnotations == $thatAssociationAnnotations &&
1235
            $thisEnd1Annotations == $thatEnd1Annotations &&
1236
            $thisEnd2Annotations == $thatEnd2Annotations &&
1237
            $thisConstraintAnnotations == $thatConstraintAnnotations)) {
1238
            return false;
1239
        }
1240
1241
        return true;
1242
    }
1243
1244
    private function sharesEnd(INavigationProperty $end1, INavigationProperty $end2): bool
1245
    {
1246
        $end1DeclaringType = $end1->getDeclaringType();
1247
        $end2DeclaringType = $end2->getDeclaringType();
1248
        assert($end1DeclaringType instanceof IEntityType);
1249
        assert($end2DeclaringType instanceof IEntityType);
1250
        if (!($end1DeclaringType->fullName() == $end2DeclaringType->fullName() &&
1251
              $this->model->getAssociationEndName($end1) == $this->model->getAssociationEndName($end2) &&
1252
              $end1->multiplicity()->equals($end2->multiplicity()) &&
1253
              $end1->getOnDelete()->equals($end2->getOnDelete()))) {
1254
            return false;
1255
        }
1256
1257
        return true;
1258
    }
1259
1260
    /**
1261
     * @param  array|IStructuralProperty[] $theseProperties
1262
     * @param  array|IStructuralProperty[] $thoseProperties
1263
     * @return bool
1264
     */
1265
    private function sharesReferentialConstraintEnd(?array $theseProperties, ?array $thoseProperties): bool
1266
    {
1267
        if (null === $theseProperties || null === $thoseProperties) {
0 ignored issues
show
introduced by
The condition null === $thoseProperties is always false.
Loading history...
1268
            return false;
1269
        }
1270
        $numProp   = count($theseProperties);
1271
        if ($numProp != count($thoseProperties)) {
1272
            return false;
1273
        }
1274
        $theseKeys = array_keys($theseProperties);
1275
        $thoseKeys = array_keys($thoseProperties);
1276
1277
        for ($i = 0; $i < $numProp; $i++) {
1278
            $these = $theseProperties[$theseKeys[$i]];
1279
            $those = $thoseProperties[$thoseKeys[$i]];
1280
            if ($these->getName() != $those->getName()) {
1281
                return false;
1282
            }
1283
        }
1284
1285
        return true;
1286
    }
1287
1288
    private function sharesAssociationSet(
1289
        IEntitySet $thisEntitySet,
1290
        INavigationProperty $thisNavprop,
1291
        IEntitySet $thatEntitySet,
1292
        INavigationProperty $thatNavprop
1293
    ): bool {
1294
        if ($thisEntitySet === $thatEntitySet && $thisNavprop === $thatNavprop) {
1295
            return true;
1296
        }
1297
1298
        // Association Set
1299
        if (!($this->model->getAssociationSetName($thisEntitySet, $thisNavprop) ==
1300
              $this->model->getAssociationSetName($thatEntitySet, $thatNavprop) &&
1301
              $this->model->getAssociationFullName($thisNavprop) == $this->model->getAssociationFullName($thatNavprop))) {
1302
            return false;
1303
        }
1304
1305
        // End 1
1306
        if (!($this->model->getAssociationEndName($thisNavprop) == $this->model->getAssociationEndName($thatNavprop) &&
1307
            $thisEntitySet->getName() == $thatEntitySet->getName())) {
1308
            return false;
1309
        }
1310
1311
        // End 2
1312
        $thisOtherEntitySet = $thisEntitySet->findNavigationTarget($thisNavprop);
1313
        $thatOtherEntitySet = $thatEntitySet->findNavigationTarget($thatNavprop);
1314
1315
        $nullityMismatch    = (null === $thisOtherEntitySet) !== (null === $thatOtherEntitySet);
1316
        if ($nullityMismatch) {
1317
            return false;
1318
        }
1319
1320
        if (null !== $thisOtherEntitySet) {
1321
            if (!($this->model->getAssociationEndName($thisNavprop->getPartner()) ==
1322
                  $this->model->getAssociationEndName($thatNavprop->getPartner()) &&
1323
                  $thisOtherEntitySet->getName() == $thatOtherEntitySet->getName())) {
1324
                return false;
1325
            }
1326
        }
1327
1328
        // Annotations
1329
        $thisAssociationSetAnnotations = [];
1330
        $thisEnd1Annotations           = [];
1331
        $thisEnd2Annotations           = [];
1332
        $this->model->getAssociationSetAnnotations(
1333
            $thisEntitySet,
1334
            $thisNavprop,
1335
            $thisAssociationSetAnnotations,
1336
            $thisEnd1Annotations,
1337
            $thisEnd2Annotations
1338
        );
1339
1340
        $thatAssociationSetAnnotations = [];
1341
        $thatEnd1Annotations           =[];
1342
        $thatEnd2Annotations           =[];
1343
        $this->model->getAssociationSetAnnotations(
1344
            $thatEntitySet,
1345
            $thatNavprop,
1346
            $thatAssociationSetAnnotations,
1347
            $thatEnd1Annotations,
1348
            $thatEnd2Annotations
1349
        );
1350
1351
        if (!($thisAssociationSetAnnotations == $thatAssociationSetAnnotations &&
1352
            $thisEnd1Annotations == $thatEnd1Annotations &&
1353
            $thisEnd2Annotations == $thatEnd2Annotations)) {
1354
            return false;
1355
        }
1356
1357
        return true;
1358
    }
1359
}
1360