Failed Conditions
Pull Request — master (#6735)
by Matthias
09:54
created

XmlDriver   F

Complexity

Total Complexity 167

Size/Duplication

Total Lines 885
Duplicated Lines 0 %

Test Coverage

Coverage 86.18%

Importance

Changes 0
Metric Value
eloc 433
dl 0
loc 885
ccs 368
cts 427
cp 0.8618
rs 2
c 0
b 0
f 0
wmc 167

10 Methods

Rating   Name   Duplication   Size   Complexity  
A evaluateBoolean() 0 5 2
F convertFieldElementToFieldMetadata() 0 45 11
A convertJoinColumnElementToJoinColumnMetadata() 0 28 6
F loadMetadataForClass() 0 634 129
A getMethodCallbacks() 0 15 1
B loadMappingFile() 0 24 7
A getCascadeMappings() 0 15 2
A __construct() 0 3 1
A convertCacheElementToCacheMetadata() 0 14 3
A parseOptions() 0 25 5

How to fix   Complexity   

Complex Class

Complex classes like XmlDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XmlDriver, and based on these observations, apply Extract Interface, too.

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