Failed Conditions
Pull Request — master (#6743)
by Grégoire
18:17 queued 12:33
created

XmlDriver   F

Complexity

Total Complexity 184

Size/Duplication

Total Lines 948
Duplicated Lines 0 %

Test Coverage

Coverage 87.94%

Importance

Changes 0
Metric Value
dl 0
loc 948
ccs 401
cts 456
cp 0.8794
rs 1.263
c 0
b 0
f 0
wmc 184

10 Methods

Rating   Name   Duplication   Size   Complexity  
A evaluateBoolean() 0 5 2
F convertFieldElementToFieldMetadata() 0 46 11
B convertJoinColumnElementToJoinColumnMetadata() 0 28 6
F loadMetadataForClass() 0 695 146
A getMethodCallbacks() 0 15 1
C loadMappingFile() 0 24 7
A getCascadeMappings() 0 15 2
B parseOptions() 0 25 5
A __construct() 0 3 1
A convertCacheElementToCacheMetadata() 0 15 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\DBAL\Types\Type;
8
use Doctrine\ORM\Events;
9
use Doctrine\ORM\Mapping;
10
use SimpleXMLElement;
11
12
/**
13
 * XmlDriver is a metadata driver that enables mapping through XML files.
14
 */
15
class XmlDriver extends FileDriver
16
{
17
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
18
19
    /**
20
     * {@inheritDoc}
21
     */
22 37
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
23
    {
24 37
        parent::__construct($locator, $fileExtension);
25 37
    }
26
27
    /**
28
     * {@inheritDoc}
29
     */
30 32
    public function loadMetadataForClass(
31
        string $className,
32
        Mapping\ClassMetadata $metadata,
33
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
34
    ) {
35
        /* @var \SimpleXMLElement $xmlRoot */
36 32
        $xmlRoot = $this->getElement($className);
37
38 30
        if ($xmlRoot->getName() === 'entity') {
39 29
            if (isset($xmlRoot['repository-class'])) {
40
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
41
            }
42
43 29
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
44 29
                $metadata->asReadOnly();
45
            }
46 5
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
47 4
            if (isset($xmlRoot['repository-class'])) {
48 1
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
49
            }
50
51 4
            $metadata->isMappedSuperclass = true;
52 1
        } elseif ($xmlRoot->getName() === 'embeddable') {
53 1
            $metadata->isEmbeddedClass = true;
54
        } else {
55
            throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
56
        }
57
58
        // Process table information
59 30
        $parent = $metadata->getParent();
60
61 30
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
62 2
            $metadata->setTable($parent->table);
63
        } else {
64 30
            $namingStrategy = $metadataBuildingContext->getNamingStrategy();
65 30
            $tableMetadata  = new Mapping\TableMetadata();
66
67 30
            $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
68
69
            // Evaluate <entity...> attributes
70 30
            if (isset($xmlRoot['table'])) {
71 12
                $tableMetadata->setName((string) $xmlRoot['table']);
72
            }
73
74 30
            if (isset($xmlRoot['schema'])) {
75 2
                $tableMetadata->setSchema((string) $xmlRoot['schema']);
76
            }
77
78 30
            if (isset($xmlRoot->options)) {
79 4
                $options = $this->parseOptions($xmlRoot->options->children());
80
81 4
                foreach ($options as $optionName => $optionValue) {
82 4
                    $tableMetadata->addOption($optionName, $optionValue);
83
                }
84
            }
85
86
            // Evaluate <indexes...>
87 30
            if (isset($xmlRoot->indexes)) {
88 5
                foreach ($xmlRoot->indexes->index as $indexXml) {
89 5
                    $indexName = isset($indexXml['name']) ? (string) $indexXml['name'] : null;
90 5
                    $columns   = explode(',', (string) $indexXml['columns']);
91 5
                    $isUnique  = isset($indexXml['unique']) && $indexXml['unique'];
92 5
                    $options   = isset($indexXml->options) ? $this->parseOptions($indexXml->options->children()) : [];
93 5
                    $flags     = isset($indexXml['flags']) ? explode(',', (string) $indexXml['flags']) : [];
94
95 5
                    $tableMetadata->addIndex([
96 5
                        'name'    => $indexName,
97 5
                        'columns' => $columns,
98 5
                        'unique'  => $isUnique,
99 5
                        'options' => $options,
100 5
                        'flags'   => $flags,
101
                    ]);
102
                }
103
            }
104
105
            // Evaluate <unique-constraints..>
106
107 30
            if (isset($xmlRoot->{'unique-constraints'})) {
108 4
                foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
109 4
                    $indexName = isset($uniqueXml['name']) ? (string) $uniqueXml['name'] : null;
110 4
                    $columns   = explode(',', (string) $uniqueXml['columns']);
111 4
                    $options   = isset($uniqueXml->options) ? $this->parseOptions($uniqueXml->options->children()) : [];
112 4
                    $flags     = isset($uniqueXml['flags']) ? explode(',', (string) $uniqueXml['flags']) : [];
113
114 4
                    $tableMetadata->addUniqueConstraint([
115 4
                        'name'    => $indexName,
116 4
                        'columns' => $columns,
117 4
                        'options' => $options,
118 4
                        'flags'   => $flags,
119
                    ]);
120
                }
121
            }
122
123 30
            $metadata->setTable($tableMetadata);
124
        }
125
126
        // Evaluate second level cache
127 30
        if (isset($xmlRoot->cache)) {
128 1
            $cache = $this->convertCacheElementToCacheMetadata($xmlRoot->cache, $metadata);
129
130 1
            $metadata->setCache($cache);
131
        }
132
133
        // Evaluate named queries
134 30
        if (isset($xmlRoot->{'named-queries'})) {
135 5
            foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) {
136 5
                $metadata->addNamedQuery((string) $namedQueryElement['name'], (string) $namedQueryElement['query']);
137
            }
138
        }
139
140
        // Evaluate native named queries
141 30
        if (isset($xmlRoot->{'named-native-queries'})) {
142 3
            foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) {
143 3
                $metadata->addNamedNativeQuery(
144 3
                    isset($nativeQueryElement['name']) ? (string) $nativeQueryElement['name'] : null,
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? (string)$nat...yElement['name'] : null can also be of type null; however, parameter $name of Doctrine\ORM\Mapping\Cla...::addNamedNativeQuery() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

144
                    /** @scrutinizer ignore-type */ isset($nativeQueryElement['name']) ? (string) $nativeQueryElement['name'] : null,
Loading history...
145 3
                    isset($nativeQueryElement->query) ? (string) $nativeQueryElement->query : null,
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? (string)$nat...ryElement->query : null can also be of type null; however, parameter $query of Doctrine\ORM\Mapping\Cla...::addNamedNativeQuery() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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