Failed Conditions
Pull Request — master (#6959)
by Matthew
12:20
created

XmlDriver::getCascadeMappings()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 15
ccs 5
cts 5
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
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 39
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
23
    {
24 39
        parent::__construct($locator, $fileExtension);
25 39
    }
26
27
    /**
28
     * {@inheritDoc}
29
     */
30 34
    public function loadMetadataForClass(
31
        string $className,
32
        Mapping\ClassMetadata $metadata,
33
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
34
    ) {
35
        /* @var \SimpleXMLElement $xmlRoot */
36 34
        $xmlRoot = $this->getElement($className);
37
38 32
        if ($xmlRoot->getName() === 'entity') {
39 31
            if (isset($xmlRoot['repository-class'])) {
40
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
41
            }
42
43 31
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
44 31
                $metadata->asReadOnly();
45
            }
46 6
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
47 5
            if (isset($xmlRoot['repository-class'])) {
48 1
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
49
            }
50
51 5
            $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 32
        $parent = $metadata->getParent();
60
61 32
        if ($parent instanceof Mapping\ClassMetadata
62 32
            && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE
63
        ) {
64 2
            $metadata->setTable($parent->table);
65
        } else {
66 32
            $namingStrategy = $metadataBuildingContext->getNamingStrategy();
67 32
            $tableMetadata  = new Mapping\TableMetadata();
68
69 32
            $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
70
71
            // Evaluate <entity...> attributes
72 32
            if (isset($xmlRoot['table'])) {
73 12
                $tableMetadata->setName((string) $xmlRoot['table']);
74
            }
75
76 32
            if (isset($xmlRoot['schema'])) {
77 2
                $tableMetadata->setSchema((string) $xmlRoot['schema']);
78
            }
79
80 32
            if (isset($xmlRoot->options)) {
81 4
                $options = $this->parseOptions($xmlRoot->options->children());
82
83 4
                foreach ($options as $optionName => $optionValue) {
84 4
                    $tableMetadata->addOption($optionName, $optionValue);
85
                }
86
            }
87
88
            // Evaluate <indexes...>
89 32
            if (isset($xmlRoot->indexes)) {
90 5
                foreach ($xmlRoot->indexes->index as $indexXml) {
91 5
                    $indexName = isset($indexXml['name']) ? (string) $indexXml['name'] : null;
92 5
                    $columns   = explode(',', (string) $indexXml['columns']);
93 5
                    $isUnique  = isset($indexXml['unique']) && $indexXml['unique'];
94 5
                    $options   = isset($indexXml->options) ? $this->parseOptions($indexXml->options->children()) : [];
95 5
                    $flags     = isset($indexXml['flags']) ? explode(',', (string) $indexXml['flags']) : [];
96
97 5
                    $tableMetadata->addIndex([
98 5
                        'name'    => $indexName,
99 5
                        'columns' => $columns,
100 5
                        'unique'  => $isUnique,
101 5
                        'options' => $options,
102 5
                        'flags'   => $flags,
103
                    ]);
104
                }
105
            }
106
107
            // Evaluate <unique-constraints..>
108
109 32
            if (isset($xmlRoot->{'unique-constraints'})) {
110 4
                foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
111 4
                    $indexName = isset($uniqueXml['name']) ? (string) $uniqueXml['name'] : null;
112 4
                    $columns   = explode(',', (string) $uniqueXml['columns']);
113 4
                    $options   = isset($uniqueXml->options) ? $this->parseOptions($uniqueXml->options->children()) : [];
114 4
                    $flags     = isset($uniqueXml['flags']) ? explode(',', (string) $uniqueXml['flags']) : [];
115
116 4
                    $tableMetadata->addUniqueConstraint([
117 4
                        'name'    => $indexName,
118 4
                        'columns' => $columns,
119 4
                        'options' => $options,
120 4
                        'flags'   => $flags,
121
                    ]);
122
                }
123
            }
124
125 32
            $metadata->setTable($tableMetadata);
126
        }
127
128
        // Evaluate second level cache
129 32
        if (isset($xmlRoot->cache)) {
130 2
            $cache = $this->convertCacheElementToCacheMetadata($xmlRoot->cache, $metadata);
131
132 2
            $metadata->setCache($cache);
133
        }
134
135
        // Evaluate named queries
136 32
        if (isset($xmlRoot->{'named-queries'})) {
137 5
            foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) {
138 5
                $metadata->addNamedQuery((string) $namedQueryElement['name'], (string) $namedQueryElement['query']);
139
            }
140
        }
141
142
        // Evaluate native named queries
143 32
        if (isset($xmlRoot->{'named-native-queries'})) {
144 3
            foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) {
145 3
                $metadata->addNamedNativeQuery(
146 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

146
                    /** @scrutinizer ignore-type */ isset($nativeQueryElement['name']) ? (string) $nativeQueryElement['name'] : null,
Loading history...
147 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

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