Failed Conditions
Pull Request — master (#7210)
by Michael
12:25
created

XmlDriver   F

Complexity

Total Complexity 167

Size/Duplication

Total Lines 887
Duplicated Lines 0 %

Test Coverage

Coverage 86.42%

Importance

Changes 0
Metric Value
dl 0
loc 887
ccs 369
cts 427
cp 0.8642
rs 1.263
c 0
b 0
f 0
wmc 167

10 Methods

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

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