Failed Conditions
Push — master ( 2b713c...735d0a )
by Alex
16s queued 13s
created

sharesAssociation()   C

Complexity

Conditions 13
Paths 8

Size

Total Lines 71
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

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