Failed Conditions
Push — master ( f185a6...047620 )
by Guilherme
09:26
created

convertDiscriminiatorColumnElementToDiscriminatorColumnAnnotation()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
nc 4
nop 1
dl 0
loc 17
ccs 9
cts 9
cp 1
crap 3
rs 10
c 1
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\DBAL\DBALException;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Annotation;
11
use Doctrine\ORM\Events;
12
use Doctrine\ORM\Mapping;
13
use Doctrine\ORM\Mapping\Builder;
14
use InvalidArgumentException;
15
use SimpleXMLElement;
16
use function class_exists;
17
use function constant;
18
use function explode;
19
use function file_get_contents;
20
use function get_class;
21
use function in_array;
22
use function simplexml_load_string;
23
use function sprintf;
24
use function str_replace;
25
use function strtoupper;
26
use function var_export;
27
28
/**
29
 * XmlDriver is a metadata driver that enables mapping through XML files.
30
 */
31
class XmlDriver extends FileDriver
32
{
33
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
34
35
    /**
36
     * {@inheritDoc}
37
     */
38 41
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
39
    {
40 41
        parent::__construct($locator, $fileExtension);
41 41
    }
42
43
    /**
44
     * {@inheritDoc}
45
     *
46
     * @throws DBALException
47
     */
48 36
    public function loadMetadataForClass(
49
        string $className,
50
        ?Mapping\ComponentMetadata $parent,
51
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
52
    ) : Mapping\ComponentMetadata {
53 36
        $metadata = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
54
55
        /** @var SimpleXMLElement $xmlRoot */
56 36
        $xmlRoot = $this->getElement($className);
57
58 34
        if ($xmlRoot->getName() === 'entity') {
59 34
            if (isset($xmlRoot['repository-class'])) {
60
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
61
            }
62
63 34
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
64 34
                $metadata->asReadOnly();
65
            }
66 5
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
67 5
            if (isset($xmlRoot['repository-class'])) {
68 1
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
69
            }
70
71 5
            $metadata->isMappedSuperclass = true;
72
        } elseif ($xmlRoot->getName() === 'embeddable') {
73
            $metadata->isEmbeddedClass = true;
74
        } else {
75
            throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
76
        }
77
78
        // Process table information
79 34
        $parent = $metadata->getParent();
80
81 34
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
82
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
83
            do {
84 2
                if (! $parent->isMappedSuperclass) {
85 2
                    $metadata->setTable($parent->table);
86
87 2
                    break;
88
                }
89
90
                $parent = $parent->getParent();
91 2
            } while ($parent !== null);
92
        } else {
93 34
            $tableAnnotation = new Annotation\Table();
94
95
            // Evaluate <entity...> attributes
96 34
            if (isset($xmlRoot['table'])) {
97 12
                $tableAnnotation->name = (string) $xmlRoot['table'];
98
            }
99
100 34
            if (isset($xmlRoot['schema'])) {
101 2
                $tableAnnotation->schema = (string) $xmlRoot['schema'];
102
            }
103
104
            // Evaluate <indexes...>
105 34
            if (isset($xmlRoot->indexes)) {
106 4
                $tableAnnotation->indexes = $this->parseIndexes($xmlRoot->indexes->children());
107
            }
108
109
            // Evaluate <unique-constraints..>
110 34
            if (isset($xmlRoot->{'unique-constraints'})) {
111 3
                $tableAnnotation->uniqueConstraints = $this->parseUniqueConstraints($xmlRoot->{'unique-constraints'}->children());
112
            }
113
114 34
            if (isset($xmlRoot->options)) {
115 3
                $tableAnnotation->options = $this->parseOptions($xmlRoot->options->children());
116
            }
117
118 34
            $tableBuilder = new Builder\TableMetadataBuilder($metadataBuildingContext);
119
120
            $tableBuilder
121 34
                ->withEntityClassMetadata($metadata)
122 34
                ->withTableAnnotation($tableAnnotation);
123
124 34
            $metadata->setTable($tableBuilder->build());
125
        }
126
127
        // Evaluate second level cache
128 34
        if (isset($xmlRoot->cache)) {
129 2
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
130
131
            $cacheBuilder
132 2
                ->withComponentMetadata($metadata)
133 2
                ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($xmlRoot->cache));
134
135 2
            $metadata->setCache($cacheBuilder->build());
136
        }
137
138 34
        if (isset($xmlRoot['inheritance-type'])) {
139 10
            $inheritanceType = strtoupper((string) $xmlRoot['inheritance-type']);
140
141 10
            $metadata->setInheritanceType(
142 10
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceType))
143
            );
144
145 10
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
146 10
                $discriminatorColumnBuilder = new Builder\DiscriminatorColumnMetadataBuilder($metadataBuildingContext);
147
148
                $discriminatorColumnBuilder
149 10
                    ->withComponentMetadata($metadata)
150 10
                    ->withDiscriminatorColumnAnnotation(
151 10
                        isset($xmlRoot->{'discriminator-column'})
152 8
                            ? $this->convertDiscriminiatorColumnElementToDiscriminatorColumnAnnotation($xmlRoot->{'discriminator-column'})
153 10
                            : null
154
                    );
155
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
156
157 10
                $metadata->setDiscriminatorColumn($discriminatorColumnBuilder->build());
158
159
                // Evaluate <discriminator-map...>
160 10
                if (isset($xmlRoot->{'discriminator-map'})) {
161 10
                    $map = [];
162
163 10
                    foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
164 10
                        $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
165
                    }
166
167 10
                    $metadata->setDiscriminatorMap($map);
168
                }
169
            }
170
        }
171
172
        // Evaluate <change-tracking-policy...>
173 34
        if (isset($xmlRoot['change-tracking-policy'])) {
174
            $changeTrackingPolicy = strtoupper((string) $xmlRoot['change-tracking-policy']);
175
176
            $metadata->setChangeTrackingPolicy(
177
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingPolicy))
178
            );
179
        }
180
181
        // Evaluate <field ...> mappings
182 34
        if (isset($xmlRoot->field)) {
183 20
            foreach ($xmlRoot->field as $fieldElement) {
184 20
                $fieldName     = (string) $fieldElement['name'];
185 20
                $fieldMetadata = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, $metadata, $metadataBuildingContext);
186
187 20
                $metadata->addProperty($fieldMetadata);
188
            }
189
        }
190
191 34
        if (isset($xmlRoot->embedded)) {
192
            foreach ($xmlRoot->embedded as $embeddedMapping) {
193
                $columnPrefix = isset($embeddedMapping['column-prefix'])
194
                    ? (string) $embeddedMapping['column-prefix']
195
                    : null;
196
197
                $useColumnPrefix = isset($embeddedMapping['use-column-prefix'])
198
                    ? $this->evaluateBoolean($embeddedMapping['use-column-prefix'])
199
                    : true;
200
201
                $mapping = [
202
                    'fieldName'    => (string) $embeddedMapping['name'],
203
                    'class'        => (string) $embeddedMapping['class'],
204
                    'columnPrefix' => $useColumnPrefix ? $columnPrefix : false,
205
                ];
206
207
                $metadata->mapEmbedded($mapping);
208
            }
209
        }
210
211
        // Evaluate <id ...> mappings
212 34
        $associationIds = [];
213
214 34
        foreach ($xmlRoot->id as $idElement) {
215 29
            $fieldName = (string) $idElement['name'];
216
217 29
            if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) {
218 2
                $associationIds[$fieldName] = true;
219
220 2
                continue;
221
            }
222
223 28
            $fieldMetadata = $this->convertFieldElementToFieldMetadata($idElement, $fieldName, $metadata, $metadataBuildingContext);
224
225 28
            $fieldMetadata->setPrimaryKey(true);
226
227
            // Prevent PK and version on same field
228 28
            if ($fieldMetadata->isVersioned()) {
229
                throw Mapping\MappingException::cannotVersionIdField($className, $fieldName);
230
            }
231
232 28
            if (isset($idElement->generator)) {
233 27
                $strategy = (string) ($idElement->generator['strategy'] ?? 'AUTO');
234
235 27
                $idGeneratorType = constant(sprintf('%s::%s', Mapping\GeneratorType::class, strtoupper($strategy)));
236
237 27
                if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
238 20
                    $idGeneratorDefinition = [];
239
240
                    // Check for SequenceGenerator/TableGenerator definition
241 20
                    if (isset($idElement->{'sequence-generator'})) {
242 3
                        $seqGenerator          = $idElement->{'sequence-generator'};
243
                        $idGeneratorDefinition = [
244 3
                            'sequenceName'   => (string) $seqGenerator['sequence-name'],
245 3
                            'allocationSize' => (string) $seqGenerator['allocation-size'],
246
                        ];
247 17
                    } elseif (isset($idElement->{'custom-id-generator'})) {
248 2
                        $customGenerator = $idElement->{'custom-id-generator'};
249
250
                        $idGeneratorDefinition = [
251 2
                            'class'     => (string) $customGenerator['class'],
252
                            'arguments' => [],
253
                        ];
254
255 2
                        if (! isset($idGeneratorDefinition['class'])) {
256
                            throw new Mapping\MappingException(
257
                                sprintf('Cannot instantiate custom generator, no class has been defined')
258
                            );
259
                        }
260
261 2
                        if (! class_exists($idGeneratorDefinition['class'])) {
262
                            throw new Mapping\MappingException(
263 2
                                sprintf('Cannot instantiate custom generator : %s', var_export($idGeneratorDefinition, true))
264
                            );
265
                        }
266 15
                    } elseif (isset($idElement->{'table-generator'})) {
267
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
268
                    }
269
270 20
                    $fieldMetadata->setValueGenerator(
271 20
                        new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition)
272
                    );
273
                }
274
            }
275
276 28
            $metadata->addProperty($fieldMetadata);
277
        }
278
279
        // Evaluate <one-to-one ...> mappings
280 34
        if (isset($xmlRoot->{'one-to-one'})) {
281 7
            foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
282 7
                $association  = new Mapping\OneToOneAssociationMetadata((string) $oneToOneElement['field']);
283 7
                $targetEntity = (string) $oneToOneElement['target-entity'];
284
285 7
                $association->setTargetEntity($targetEntity);
286
287 7
                if (isset($associationIds[$association->getName()])) {
288
                    $association->setPrimaryKey(true);
289
                }
290
291 7
                if (isset($oneToOneElement['fetch'])) {
292 3
                    $association->setFetchMode(
293 3
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToOneElement['fetch']))
294
                    );
295
                }
296
297 7
                if (isset($oneToOneElement['mapped-by'])) {
298 3
                    $association->setMappedBy((string) $oneToOneElement['mapped-by']);
299 3
                    $association->setOwningSide(false);
300
                } else {
301 5
                    if (isset($oneToOneElement['inversed-by'])) {
302 4
                        $association->setInversedBy((string) $oneToOneElement['inversed-by']);
303
                    }
304
305 5
                    $joinColumns = [];
306
307 5
                    if (isset($oneToOneElement->{'join-column'})) {
308 5
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($oneToOneElement->{'join-column'});
309
                    } elseif (isset($oneToOneElement->{'join-columns'})) {
310
                        foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
311
                            $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
312
                        }
313
                    }
314
315 5
                    $association->setJoinColumns($joinColumns);
316
                }
317
318 7
                if (isset($oneToOneElement->cascade)) {
319 7
                    $association->setCascade($this->getCascadeMappings($oneToOneElement->cascade));
320
                }
321
322 7
                if (isset($oneToOneElement['orphan-removal'])) {
323
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToOneElement['orphan-removal']));
324
                }
325
326
                // Evaluate second level cache
327 7
                if (isset($oneToOneElement->cache)) {
328
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
329
330
                    $cacheBuilder
331
                        ->withComponentMetadata($metadata)
332
                        ->withFieldName($association->getName())
333
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($oneToOneElement->cache));
334
335
                    $association->setCache($cacheBuilder->build());
336
                }
337
338 7
                $metadata->addProperty($association);
339
            }
340
        }
341
342
        // Evaluate <one-to-many ...> mappings
343 34
        if (isset($xmlRoot->{'one-to-many'})) {
344 9
            foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
345 9
                $association  = new Mapping\OneToManyAssociationMetadata((string) $oneToManyElement['field']);
346 9
                $targetEntity = (string) $oneToManyElement['target-entity'];
347
348 9
                $association->setTargetEntity($targetEntity);
349 9
                $association->setOwningSide(false);
350 9
                $association->setMappedBy((string) $oneToManyElement['mapped-by']);
351
352 9
                if (isset($associationIds[$association->getName()])) {
353
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
354
                }
355
356 9
                if (isset($oneToManyElement['fetch'])) {
357
                    $association->setFetchMode(
358
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToManyElement['fetch']))
359
                    );
360
                }
361
362 9
                if (isset($oneToManyElement->cascade)) {
363 6
                    $association->setCascade($this->getCascadeMappings($oneToManyElement->cascade));
364
                }
365
366 9
                if (isset($oneToManyElement['orphan-removal'])) {
367 3
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToManyElement['orphan-removal']));
368
                }
369
370 9
                if (isset($oneToManyElement->{'order-by'})) {
371 5
                    $orderBy = [];
372
373 5
                    foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
374 5
                        $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
375 4
                            ? (string) $orderByField['direction']
376 1
                            : Criteria::ASC;
377
                    }
378
379 5
                    $association->setOrderBy($orderBy);
380
                }
381
382 9
                if (isset($oneToManyElement['index-by'])) {
383 3
                    $association->setIndexedBy((string) $oneToManyElement['index-by']);
384 6
                } elseif (isset($oneToManyElement->{'index-by'})) {
385
                    throw new InvalidArgumentException('<index-by /> is not a valid tag');
386
                }
387
388
                // Evaluate second level cache
389 9
                if (isset($oneToManyElement->cache)) {
390 1
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
391
392
                    $cacheBuilder
393 1
                        ->withComponentMetadata($metadata)
394 1
                        ->withFieldName($association->getName())
395 1
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($oneToManyElement->cache));
396
397 1
                    $association->setCache($cacheBuilder->build());
398
                }
399
400 9
                $metadata->addProperty($association);
401
            }
402
        }
403
404
        // Evaluate <many-to-one ...> mappings
405 34
        if (isset($xmlRoot->{'many-to-one'})) {
406 8
            foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
407 8
                $association  = new Mapping\ManyToOneAssociationMetadata((string) $manyToOneElement['field']);
408 8
                $targetEntity = (string) $manyToOneElement['target-entity'];
409
410 8
                $association->setTargetEntity($targetEntity);
411
412 8
                if (isset($associationIds[$association->getName()])) {
413 2
                    $association->setPrimaryKey(true);
414
                }
415
416 8
                if (isset($manyToOneElement['fetch'])) {
417
                    $association->setFetchMode(
418
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $manyToOneElement['fetch'])
419
                    );
420
                }
421
422 8
                if (isset($manyToOneElement['inversed-by'])) {
423 2
                    $association->setInversedBy((string) $manyToOneElement['inversed-by']);
424
                }
425
426 8
                $joinColumns = [];
427
428 8
                if (isset($manyToOneElement->{'join-column'})) {
429 7
                    $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($manyToOneElement->{'join-column'});
430 1
                } elseif (isset($manyToOneElement->{'join-columns'})) {
431 1
                    foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
432 1
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
433
                    }
434
                }
435
436 8
                $association->setJoinColumns($joinColumns);
437
438 8
                if (isset($manyToOneElement->cascade)) {
439 4
                    $association->setCascade($this->getCascadeMappings($manyToOneElement->cascade));
440
                }
441
442
                // Evaluate second level cache
443 8
                if (isset($manyToOneElement->cache)) {
444 1
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
445
446
                    $cacheBuilder
447 1
                        ->withComponentMetadata($metadata)
448 1
                        ->withFieldName($association->getName())
449 1
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($manyToOneElement->cache));
450
451 1
                    $association->setCache($cacheBuilder->build());
452
                }
453
454 8
                $metadata->addProperty($association);
455
            }
456
        }
457
458
        // Evaluate <many-to-many ...> mappings
459 33
        if (isset($xmlRoot->{'many-to-many'})) {
460 14
            foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
461 14
                $association  = new Mapping\ManyToManyAssociationMetadata((string) $manyToManyElement['field']);
462 14
                $targetEntity = (string) $manyToManyElement['target-entity'];
463
464 14
                $association->setTargetEntity($targetEntity);
465
466 14
                if (isset($associationIds[$association->getName()])) {
467
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
468
                }
469
470 14
                if (isset($manyToManyElement['fetch'])) {
471 4
                    $association->setFetchMode(
472 4
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $manyToManyElement['fetch']))
473
                    );
474
                }
475
476 14
                if (isset($manyToManyElement['orphan-removal'])) {
477
                    $association->setOrphanRemoval($this->evaluateBoolean($manyToManyElement['orphan-removal']));
478
                }
479
480 14
                if (isset($manyToManyElement['mapped-by'])) {
481 5
                    $association->setMappedBy((string) $manyToManyElement['mapped-by']);
482 5
                    $association->setOwningSide(false);
483 10
                } elseif (isset($manyToManyElement->{'join-table'})) {
484 8
                    if (isset($manyToManyElement['inversed-by'])) {
485 5
                        $association->setInversedBy((string) $manyToManyElement['inversed-by']);
486
                    }
487
488 8
                    $joinTableElement = $manyToManyElement->{'join-table'};
489 8
                    $joinTable        = new Mapping\JoinTableMetadata();
490
491 8
                    if (isset($joinTableElement['name'])) {
492 8
                        $joinTable->setName((string) $joinTableElement['name']);
493
                    }
494
495 8
                    if (isset($joinTableElement['schema'])) {
496
                        $joinTable->setSchema((string) $joinTableElement['schema']);
497
                    }
498
499 8
                    if (isset($joinTableElement->{'join-columns'})) {
500 8
                        foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
501 8
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
502
503 8
                            $joinTable->addJoinColumn($joinColumn);
504
                        }
505
                    }
506
507 8
                    if (isset($joinTableElement->{'inverse-join-columns'})) {
508 8
                        foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
509 8
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
510
511 8
                            $joinTable->addInverseJoinColumn($joinColumn);
512
                        }
513
                    }
514
515 8
                    $association->setJoinTable($joinTable);
516
                }
517
518 14
                if (isset($manyToManyElement->cascade)) {
519 8
                    $association->setCascade($this->getCascadeMappings($manyToManyElement->cascade));
520
                }
521
522 14
                if (isset($manyToManyElement->{'order-by'})) {
523 1
                    $orderBy = [];
524
525 1
                    foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
526 1
                        $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
527
                            ? (string) $orderByField['direction']
528 1
                            : Criteria::ASC;
529
                    }
530
531 1
                    $association->setOrderBy($orderBy);
532
                }
533
534 14
                if (isset($manyToManyElement['index-by'])) {
535
                    $association->setIndexedBy((string) $manyToManyElement['index-by']);
536 14
                } elseif (isset($manyToManyElement->{'index-by'})) {
537
                    throw new InvalidArgumentException('<index-by /> is not a valid tag');
538
                }
539
540
                // Evaluate second level cache
541 14
                if (isset($manyToManyElement->cache)) {
542
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
543
544
                    $cacheBuilder
545
                        ->withComponentMetadata($metadata)
546
                        ->withFieldName($association->getName())
547
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($manyToManyElement->cache));
548
549
                    $association->setCache($cacheBuilder->build());
550
                }
551
552 14
                $metadata->addProperty($association);
553
            }
554
        }
555
556
        // Evaluate association-overrides
557 33
        if (isset($xmlRoot->{'attribute-overrides'})) {
558 2
            foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) {
559 2
                $fieldName = (string) $overrideElement['name'];
560
561 2
                foreach ($overrideElement->field as $fieldElement) {
562 2
                    $fieldMetadata = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, $metadata, $metadataBuildingContext);
563
564 2
                    $metadata->setPropertyOverride($fieldMetadata);
565
                }
566
            }
567
        }
568
569
        // Evaluate association-overrides
570 33
        if (isset($xmlRoot->{'association-overrides'})) {
571 4
            foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) {
572 4
                $fieldName = (string) $overrideElement['name'];
573 4
                $property  = $metadata->getProperty($fieldName);
574
575 4
                if (! $property) {
576
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
577
                }
578
579 4
                $existingClass = get_class($property);
580 4
                $override      = new $existingClass($fieldName);
581
582
                // Check for join-columns
583 4
                if (isset($overrideElement->{'join-columns'})) {
584 2
                    $joinColumns = [];
585
586 2
                    foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
587 2
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
588
                    }
589
590 2
                    $override->setJoinColumns($joinColumns);
591
                }
592
593
                // Check for join-table
594 4
                if ($overrideElement->{'join-table'}) {
595 2
                    $joinTableElement = $overrideElement->{'join-table'};
596 2
                    $joinTable        = new Mapping\JoinTableMetadata();
597
598 2
                    if (isset($joinTableElement['name'])) {
599 2
                        $joinTable->setName((string) $joinTableElement['name']);
600
                    }
601
602 2
                    if (isset($joinTableElement['schema'])) {
603
                        $joinTable->setSchema((string) $joinTableElement['schema']);
604
                    }
605
606 2
                    if (isset($joinTableElement->{'join-columns'})) {
607 2
                        foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
608 2
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
609
610 2
                            $joinTable->addJoinColumn($joinColumn);
611
                        }
612
                    }
613
614 2
                    if (isset($joinTableElement->{'inverse-join-columns'})) {
615 2
                        foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
616 2
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
617
618 2
                            $joinTable->addInverseJoinColumn($joinColumn);
619
                        }
620
                    }
621
622 2
                    $override->setJoinTable($joinTable);
623
                }
624
625
                // Check for inversed-by
626 4
                if (isset($overrideElement->{'inversed-by'})) {
627 1
                    $override->setInversedBy((string) $overrideElement->{'inversed-by'}['name']);
628
                }
629
630
                // Check for fetch
631 4
                if (isset($overrideElement['fetch'])) {
632 1
                    $override->setFetchMode(
633 1
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $overrideElement['fetch'])
634
                    );
635
                }
636
637 4
                $metadata->setPropertyOverride($override);
638
            }
639
        }
640
641
        // Evaluate <lifecycle-callbacks...>
642 33
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
643 3
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
644 3
                $eventName  = constant(Events::class . '::' . (string) $lifecycleCallback['type']);
645 3
                $methodName = (string) $lifecycleCallback['method'];
646
647 3
                $metadata->addLifecycleCallback($eventName, $methodName);
648
            }
649
        }
650
651
        // Evaluate entity listener
652 33
        if (isset($xmlRoot->{'entity-listeners'})) {
653 2
            foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) {
654 2
                $listenerClassName = (string) $listenerElement['class'];
655
656 2
                if (! class_exists($listenerClassName)) {
657
                    throw Mapping\MappingException::entityListenerClassNotFound(
658
                        $listenerClassName,
659
                        $metadata->getClassName()
660
                    );
661
                }
662
663 2
                foreach ($listenerElement as $callbackElement) {
664 2
                    $eventName  = (string) $callbackElement['type'];
665 2
                    $methodName = (string) $callbackElement['method'];
666
667 2
                    $metadata->addEntityListener($eventName, $listenerClassName, $methodName);
668
                }
669
            }
670
        }
671
672 33
        return $metadata;
673
    }
674
675
    /**
676
     * Parses (nested) index elements.
677
     *
678
     * @param SimpleXMLElement $indexes The XML element.
679
     *
680
     * @return Annotation\Index[] The indexes array.
681
     */
682 4
    private function parseIndexes(SimpleXMLElement $indexes) : array
683
    {
684 4
        $array = [];
685
686
        /** @var SimpleXMLElement $index */
687 4
        foreach ($indexes as $index) {
688 4
            $indexAnnotation = new Annotation\Index();
689
690 4
            $indexAnnotation->columns = explode(',', (string) $index['columns']);
691 4
            $indexAnnotation->options = isset($index->options) ? $this->parseOptions($index->options->children()) : [];
692 4
            $indexAnnotation->flags   = isset($index['flags']) ? explode(',', (string) $index['flags']) : [];
693
694 4
            if (isset($index['name'])) {
695 3
                $indexAnnotation->name = (string) $index['name'];
696
            }
697
698 4
            if (isset($index['unique'])) {
699
                $indexAnnotation->unique = $this->evaluateBoolean($index['unique']);
700
            }
701
702 4
            $array[] = $indexAnnotation;
703
        }
704
705 4
        return $array;
706
    }
707
708
    /**
709
     * Parses (nested) unique constraint elements.
710
     *
711
     * @param SimpleXMLElement $uniqueConstraints The XML element.
712
     *
713
     * @return Annotation\UniqueConstraint[] The unique constraints array.
714
     */
715 3
    private function parseUniqueConstraints(SimpleXMLElement $uniqueConstraints) : array
716
    {
717 3
        $array = [];
718
719
        /** @var SimpleXMLElement $uniqueConstraint */
720 3
        foreach ($uniqueConstraints as $uniqueConstraint) {
721 3
            $uniqueConstraintAnnotation = new Annotation\UniqueConstraint();
722
723 3
            $uniqueConstraintAnnotation->columns = explode(',', (string) $uniqueConstraint['columns']);
724 3
            $uniqueConstraintAnnotation->options = isset($uniqueConstraint->options) ? $this->parseOptions($uniqueConstraint->options->children()) : [];
725 3
            $uniqueConstraintAnnotation->flags   = isset($uniqueConstraint['flags']) ? explode(',', (string) $uniqueConstraint['flags']) : [];
726
727 3
            if (isset($uniqueConstraint['name'])) {
728 3
                $uniqueConstraintAnnotation->name = (string) $uniqueConstraint['name'];
729
            }
730
731 3
            $array[] = $uniqueConstraintAnnotation;
732
        }
733
734 3
        return $array;
735
    }
736
737
    /**
738
     * Parses (nested) option elements.
739
     *
740
     * @param SimpleXMLElement $options The XML element.
741
     *
742
     * @return mixed[] The options array.
743
     */
744 4
    private function parseOptions(SimpleXMLElement $options) : array
745
    {
746 4
        $array = [];
747
748
        /** @var SimpleXMLElement $option */
749 4
        foreach ($options as $option) {
750 4
            if ($option->count()) {
751 3
                $value = $this->parseOptions($option->children());
752
            } else {
753 4
                $value = (string) $option;
754
            }
755
756 4
            $attributes = $option->attributes();
757
758 4
            if (isset($attributes->name)) {
759 4
                $nameAttribute = (string) $attributes->name;
760
761 4
                $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true)
762 3
                    ? $this->evaluateBoolean($value)
763 4
                    : $value;
764
            } else {
765
                $array[] = $value;
766
            }
767
        }
768
769 4
        return $array;
770
    }
771
772
    /**
773
     * @throws Mapping\MappingException
774
     */
775 30
    private function convertFieldElementToFieldMetadata(
776
        SimpleXMLElement $fieldElement,
777
        string $fieldName,
778
        Mapping\ClassMetadata $metadata,
779
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
780
    ) : Mapping\FieldMetadata {
781 30
        $className     = $metadata->getClassName();
782 30
        $isVersioned   = isset($fieldElement['version']) && $fieldElement['version'];
783 30
        $fieldMetadata = new Mapping\FieldMetadata($fieldName);
784 30
        $fieldType     = isset($fieldElement['type']) ? (string) $fieldElement['type'] : 'string';
785 30
        $columnName    = isset($fieldElement['column'])
786 20
            ? (string) $fieldElement['column']
787 30
            : $metadataBuildingContext->getNamingStrategy()->propertyToColumnName($fieldName, $className);
788
789 30
        $fieldMetadata->setType(Type::getType($fieldType));
790 30
        $fieldMetadata->setVersioned($isVersioned);
791 30
        $fieldMetadata->setColumnName($columnName);
792
793 30
        if (isset($fieldElement['length'])) {
794 6
            $fieldMetadata->setLength((int) $fieldElement['length']);
795
        }
796
797 30
        if (isset($fieldElement['precision'])) {
798 1
            $fieldMetadata->setPrecision((int) $fieldElement['precision']);
799
        }
800
801 30
        if (isset($fieldElement['scale'])) {
802 1
            $fieldMetadata->setScale((int) $fieldElement['scale']);
803
        }
804
805 30
        if (isset($fieldElement['unique'])) {
806 7
            $fieldMetadata->setUnique($this->evaluateBoolean($fieldElement['unique']));
807
        }
808
809 30
        if (isset($fieldElement['nullable'])) {
810 7
            $fieldMetadata->setNullable($this->evaluateBoolean($fieldElement['nullable']));
811
        }
812
813 30
        if (isset($fieldElement['column-definition'])) {
814 4
            $fieldMetadata->setColumnDefinition((string) $fieldElement['column-definition']);
815
        }
816
817 30
        if (isset($fieldElement->options)) {
818 3
            $fieldMetadata->setOptions($this->parseOptions($fieldElement->options->children()));
819
        }
820
821
        // Prevent column duplication
822 30
        if ($metadata->checkPropertyDuplication($columnName)) {
823
            throw Mapping\MappingException::duplicateColumnName($className, $columnName);
824
        }
825
826 30
        return $fieldMetadata;
827
    }
828
829
    /**
830
     * Constructs a joinColumn mapping array based on the information
831
     * found in the given SimpleXMLElement.
832
     *
833
     * @param SimpleXMLElement $joinColumnElement The XML element.
834
     */
835 13
    private function convertJoinColumnElementToJoinColumnMetadata(SimpleXMLElement $joinColumnElement) : Mapping\JoinColumnMetadata
836
    {
837 13
        $joinColumnMetadata = new Mapping\JoinColumnMetadata();
838
839 13
        $joinColumnMetadata->setColumnName((string) $joinColumnElement['name']);
840 13
        $joinColumnMetadata->setReferencedColumnName((string) $joinColumnElement['referenced-column-name']);
841
842 13
        if (isset($joinColumnElement['column-definition'])) {
843 3
            $joinColumnMetadata->setColumnDefinition((string) $joinColumnElement['column-definition']);
844
        }
845
846 13
        if (isset($joinColumnElement['field-name'])) {
847
            $joinColumnMetadata->setAliasedName((string) $joinColumnElement['field-name']);
848
        }
849
850 13
        if (isset($joinColumnElement['nullable'])) {
851 4
            $joinColumnMetadata->setNullable($this->evaluateBoolean($joinColumnElement['nullable']));
852
        }
853
854 13
        if (isset($joinColumnElement['unique'])) {
855 3
            $joinColumnMetadata->setUnique($this->evaluateBoolean($joinColumnElement['unique']));
856
        }
857
858 13
        if (isset($joinColumnElement['on-delete'])) {
859 3
            $joinColumnMetadata->setOnDelete(strtoupper((string) $joinColumnElement['on-delete']));
860
        }
861
862 13
        return $joinColumnMetadata;
863
    }
864
865
    /**
866
     * Parse the given Cache as CacheMetadata
867
     */
868 2
    private function convertCacheElementToCacheAnnotation(SimpleXMLElement $cacheMapping) : Annotation\Cache
869
    {
870 2
        $cacheAnnotation = new Annotation\Cache();
871
872 2
        if (isset($cacheMapping['region'])) {
873
            $cacheAnnotation->region = (string) $cacheMapping['region'];
874
        }
875
876 2
        if (isset($cacheMapping['usage'])) {
877 2
            $cacheAnnotation->usage = strtoupper((string) $cacheMapping['usage']);
878
        }
879
880 2
        return $cacheAnnotation;
881
    }
882
883 8
    private function convertDiscriminiatorColumnElementToDiscriminatorColumnAnnotation(
884
        SimpleXMLElement $discriminatorColumnMapping
885
    ) : Annotation\DiscriminatorColumn {
886 8
        $discriminatorColumnAnnotation = new Annotation\DiscriminatorColumn();
887
888 8
        $discriminatorColumnAnnotation->type = (string) ($discriminatorColumnMapping['type'] ?? 'string');
889 8
        $discriminatorColumnAnnotation->name = (string) $discriminatorColumnMapping['name'];
890
891 8
        if (isset($discriminatorColumnMapping['column-definition'])) {
892 1
            $discriminatorColumnAnnotation->columnDefinition = (string) $discriminatorColumnMapping['column-definition'];
893
        }
894
895 8
        if (isset($discriminatorColumnMapping['length'])) {
896 3
            $discriminatorColumnAnnotation->length = (int) $discriminatorColumnMapping['length'];
897
        }
898
899 8
        return $discriminatorColumnAnnotation;
900
    }
901
902
    /**
903
     * Gathers a list of cascade options found in the given cascade element.
904
     *
905
     * @param SimpleXMLElement $cascadeElement The cascade element.
906
     *
907
     * @return string[] The list of cascade options.
908
     */
909 10
    private function getCascadeMappings(SimpleXMLElement $cascadeElement) : array
910
    {
911 10
        $cascades = [];
912
913
        /** @var SimpleXMLElement $action */
914 10
        foreach ($cascadeElement->children() as $action) {
915
            // According to the JPA specifications, XML uses "cascade-persist"
916
            // instead of "persist". Here, both variations are supported
917
            // because Annotation use "persist" and we want to make sure that
918
            // this driver doesn't need to know anything about the supported
919
            // cascading actions
920 10
            $cascades[] = str_replace('cascade-', '', $action->getName());
921
        }
922
923 10
        return $cascades;
924
    }
925
926
    /**
927
     * {@inheritDoc}
928
     */
929 36
    protected function loadMappingFile($file)
930
    {
931 36
        $result = [];
932
        // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577
933 36
        $xmlElement = simplexml_load_string(file_get_contents($file));
934
935 36
        if (isset($xmlElement->entity)) {
936 35
            foreach ($xmlElement->entity as $entityElement) {
937 35
                $entityName          = (string) $entityElement['name'];
938 35
                $result[$entityName] = $entityElement;
939
            }
940 6
        } elseif (isset($xmlElement->{'mapped-superclass'})) {
941 5
            foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) {
942 5
                $className          = (string) $mappedSuperClass['name'];
943 5
                $result[$className] = $mappedSuperClass;
944
            }
945 1
        } elseif (isset($xmlElement->embeddable)) {
946
            foreach ($xmlElement->embeddable as $embeddableElement) {
947
                $embeddableName          = (string) $embeddableElement['name'];
948
                $result[$embeddableName] = $embeddableElement;
949
            }
950
        }
951
952 36
        return $result;
953
    }
954
955
    /**
956
     * @param mixed $element
957
     *
958
     * @return bool
959
     */
960 9
    protected function evaluateBoolean($element)
961
    {
962 9
        $flag = (string) $element;
963
964 9
        return $flag === 'true' || $flag === '1';
965
    }
966
}
967