Failed Conditions
Pull Request — master (#7085)
by Guilherme
14:34
created

XmlDriver::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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
use function array_filter;
12
use function class_exists;
13
use function constant;
14
use function explode;
15
use function file_get_contents;
16
use function get_class;
17
use function in_array;
18
use function simplexml_load_string;
19
use function sprintf;
20
use function str_replace;
21
use function strtolower;
22
use function strtoupper;
23
24
/**
25
 * XmlDriver is a metadata driver that enables mapping through XML files.
26
 */
27
class XmlDriver extends FileDriver
28
{
29
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
30
31
    /**
32
     * {@inheritDoc}
33
     */
34 42
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
35
    {
36 42
        parent::__construct($locator, $fileExtension);
37 42
    }
38
39
    /**
40
     * {@inheritDoc}
41
     */
42 37
    public function loadMetadataForClass(
43
        string $className,
44
        Mapping\ClassMetadata $metadata,
45
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
46
    ) {
47
        /* @var \SimpleXMLElement $xmlRoot */
48 37
        $xmlRoot = $this->getElement($className);
49
50 35
        if ($xmlRoot->getName() === 'entity') {
51 35
            if (isset($xmlRoot['repository-class'])) {
52
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
53
            }
54
55 35
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
56 35
                $metadata->asReadOnly();
57
            }
58 5
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
59 5
            if (isset($xmlRoot['repository-class'])) {
60 1
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
61
            }
62
63 5
            $metadata->isMappedSuperclass = true;
64
        } elseif ($xmlRoot->getName() === 'embeddable') {
65
            $metadata->isEmbeddedClass = true;
66
        } else {
67
            throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
68
        }
69
70
        // Process table information
71 35
        $parent = $metadata->getParent();
72
73 35
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
74 2
            $metadata->setTable($parent->table);
75
        } else {
76 35
            $namingStrategy = $metadataBuildingContext->getNamingStrategy();
77 35
            $tableMetadata  = new Mapping\TableMetadata();
78
79 35
            $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
80
81
            // Evaluate <entity...> attributes
82 35
            if (isset($xmlRoot['table'])) {
83 15
                $tableMetadata->setName((string) $xmlRoot['table']);
84
            }
85
86 35
            if (isset($xmlRoot['schema'])) {
87 2
                $tableMetadata->setSchema((string) $xmlRoot['schema']);
88
            }
89
90 35
            if (isset($xmlRoot->options)) {
91 3
                $options = $this->parseOptions($xmlRoot->options->children());
92
93 3
                foreach ($options as $optionName => $optionValue) {
94 3
                    $tableMetadata->addOption($optionName, $optionValue);
95
                }
96
            }
97
98
            // Evaluate <indexes...>
99 35
            if (isset($xmlRoot->indexes)) {
100 4
                foreach ($xmlRoot->indexes->index as $indexXml) {
101 4
                    $indexName = isset($indexXml['name']) ? (string) $indexXml['name'] : null;
102 4
                    $columns   = explode(',', (string) $indexXml['columns']);
103 4
                    $isUnique  = isset($indexXml['unique']) && $indexXml['unique'];
104 4
                    $options   = isset($indexXml->options) ? $this->parseOptions($indexXml->options->children()) : [];
105 4
                    $flags     = isset($indexXml['flags']) ? explode(',', (string) $indexXml['flags']) : [];
106
107 4
                    $tableMetadata->addIndex([
108 4
                        'name'    => $indexName,
109 4
                        'columns' => $columns,
110 4
                        'unique'  => $isUnique,
111 4
                        'options' => $options,
112 4
                        'flags'   => $flags,
113
                    ]);
114
                }
115
            }
116
117
            // Evaluate <unique-constraints..>
118
119 35
            if (isset($xmlRoot->{'unique-constraints'})) {
120 3
                foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
121 3
                    $indexName = isset($uniqueXml['name']) ? (string) $uniqueXml['name'] : null;
122 3
                    $columns   = explode(',', (string) $uniqueXml['columns']);
123 3
                    $options   = isset($uniqueXml->options) ? $this->parseOptions($uniqueXml->options->children()) : [];
124 3
                    $flags     = isset($uniqueXml['flags']) ? explode(',', (string) $uniqueXml['flags']) : [];
125
126 3
                    $tableMetadata->addUniqueConstraint([
127 3
                        'name'    => $indexName,
128 3
                        'columns' => $columns,
129 3
                        'options' => $options,
130 3
                        'flags'   => $flags,
131
                    ]);
132
                }
133
            }
134
135 35
            $metadata->setTable($tableMetadata);
136
        }
137
138
        // Evaluate second level cache
139 35
        if (isset($xmlRoot->cache)) {
140 2
            $cache = $this->convertCacheElementToCacheMetadata($xmlRoot->cache, $metadata);
141
142 2
            $metadata->setCache($cache);
143
        }
144
145
        // Evaluate native named queries
146 35
        if (isset($xmlRoot->{'named-native-queries'})) {
147 3
            foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) {
148 3
                $metadata->addNamedNativeQuery(
149 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

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

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