Failed Conditions
Pull Request — master (#7898)
by Guilherme
63:09
created

XmlDriver::getCascadeMappings()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 15
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\ORM\Annotation;
9
use Doctrine\ORM\Events;
10
use Doctrine\ORM\Mapping;
11
use Doctrine\ORM\Mapping\Builder;
12
use InvalidArgumentException;
13
use SimpleXMLElement;
14
use function class_exists;
15
use function constant;
16
use function explode;
17
use function file_get_contents;
18
use function in_array;
19
use function simplexml_load_string;
20
use function str_replace;
21
use function strtoupper;
22
23
/**
24
 * XmlDriver is a metadata driver that enables mapping through XML files.
25
 */
26
class XmlDriver extends FileDriver
27
{
28
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
29
30
    /**
31
     * {@inheritDoc}
32
     */
33 41
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
34
    {
35 41
        parent::__construct($locator, $fileExtension);
36 41
    }
37
38 36
    public function loadMetadataForClass(
39
        string $className,
40
        ?Mapping\ComponentMetadata $parent,
41
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
42
    ) : Mapping\ComponentMetadata {
43
        /** @var SimpleXMLElement $xmlRoot */
44 36
        $xmlRoot       = $this->getElement($className);
45 34
        $classBuilder  = new Builder\ClassMetadataBuilder($metadataBuildingContext);
46
        $classMetadata = $classBuilder
47 34
            ->withClassName($className)
48 34
            ->withParentMetadata($parent)
49 34
            ->withEntityAnnotation(
50 34
                $xmlRoot->getName() === 'entity'
51 34
                    ? $this->convertEntityElementToEntityAnnotation($xmlRoot)
52 34
                    : null
53
            )
54 34
            ->withMappedSuperclassAnnotation(
55 34
                $xmlRoot->getName() === 'mapped-superclass'
56 5
                    ? $this->convertMappedSuperclassElementToMappedSuperclassAnnotation($xmlRoot)
57 34
                    : null
58
            )
59 34
            ->withEmbeddableAnnotation(
60 34
                $xmlRoot->getName() === 'embeddable'
61
                    ? null
62 34
                    : null
63
            )
64 34
            ->withTableAnnotation(
65
                // @todo guilhermeblanco Is this the proper check to build Table annotation?
66 34
                $xmlRoot->getName() === 'entity'
67 34
                    ? $this->convertTableElementToTableAnnotation($xmlRoot)
68 34
                    : null
69
            )
70 34
            ->withInheritanceTypeAnnotation(
71 34
                isset($xmlRoot['inheritance-type'])
72 10
                    ? $this->convertInheritanceTypeElementToInheritanceTypeAnnotation($xmlRoot)
73 34
                    : null
74
            )
75 34
            ->withDiscriminatorColumnAnnotation(
76 34
                isset($xmlRoot->{'discriminator-column'})
77 8
                    ? $this->convertDiscrimininatorColumnElementToDiscriminatorColumnAnnotation($xmlRoot->{'discriminator-column'})
78 34
                    : null
79
            )
80 34
            ->withDiscriminatorMapAnnotation(
81 34
                isset($xmlRoot->{'discriminator-map'})
82 10
                    ? $this->convertDiscriminatorMapElementToDiscriminatorMapAnnotation($xmlRoot->{'discriminator-map'})
83 34
                    : null
84
            )
85 34
            ->withChangeTrackingPolicyAnnotation(
86 34
                isset($xmlRoot['change-tracking-policy'])
87
                    ? $this->convertChangeTrackingPolicyElementToChangeTrackingPolicyAnnotation($xmlRoot)
88 34
                    : null
89
            )
90 34
            ->withCacheAnnotation(
91 34
                isset($xmlRoot->cache)
92 2
                    ? $this->convertCacheElementToCacheAnnotation($xmlRoot->cache)
93 34
                    : null
94
            )
95 34
            ->build();
96
97 34
        $propertyBuilder = new Builder\PropertyMetadataBuilder($metadataBuildingContext);
98
99 34
        $propertyBuilder->withComponentMetadata($classMetadata);
100
101
        // Evaluate <field ...> mappings
102 34
        if (isset($xmlRoot->field)) {
103 20
            foreach ($xmlRoot->field as $fieldElement) {
104
                $propertyBuilder
105 20
                    ->withFieldName((string) $fieldElement['name'])
106 20
                    ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($fieldElement))
107 20
                    ->withIdAnnotation(null)
108 20
                    ->withVersionAnnotation(
109 20
                        isset($fieldElement['version']) && $this->evaluateBoolean($fieldElement['version'])
110 3
                            ? new Annotation\Version()
111 20
                            : null
112
                    );
113
114 20
                $classMetadata->addProperty($propertyBuilder->build());
115
            }
116
        }
117
118 34
        // Evaluate <embedded ...> mappings
119
        if (isset($xmlRoot->embedded)) {
120
            foreach ($xmlRoot->embedded as $embeddedElement) {
121
                $propertyBuilder
122
                    ->withFieldName((string) $embeddedElement['name'])
123
                    ->withEmbeddedAnnotation($this->convertEmbeddedElementToEmbeddedAnnotation($embeddedElement))
124
                    ->withIdAnnotation(null)
125
                    ->withVersionAnnotation(
126
                        isset($embeddedElement['version']) && $this->evaluateBoolean($embeddedElement['version'])
127
                            ? new Annotation\Version()
128
                            : null
129
                    );
130
131
                $classMetadata->addProperty($propertyBuilder->build());
132
            }
133
        }
134
135
        // Evaluate <id ...> mappings
136
        $associationIds = [];
137
138
        foreach ($xmlRoot->id as $idElement) {
139 34
            $fieldName = (string) $idElement['name'];
140
141 34
            if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) {
142 29
                $associationIds[$fieldName] = true;
143
144 29
                continue;
145 2
            }
146
147 2
            $propertyBuilder
148
                ->withFieldName($fieldName)
149
                ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($idElement))
150
                ->withIdAnnotation(new Annotation\Id())
151 28
                ->withVersionAnnotation(
152 28
                    isset($idElement['version']) && $this->evaluateBoolean($idElement['version'])
153 28
                        ? new Annotation\Version()
154 28
                        : null
155 28
                )
156
                ->withGeneratedValueAnnotation(
157 28
                    isset($idElement->generator)
158
                        ? $this->convertGeneratorElementToGeneratedValueAnnotation($idElement->generator)
159 28
                        : null
160 28
                )
161 27
                ->withSequenceGeneratorAnnotation(
162 28
                    isset($idElement->{'sequence-generator'})
163
                        ? $this->convertSequenceGeneratorElementToSequenceGeneratorAnnotation($idElement->{'sequence-generator'})
164 28
                        : null
165 28
                )
166 3
                ->withCustomIdGeneratorAnnotation(
167 28
                    isset($idElement->{'custom-id-generator'})
168
                        ? $this->convertCustomIdGeneratorElementToCustomIdGeneratorAnnotation($idElement->{'custom-id-generator'})
169 28
                        : null
170 28
                );
171 2
172 28
            $classMetadata->addProperty($propertyBuilder->build());
173
        }
174
175 28
        // Evaluate <one-to-one ...> mappings
176
        if (isset($xmlRoot->{'one-to-one'})) {
177
            foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
178
                $fieldName = (string) $oneToOneElement['field'];
179 34
180 7
                $propertyBuilder
181 7
                    ->withFieldName($fieldName)
182
                    ->withOneToOneAnnotation($this->convertOneToOneElementToOneToOneAnnotation($oneToOneElement))
183
                    ->withIdAnnotation(isset($associationIds[$fieldName]) ? new Annotation\Id() : null)
184 7
                    ->withVersionAnnotation(null)
185 7
                    ->withCacheAnnotation(
186 7
                        isset($oneToOneElement->cache)
187 7
                            ? $this->convertCacheElementToCacheAnnotation($oneToOneElement->cache)
188 7
                            : null
189 7
                    )
190
                    ->withJoinColumnAnnotation(
191 7
                        isset($oneToOneElement->{'join-column'})
192
                            ? $this->convertJoinColumnElementToJoinColumnAnnotation($oneToOneElement->{'join-column'})
193 7
                            : null
194 7
                    )
195 5
                    ->withJoinColumnsAnnotation(
196 7
                        isset($oneToOneElement->{'join-columns'})
197
                            ? $this->convertJoinColumnsElementToJoinColumnsAnnotation($oneToOneElement->{'join-columns'})
198 7
                            : null
199 7
                    );
200
201 7
                $classMetadata->addProperty($propertyBuilder->build());
202
            }
203
        }
204 7
205
        // Evaluate <many-to-one ...> mappings
206
        if (isset($xmlRoot->{'many-to-one'})) {
207
            foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
208
                $fieldName = (string) $manyToOneElement['field'];
209 34
210 8
                $propertyBuilder
211 8
                    ->withFieldName($fieldName)
212
                    ->withManyToOneAnnotation($this->convertManyToOneElementToManyToOneAnnotation($manyToOneElement))
213
                    ->withIdAnnotation(isset($associationIds[$fieldName]) ? new Annotation\Id() : null)
214 8
                    ->withVersionAnnotation(null)
215 8
                    ->withCacheAnnotation(
216 8
                        isset($manyToOneElement->cache)
217 8
                            ? $this->convertCacheElementToCacheAnnotation($manyToOneElement->cache)
218 8
                            : null
219 8
                    )
220 1
                    ->withJoinColumnAnnotation(
221 8
                        isset($manyToOneElement->{'join-column'})
222
                            ? $this->convertJoinColumnElementToJoinColumnAnnotation($manyToOneElement->{'join-column'})
223 8
                            : null
224 8
                    )
225 7
                    ->withJoinColumnsAnnotation(
226 8
                        isset($manyToOneElement->{'join-columns'})
227
                            ? $this->convertJoinColumnsElementToJoinColumnsAnnotation($manyToOneElement->{'join-columns'})
228 8
                            : null
229 8
                    );
230 1
231 8
                $classMetadata->addProperty($propertyBuilder->build());
232
            }
233
        }
234 8
235
        // Evaluate <one-to-many ...> mappings
236
        if (isset($xmlRoot->{'one-to-many'})) {
237
            foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
238
                $fieldName = (string) $oneToManyElement['field'];
239 33
240 9
                $propertyBuilder
241 9
                    ->withFieldName($fieldName)
242
                    ->withOneToManyAnnotation($this->convertOneToManyElementToOneToManyAnnotation($oneToManyElement))
243
                    ->withIdAnnotation(isset($associationIds[$fieldName]) ? new Annotation\Id() : null)
244 9
                    ->withVersionAnnotation(null)
245 9
                    ->withCacheAnnotation(
246 9
                        isset($oneToManyElement->cache)
247 9
                            ? $this->convertCacheElementToCacheAnnotation($oneToManyElement->cache)
248 9
                            : null
249 9
                    )
250 1
                    ->withOrderByAnnotation(
251 9
                        isset($oneToManyElement->{'order-by'})
252
                            ? $this->convertOrderByElementToOrderByAnnotation($oneToManyElement->{'order-by'})
253 9
                            : null
254 9
                    );
255 5
256 9
                $classMetadata->addProperty($propertyBuilder->build());
257
            }
258
        }
259 9
260
        // Evaluate <many-to-many ...> mappings
261
        if (isset($xmlRoot->{'many-to-many'})) {
262
            foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
263
                $fieldName = (string) $manyToManyElement['field'];
264 33
265 14
                $propertyBuilder
266 14
                    ->withFieldName($fieldName)
267
                    ->withManyToManyAnnotation($this->convertManyToManyElementToManyToManyAnnotation($manyToManyElement))
268
                    ->withIdAnnotation(isset($associationIds[$fieldName]) ? new Annotation\Id() : null)
269 14
                    ->withVersionAnnotation(null)
270 14
                    ->withCacheAnnotation(
271 14
                        isset($manyToManyElement->cache)
272 14
                            ? $this->convertCacheElementToCacheAnnotation($manyToManyElement->cache)
273 14
                            : null
274 14
                    )
275
                    ->withJoinTableAnnotation(
276 14
                        isset($manyToManyElement->{'join-table'})
277
                            ? $this->convertJoinTableElementToJoinTableAnnotation($manyToManyElement->{'join-table'})
278 14
                            : null
279 14
                    )
280 8
                    ->withOrderByAnnotation(
281 14
                        isset($manyToManyElement->{'order-by'})
282
                            ? $this->convertOrderByElementToOrderByAnnotation($manyToManyElement->{'order-by'})
283 14
                            : null
284 14
                    );
285 1
286 14
                $classMetadata->addProperty($propertyBuilder->build());
287
            }
288
        }
289 14
290
        // Evaluate association-overrides
291
        if (isset($xmlRoot->{'attribute-overrides'})) {
292
            $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext);
293
294 33
            $fieldBuilder
295 2
                ->withComponentMetadata($classMetadata);
296
297
            foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) {
298 2
                $fieldName = (string) $overrideElement['name'];
299
                $property  = $classMetadata->getProperty($fieldName);
300 2
301 2
                if (! $property) {
302 2
                    throw Mapping\MappingException::invalidOverrideFieldName($classMetadata->getClassName(), $fieldName);
303
                }
304 2
305
                foreach ($overrideElement->field as $fieldElement) {
306
                    $versionAnnotation = isset($fieldElement['version']) && $this->evaluateBoolean($fieldElement['version'])
307
                        ? new Annotation\Version()
308 2
                        : null;
309 2
310
                    $fieldBuilder
311 2
                        ->withFieldName($fieldName)
312
                        ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($fieldElement))
313
                        ->withIdAnnotation(null)
314 2
                        ->withVersionAnnotation($versionAnnotation);
315 2
316 2
                    $fieldMetadata = $fieldBuilder->build();
317 2
                    $columnName    = $fieldMetadata->getColumnName();
318
319 2
                    // Prevent column duplication
320 2
                    if ($classMetadata->checkPropertyDuplication($columnName)) {
0 ignored issues
show
Bug introduced by
It seems like $columnName can also be of type null; however, parameter $columnName of Doctrine\ORM\Mapping\Cla...ckPropertyDuplication() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

320
                    if ($classMetadata->checkPropertyDuplication(/** @scrutinizer ignore-type */ $columnName)) {
Loading history...
321
                        throw Mapping\MappingException::duplicateColumnName($classMetadata->getClassName(), $columnName);
322
                    }
323 2
324
                    $classMetadata->setPropertyOverride($fieldMetadata);
325
                }
326
            }
327 2
        }
328
329
        // Evaluate association-overrides
330
        if (isset($xmlRoot->{'association-overrides'})) {
331
            foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) {
332
                $fieldName = (string) $overrideElement['name'];
333 33
                $property  = $classMetadata->getProperty($fieldName);
334 4
335 4
                if (! $property) {
336 4
                    throw Mapping\MappingException::invalidOverrideFieldName($classMetadata->getClassName(), $fieldName);
337
                }
338 4
339
                $override = clone $property;
340
341
                // Check for join-columns
342 4
                if (isset($overrideElement->{'join-columns'})) {
343
                    $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
344
345 4
                    $joinColumnBuilder
346 2
                        ->withComponentMetadata($classMetadata)
347
                        ->withFieldName($override->getName());
348
349 2
                    $joinColumns = [];
350 2
351
                    foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
352 2
                        $joinColumnBuilder->withJoinColumnAnnotation(
353
                            $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement)
354 2
                        );
355 2
356 2
                        $joinColumnMetadata = $joinColumnBuilder->build();
357
                        $columnName         = $joinColumnMetadata->getColumnName();
0 ignored issues
show
Unused Code introduced by
The assignment to $columnName is dead and can be removed.
Loading history...
358
359 2
                        // @todo guilhermeblanco Open an issue to discuss making this scenario impossible.
360 2
                        //if ($metadata->checkPropertyDuplication($columnName)) {
361
                        //    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
362
                        //}
363
364
                        $joinColumns[] = $joinColumnMetadata;
365
                    }
366
367 2
                    $override->setJoinColumns($joinColumns);
0 ignored issues
show
Bug introduced by
The method setJoinColumns() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\ToOneAssociationMetadata. ( Ignorable by Annotation )

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

367
                    $override->/** @scrutinizer ignore-call */ 
368
                               setJoinColumns($joinColumns);
Loading history...
368
                }
369
370 2
                // Check for join-table
371
                if ($overrideElement->{'join-table'}) {
372
                    $joinTableElement    = $overrideElement->{'join-table'};
373
                    $joinTableAnnotation = $this->convertJoinTableElementToJoinTableAnnotation($joinTableElement);
374 4
                    $joinTableBuilder    = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
375 2
376 2
                    $joinTableBuilder
377 2
                        ->withComponentMetadata($classMetadata)
378
                        ->withFieldName($property->getName())
379
                        ->withTargetEntity($property->getTargetEntity())
0 ignored issues
show
Bug introduced by
The method getTargetEntity() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\EmbeddedMetadata or Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

379
                        ->withTargetEntity($property->/** @scrutinizer ignore-call */ getTargetEntity())
Loading history...
380 2
                        ->withJoinTableAnnotation($joinTableAnnotation);
381 2
382 2
                    $override->setJoinTable($joinTableBuilder->build());
0 ignored issues
show
Bug introduced by
The method setJoinTable() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. ( Ignorable by Annotation )

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

382
                    $override->/** @scrutinizer ignore-call */ 
383
                               setJoinTable($joinTableBuilder->build());
Loading history...
383 2
                }
384
385 2
                // Check for inversed-by
386
                if (isset($overrideElement->{'inversed-by'})) {
387
                    $override->setInversedBy((string) $overrideElement->{'inversed-by'}['name']);
0 ignored issues
show
Bug introduced by
The method setInversedBy() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

387
                    $override->/** @scrutinizer ignore-call */ 
388
                               setInversedBy((string) $overrideElement->{'inversed-by'}['name']);
Loading history...
388
                }
389 4
390 1
                // Check for fetch
391
                if (isset($overrideElement['fetch'])) {
392
                    $override->setFetchMode(
0 ignored issues
show
Bug introduced by
The method setFetchMode() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

392
                    $override->/** @scrutinizer ignore-call */ 
393
                               setFetchMode(
Loading history...
393
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $overrideElement['fetch'])
394 4
                    );
395 1
                }
396 1
397
                $classMetadata->setPropertyOverride($override);
398
            }
399
        }
400 4
401
        // Evaluate <lifecycle-callbacks...>
402
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
403
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
404
                $eventName  = constant(Events::class . '::' . (string) $lifecycleCallback['type']);
405 33
                $methodName = (string) $lifecycleCallback['method'];
406 3
407 3
                $classMetadata->addLifecycleCallback($eventName, $methodName);
408 3
            }
409
        }
410 3
411
        // Evaluate entity listener
412
        if (isset($xmlRoot->{'entity-listeners'})) {
413
            foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) {
414
                $listenerClassName = (string) $listenerElement['class'];
415 33
416 2
                if (! class_exists($listenerClassName)) {
417 2
                    throw Mapping\MappingException::entityListenerClassNotFound(
418
                        $listenerClassName,
419 2
                        $classMetadata->getClassName()
420
                    );
421
                }
422
423
                foreach ($listenerElement as $callbackElement) {
424
                    $eventName  = (string) $callbackElement['type'];
425
                    $methodName = (string) $callbackElement['method'];
426 2
427 2
                    $classMetadata->addEntityListener($eventName, $listenerClassName, $methodName);
428 2
                }
429
            }
430 2
        }
431
432
        return $classMetadata;
433
    }
434
435 33
    /**
436
     * {@inheritDoc}
437
     */
438
    protected function loadMappingFile($file)
439
    {
440
        $result = [];
441 36
        // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577
442
        $xmlElement = simplexml_load_string(file_get_contents($file));
443 36
444
        if (isset($xmlElement->entity)) {
445 36
            foreach ($xmlElement->entity as $entityElement) {
446
                $entityName          = (string) $entityElement['name'];
447 36
                $result[$entityName] = $entityElement;
448 35
            }
449 35
        } elseif (isset($xmlElement->{'mapped-superclass'})) {
450 35
            foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) {
451
                $className          = (string) $mappedSuperClass['name'];
452 6
                $result[$className] = $mappedSuperClass;
453 5
            }
454 5
        } elseif (isset($xmlElement->embeddable)) {
455 5
            foreach ($xmlElement->embeddable as $embeddableElement) {
456
                $embeddableName          = (string) $embeddableElement['name'];
457 1
                $result[$embeddableName] = $embeddableElement;
458
            }
459
        }
460
461
        return $result;
462
    }
463
464 36
    private function convertEntityElementToEntityAnnotation(
465
        SimpleXMLElement $entityElement
466
    ) : Annotation\Entity {
467 34
        $entityAnnotation = new Annotation\Entity();
468
469
        if (isset($entityElement['repository-class'])) {
470 34
            $entityAnnotation->repositoryClass = (string) $entityElement['repository-class'];
471
        }
472 34
473
        if (isset($entityElement['read-only'])) {
474
            $entityAnnotation->readOnly = $this->evaluateBoolean($entityElement['read-only']);
475
        }
476 34
477
        return $entityAnnotation;
478
    }
479
480 34
    private function convertMappedSuperclassElementToMappedSuperclassAnnotation(
481
        SimpleXMLElement $mappedSuperclassElement
482
    ) : Annotation\MappedSuperclass {
483 5
        $mappedSuperclassAnnotation = new Annotation\MappedSuperclass();
484
485
        if (isset($mappedSuperclassElement['repository-class'])) {
486 5
            $mappedSuperclassAnnotation->repositoryClass = (string) $mappedSuperclassElement['repository-class'];
487
        }
488 5
489 1
        return $mappedSuperclassAnnotation;
490
    }
491
492 5
    private function convertTableElementToTableAnnotation(
493
        SimpleXMLElement $tableElement
494
    ) : Annotation\Table {
495 34
        $tableAnnotation = new Annotation\Table();
496
497
        // Evaluate <entity...> attributes
498 34
        if (isset($tableElement['table'])) {
499
            $tableAnnotation->name = (string) $tableElement['table'];
500
        }
501 34
502 12
        if (isset($tableElement['schema'])) {
503
            $tableAnnotation->schema = (string) $tableElement['schema'];
504
        }
505 34
506 2
        // Evaluate <indexes...>
507
        if (isset($tableElement->indexes)) {
508
            $indexes = [];
509
510 34
            /** @var SimpleXMLElement $indexElement */
511 4
            foreach ($tableElement->indexes->children() as $indexElement) {
512
                $indexes[] = $this->convertIndexElementToIndexAnnotation($indexElement);
513
            }
514 4
515 4
            $tableAnnotation->indexes = $indexes;
516
        }
517
518 4
        // Evaluate <unique-constraints..>
519
        if (isset($tableElement->{'unique-constraints'})) {
520
            $uniqueConstraints = [];
521
522 34
            foreach ($tableElement->{'unique-constraints'}->children() as $uniqueConstraintElement) {
523 3
                $uniqueConstraints[] = $this->convertUniqueConstraintElementToUniqueConstraintAnnotation($uniqueConstraintElement);
524
            }
525 3
526 3
            $tableAnnotation->uniqueConstraints = $uniqueConstraints;
527
        }
528
529 3
        if (isset($tableElement->options)) {
530
            $tableAnnotation->options = $this->parseOptions($tableElement->options->children());
531
        }
532 34
533 3
        return $tableAnnotation;
534
    }
535
536 34
    private function convertIndexElementToIndexAnnotation(
537
        SimpleXMLElement $indexElement
538
    ) : Annotation\Index {
539 4
        $indexAnnotation = new Annotation\Index();
540
541
        $indexAnnotation->columns = explode(',', (string) $indexElement['columns']);
542 4
        $indexAnnotation->options = isset($indexElement->options) ? $this->parseOptions($indexElement->options->children()) : [];
543
        $indexAnnotation->flags   = isset($indexElement['flags']) ? explode(',', (string) $indexElement['flags']) : [];
544 4
545 4
        if (isset($indexElement['name'])) {
546 4
            $indexAnnotation->name = (string) $indexElement['name'];
547
        }
548 4
549 3
        if (isset($indexElement['unique'])) {
550
            $indexAnnotation->unique = $this->evaluateBoolean($indexElement['unique']);
551
        }
552 4
553
        return $indexAnnotation;
554
    }
555
556 4
    private function convertUniqueConstraintElementToUniqueConstraintAnnotation(
557
        SimpleXMLElement $uniqueConstraintElement
558
    ) : Annotation\UniqueConstraint {
559 3
        $uniqueConstraintAnnotation = new Annotation\UniqueConstraint();
560
561
        $uniqueConstraintAnnotation->columns = explode(',', (string) $uniqueConstraintElement['columns']);
562 3
        $uniqueConstraintAnnotation->options = isset($uniqueConstraintElement->options) ? $this->parseOptions($uniqueConstraintElement->options->children()) : [];
563
        $uniqueConstraintAnnotation->flags   = isset($uniqueConstraintElement['flags']) ? explode(',', (string) $uniqueConstraintElement['flags']) : [];
564 3
565 3
        if (isset($uniqueConstraintElement['name'])) {
566 3
            $uniqueConstraintAnnotation->name = (string) $uniqueConstraintElement['name'];
567
        }
568 3
569 3
        return $uniqueConstraintAnnotation;
570
    }
571
572 3
    private function convertInheritanceTypeElementToInheritanceTypeAnnotation(
573
        SimpleXMLElement $inheritanceTypeElement
574
    ) : Annotation\InheritanceType {
575 10
        $inheritanceTypeAnnotation = new Annotation\InheritanceType();
576
577
        $inheritanceTypeAnnotation->value = strtoupper((string) $inheritanceTypeElement['inheritance-type']);
578 10
579
        return $inheritanceTypeAnnotation;
580 10
    }
581
582 10
    private function convertChangeTrackingPolicyElementToChangeTrackingPolicyAnnotation(
583
        SimpleXMLElement $changeTrackingPolicyElement
584
    ) : Annotation\ChangeTrackingPolicy {
585
        $changeTrackingPolicyAnnotation = new Annotation\ChangeTrackingPolicy();
586
587
        $changeTrackingPolicyAnnotation->value = strtoupper((string) $changeTrackingPolicyElement['change-tracking-policy']);
588
589
        return $changeTrackingPolicyAnnotation;
590
    }
591
592
    private function convertDiscrimininatorColumnElementToDiscriminatorColumnAnnotation(
593
        SimpleXMLElement $discriminatorColumnElement
594
    ) : Annotation\DiscriminatorColumn {
595 8
        $discriminatorColumnAnnotation = new Annotation\DiscriminatorColumn();
596
597
        $discriminatorColumnAnnotation->type = (string) ($discriminatorColumnElement['type'] ?? 'string');
598 8
        $discriminatorColumnAnnotation->name = (string) $discriminatorColumnElement['name'];
599
600 8
        if (isset($discriminatorColumnElement['column-definition'])) {
601 8
            $discriminatorColumnAnnotation->columnDefinition = (string) $discriminatorColumnElement['column-definition'];
602
        }
603 8
604 1
        if (isset($discriminatorColumnElement['length'])) {
605
            $discriminatorColumnAnnotation->length = (int) $discriminatorColumnElement['length'];
606
        }
607 8
608 3
        return $discriminatorColumnAnnotation;
609
    }
610
611 8
    private function convertDiscriminatorMapElementToDiscriminatorMapAnnotation(
612
        SimpleXMLElement $discriminatorMapElement
613
    ) : Annotation\DiscriminatorMap {
614 10
        $discriminatorMapAnnotation = new Annotation\DiscriminatorMap();
615
        $discriminatorMap           = [];
616
617 10
        foreach ($discriminatorMapElement->{'discriminator-mapping'} as $discriminatorMapElement) {
618 10
            $discriminatorMap[(string) $discriminatorMapElement['value']] = (string) $discriminatorMapElement['class'];
619
        }
620 10
621 10
        $discriminatorMapAnnotation->value = $discriminatorMap;
622
623
        return $discriminatorMapAnnotation;
624 10
    }
625
626 10
    private function convertCacheElementToCacheAnnotation(
627
        SimpleXMLElement $cacheElement
628
    ) : Annotation\Cache {
629 2
        $cacheAnnotation = new Annotation\Cache();
630
631
        if (isset($cacheElement['region'])) {
632 2
            $cacheAnnotation->region = (string) $cacheElement['region'];
633
        }
634 2
635
        if (isset($cacheElement['usage'])) {
636
            $cacheAnnotation->usage = strtoupper((string) $cacheElement['usage']);
637
        }
638 2
639 2
        return $cacheAnnotation;
640
    }
641
642 2
    private function convertFieldElementToColumnAnnotation(
643
        SimpleXMLElement $fieldElement
644
    ) : Annotation\Column {
645 30
        $columnAnnotation = new Annotation\Column();
646
647
        $columnAnnotation->type = isset($fieldElement['type']) ? (string) $fieldElement['type'] : 'string';
648 30
649
        if (isset($fieldElement['column'])) {
650 30
            $columnAnnotation->name = (string) $fieldElement['column'];
651
        }
652 30
653 20
        if (isset($fieldElement['length'])) {
654
            $columnAnnotation->length = (int) $fieldElement['length'];
655
        }
656 30
657 6
        if (isset($fieldElement['precision'])) {
658
            $columnAnnotation->precision = (int) $fieldElement['precision'];
659
        }
660 30
661 1
        if (isset($fieldElement['scale'])) {
662
            $columnAnnotation->scale = (int) $fieldElement['scale'];
663
        }
664 30
665 1
        if (isset($fieldElement['unique'])) {
666
            $columnAnnotation->unique = $this->evaluateBoolean($fieldElement['unique']);
667
        }
668 30
669 7
        if (isset($fieldElement['nullable'])) {
670
            $columnAnnotation->nullable = $this->evaluateBoolean($fieldElement['nullable']);
671
        }
672 30
673 7
        if (isset($fieldElement['column-definition'])) {
674
            $columnAnnotation->columnDefinition = (string) $fieldElement['column-definition'];
675
        }
676 30
677 4
        if (isset($fieldElement->options)) {
678
            $columnAnnotation->options = $this->parseOptions($fieldElement->options->children());
679
        }
680 30
681 3
        return $columnAnnotation;
682
    }
683
684 30
    private function convertEmbeddedElementToEmbeddedAnnotation(
685
        SimpleXMLElement $embeddedElement
686
    ) : Annotation\Embedded {
687 7
        $embeddedAnnotation = new Annotation\Embedded();
688
689
        $embeddedAnnotation->class = (string) $embeddedElement['class'];
690 7
691
        if (isset($embeddedElement['column-prefix'])) {
692 7
            $embeddedAnnotation->columnPrefix = (string) $embeddedElement['column-prefix'];
693
        }
694 7
695 3
        return $embeddedAnnotation;
696
    }
697
698 7
    private function convertOneToOneElementToOneToOneAnnotation(
699 4
        SimpleXMLElement $oneToOneElement
700
    ) : Annotation\OneToOne {
701
        $oneToOneAnnotation = new Annotation\OneToOne();
702 7
703
        $oneToOneAnnotation->targetEntity = (string) $oneToOneElement['target-entity'];
704
705
        if (isset($oneToOneElement['mapped-by'])) {
706 7
            $oneToOneAnnotation->mappedBy = (string) $oneToOneElement['mapped-by'];
707 3
        }
708
709
        if (isset($oneToOneElement['inversed-by'])) {
710 7
            $oneToOneAnnotation->inversedBy = (string) $oneToOneElement['inversed-by'];
711 7
        }
712
713
        if (isset($oneToOneElement['orphan-removal'])) {
714 7
            $oneToOneAnnotation->orphanRemoval = $this->evaluateBoolean($oneToOneElement['orphan-removal']);
715
        }
716
717 8
        if (isset($oneToOneElement['fetch'])) {
718
            $oneToOneAnnotation->fetch = (string) $oneToOneElement['fetch'];
719
        }
720 8
721
        if (isset($oneToOneElement->cascade)) {
722 8
            $oneToOneAnnotation->cascade = $this->getCascadeMappings($oneToOneElement->cascade);
723
        }
724 8
725 2
        return $oneToOneAnnotation;
726
    }
727
728 8
    private function convertManyToOneElementToManyToOneAnnotation(
729
        SimpleXMLElement $manyToOneElement
730
    ) : Annotation\ManyToOne {
731
        $manyToOneAnnotation = new Annotation\ManyToOne();
732 8
733 4
        $manyToOneAnnotation->targetEntity = (string) $manyToOneElement['target-entity'];
734
735
        if (isset($manyToOneElement['inversed-by'])) {
736 8
            $manyToOneAnnotation->inversedBy = (string) $manyToOneElement['inversed-by'];
737
        }
738
739 9
        if (isset($manyToOneElement['fetch'])) {
740
            $manyToOneAnnotation->fetch = (string) $manyToOneElement['fetch'];
741
        }
742 9
743
        if (isset($manyToOneElement->cascade)) {
744 9
            $manyToOneAnnotation->cascade = $this->getCascadeMappings($manyToOneElement->cascade);
745
        }
746 9
747 9
        return $manyToOneAnnotation;
748
    }
749
750 9
    private function convertOneToManyElementToOneToManyAnnotation(
751
        SimpleXMLElement $oneToManyElement
752
    ) : Annotation\OneToMany {
753
        $oneToManyAnnotation = new Annotation\OneToMany();
754 9
755 6
        $oneToManyAnnotation->targetEntity = (string) $oneToManyElement['target-entity'];
756
757
        if (isset($oneToManyElement['mapped-by'])) {
758 9
            $oneToManyAnnotation->mappedBy = (string) $oneToManyElement['mapped-by'];
759 3
        }
760
761
        if (isset($oneToManyElement['fetch'])) {
762 9
            $oneToManyAnnotation->fetch = (string) $oneToManyElement['fetch'];
763 3
        }
764 6
765
        if (isset($oneToManyElement->cascade)) {
766
            $oneToManyAnnotation->cascade = $this->getCascadeMappings($oneToManyElement->cascade);
767
        }
768 9
769
        if (isset($oneToManyElement['orphan-removal'])) {
770
            $oneToManyAnnotation->orphanRemoval = $this->evaluateBoolean($oneToManyElement['orphan-removal']);
771 14
        }
772
773
        if (isset($oneToManyElement['index-by'])) {
774 14
            $oneToManyAnnotation->indexBy = (string) $oneToManyElement['index-by'];
775
        } elseif (isset($oneToManyElement->{'index-by'})) {
776 14
            throw new InvalidArgumentException('<index-by /> is not a valid tag');
777
        }
778 14
779 5
        return $oneToManyAnnotation;
780
    }
781
782 14
    private function convertManyToManyElementToManyToManyAnnotation(
783 6
        SimpleXMLElement $manyToManyElement
784
    ) : Annotation\ManyToMany {
785
        $manyToManyAnnotation = new Annotation\ManyToMany();
786 14
787 4
        $manyToManyAnnotation->targetEntity = (string) $manyToManyElement['target-entity'];
788
789
        if (isset($manyToManyElement['mapped-by'])) {
790 14
            $manyToManyAnnotation->mappedBy = (string) $manyToManyElement['mapped-by'];
791 8
        }
792
793
        if (isset($manyToManyElement['inversed-by'])) {
794 14
            $manyToManyAnnotation->inversedBy = (string) $manyToManyElement['inversed-by'];
795
        }
796
797
        if (isset($manyToManyElement['fetch'])) {
798 14
            $manyToManyAnnotation->fetch = (string) $manyToManyElement['fetch'];
799
        }
800 14
801
        if (isset($manyToManyElement->cascade)) {
802
            $manyToManyAnnotation->cascade = $this->getCascadeMappings($manyToManyElement->cascade);
803
        }
804 14
805
        if (isset($manyToManyElement['orphan-removal'])) {
806
            $manyToManyAnnotation->orphanRemoval = $this->evaluateBoolean($manyToManyElement['orphan-removal']);
807
        }
808
809
        if (isset($manyToManyElement['index-by'])) {
810
            $manyToManyAnnotation->indexBy = (string) $manyToManyElement['index-by'];
811
        } elseif (isset($manyToManyElement->{'index-by'})) {
812
            throw new InvalidArgumentException('<index-by /> is not a valid tag');
813 8
        }
814
815
        return $manyToManyAnnotation;
816 8
    }
817
818 8
    /**
819 8
     * Constructs a JoinTable annotation based on the information
820
     * found in the given SimpleXMLElement.
821
     *
822 8
     * @param SimpleXMLElement $joinTableElement The XML element.
823
     */
824
    private function convertJoinTableElementToJoinTableAnnotation(
825
        SimpleXMLElement $joinTableElement
826 8
    ) : Annotation\JoinTable {
827 8
        $joinTableAnnotation = new Annotation\JoinTable();
828
829 8
        if (isset($joinTableElement['name'])) {
830 8
            $joinTableAnnotation->name = (string) $joinTableElement['name'];
831
        }
832
833 8
        if (isset($joinTableElement['schema'])) {
834
            $joinTableAnnotation->schema = (string) $joinTableElement['schema'];
835
        }
836 8
837 8
        if (isset($joinTableElement->{'join-columns'})) {
838
            $joinColumns = [];
839 8
840 8
            foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
841
                $joinColumns[] = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
842
            }
843 8
844
            $joinTableAnnotation->joinColumns = $joinColumns;
845
        }
846 8
847
        if (isset($joinTableElement->{'inverse-join-columns'})) {
848
            $joinColumns = [];
849 1
850
            foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
851
                $joinColumns[] = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
852 1
            }
853 1
854
            $joinTableAnnotation->inverseJoinColumns = $joinColumns;
855 1
        }
856 1
857
        return $joinTableAnnotation;
858
    }
859 1
860
    private function convertJoinColumnsElementToJoinColumnsAnnotation(
861 1
        SimpleXMLElement $joinColumnsElement
862
    ) : Annotation\JoinColumns {
863
        $joinColumnsAnnotation = new Annotation\JoinColumns();
864
        $joinColumns           = [];
865
866
        foreach ($joinColumnsElement->{'join-column'} as $joinColumnElement) {
867
            $joinColumns[] = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
868
        }
869
870 13
        $joinColumnsAnnotation->value = $joinColumns;
871
872
        return $joinColumnsAnnotation;
873 13
    }
874
875 13
    /**
876 13
     * Constructs a JoinColumn annotation based on the information
877
     * found in the given SimpleXMLElement.
878 13
     *
879 3
     * @param SimpleXMLElement $joinColumnElement The XML element.
880
     */
881
    private function convertJoinColumnElementToJoinColumnAnnotation(
882 13
        SimpleXMLElement $joinColumnElement
883
    ) : Annotation\JoinColumn {
884
        $joinColumnAnnotation = new Annotation\JoinColumn();
885
886 13
        $joinColumnAnnotation->name                 = (string) $joinColumnElement['name'];
887 4
        $joinColumnAnnotation->referencedColumnName = (string) $joinColumnElement['referenced-column-name'];
888
889
        if (isset($joinColumnElement['column-definition'])) {
890 13
            $joinColumnAnnotation->columnDefinition = (string) $joinColumnElement['column-definition'];
891 3
        }
892
893
        if (isset($joinColumnElement['field-name'])) {
894 13
            $joinColumnAnnotation->fieldName = (string) $joinColumnElement['field-name'];
895 3
        }
896
897
        if (isset($joinColumnElement['nullable'])) {
898 13
            $joinColumnAnnotation->nullable = $this->evaluateBoolean($joinColumnElement['nullable']);
899
        }
900
901 6
        if (isset($joinColumnElement['unique'])) {
902
            $joinColumnAnnotation->unique = $this->evaluateBoolean($joinColumnElement['unique']);
903
        }
904 6
905 6
        if (isset($joinColumnElement['on-delete'])) {
906
            $joinColumnAnnotation->onDelete = strtoupper((string) $joinColumnElement['on-delete']);
907 6
        }
908 6
909 4
        return $joinColumnAnnotation;
910 2
    }
911
912
    private function convertOrderByElementToOrderByAnnotation(
913 6
        SimpleXMLElement $orderByElement
914
    ) : Annotation\OrderBy {
915 6
        $orderByAnnotation = new Annotation\OrderBy();
916
        $orderBy           = [];
917
918 27
        foreach ($orderByElement->{'order-by-field'} as $orderByField) {
919
            $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
920
                ? (string) $orderByField['direction']
921 27
                : Criteria::ASC;
922
        }
923 27
924
        $orderByAnnotation->value = $orderBy;
925 27
926
        return $orderByAnnotation;
927
    }
928 3
929
    private function convertGeneratorElementToGeneratedValueAnnotation(
930
        SimpleXMLElement $generatorElement
931 3
    ) : Annotation\GeneratedValue {
932
        $generatedValueAnnotation = new Annotation\GeneratedValue();
933 3
934 3
        $generatedValueAnnotation->strategy = (string) ($generatorElement['strategy'] ?? 'AUTO');
935
936 3
        return $generatedValueAnnotation;
937
    }
938
939 2
    private function convertSequenceGeneratorElementToSequenceGeneratorAnnotation(
940
        SimpleXMLElement $sequenceGeneratorElement
941
    ) : Annotation\SequenceGenerator {
942 2
        $sequenceGeneratorAnnotation = new Annotation\SequenceGenerator();
943
944 2
        $sequenceGeneratorAnnotation->sequenceName   = (string) ($sequenceGeneratorElement['sequence-name'] ?? null);
945 2
        $sequenceGeneratorAnnotation->allocationSize = (int) ($sequenceGeneratorElement['allocation-size'] ?? 1);
946
947 2
        return $sequenceGeneratorAnnotation;
948
    }
949
950
    private function convertCustomIdGeneratorElementToCustomIdGeneratorAnnotation(
951
        SimpleXMLElement $customIdGeneratorElement
952
    ) : Annotation\CustomIdGenerator {
953
        $customIdGeneratorAnnotation = new Annotation\CustomIdGenerator();
954
955
        $customIdGeneratorAnnotation->class     = (string) $customIdGeneratorElement['class'];
956
        $customIdGeneratorAnnotation->arguments = [];
957 10
958
        return $customIdGeneratorAnnotation;
959 10
    }
960
961
    /**
962 10
     * Gathers a list of cascade options found in the given cascade element.
963
     *
964
     * @param SimpleXMLElement $cascadeElement The cascade element.
965
     *
966
     * @return string[] The list of cascade options.
967
     */
968 10
    private function getCascadeMappings(SimpleXMLElement $cascadeElement) : array
969
    {
970
        $cascades = [];
971 10
972
        /** @var SimpleXMLElement $action */
973
        foreach ($cascadeElement->children() as $action) {
974
            // According to the JPA specifications, XML uses "cascade-persist"
975
            // instead of "persist". Here, both variations are supported
976
            // because Annotation use "persist" and we want to make sure that
977
            // this driver doesn't need to know anything about the supported
978
            // cascading actions
979 9
            $cascades[] = str_replace('cascade-', '', $action->getName());
980
        }
981 9
982
        return $cascades;
983 9
    }
984
985
    /**
986
     * @param mixed $element
987
     *
988
     * @return bool
989
     */
990
    private function evaluateBoolean($element)
991
    {
992
        $flag = (string) $element;
993 4
994
        return $flag === 'true' || $flag === '1';
995 4
    }
996
997
    /**
998 4
     * Parses (nested) option elements.
999 4
     *
1000 3
     * @param SimpleXMLElement $options The XML element.
1001
     *
1002 4
     * @return mixed[] The options array.
1003
     */
1004
    private function parseOptions(SimpleXMLElement $options) : array
1005 4
    {
1006
        $array = [];
1007 4
1008 4
        /** @var SimpleXMLElement $option */
1009
        foreach ($options as $option) {
1010 4
            if ($option->count()) {
1011 3
                $value = $this->parseOptions($option->children());
1012 4
            } else {
1013
                $value = (string) $option;
1014
            }
1015
1016
            $attributes = $option->attributes();
1017
1018 4
            if (isset($attributes->name)) {
1019
                $nameAttribute = (string) $attributes->name;
1020
1021
                $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true)
1022
                    ? $this->evaluateBoolean($value)
1023
                    : $value;
1024
            } else {
1025
                $array[] = $value;
1026
            }
1027
        }
1028
1029
        return $array;
1030
    }
1031
}
1032