Failed Conditions
Push — master ( e747f7...5b15a6 )
by Guilherme
19:57
created

XmlDriver   F

Complexity

Total Complexity 169

Size/Duplication

Total Lines 895
Duplicated Lines 0 %

Test Coverage

Coverage 87.07%

Importance

Changes 0
Metric Value
eloc 440
dl 0
loc 895
ccs 377
cts 433
cp 0.8707
rs 2
c 0
b 0
f 0
wmc 169

10 Methods

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

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