Failed Conditions
Pull Request — master (#6959)
by Matthew
12:08
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
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 30
            if (isset($xmlRoot['repository-class'])) {
40
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
41
            }
42
43 30
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
44 30
                $metadata->asReadOnly();
45
            }
46 5
        } 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
        } elseif ($xmlRoot->getName() === 'embeddable') {
53
            $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 instanceof Mapping\ClassMetadata
62 30
            && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE
63
        ) {
64 2
            $metadata->setTable($parent->table);
65
        } else {
66 30
            $namingStrategy = $metadataBuildingContext->getNamingStrategy();
67 30
            $tableMetadata  = new Mapping\TableMetadata();
68
69 30
            $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
70
71
            // Evaluate <entity...> attributes
72 30
            if (isset($xmlRoot['table'])) {
73 11
                $tableMetadata->setName((string) $xmlRoot['table']);
74
            }
75
76 30
            if (isset($xmlRoot['schema'])) {
77 2
                $tableMetadata->setSchema((string) $xmlRoot['schema']);
78
            }
79
80 30
            if (isset($xmlRoot->options)) {
81 3
                $options = $this->parseOptions($xmlRoot->options->children());
82
83 3
                foreach ($options as $optionName => $optionValue) {
84 3
                    $tableMetadata->addOption($optionName, $optionValue);
85
                }
86
            }
87
88
            // Evaluate <indexes...>
89 30
            if (isset($xmlRoot->indexes)) {
90 4
                foreach ($xmlRoot->indexes->index as $indexXml) {
91 4
                    $indexName = isset($indexXml['name']) ? (string) $indexXml['name'] : null;
92 4
                    $columns   = explode(',', (string) $indexXml['columns']);
93 4
                    $isUnique  = isset($indexXml['unique']) && $indexXml['unique'];
94 4
                    $options   = isset($indexXml->options) ? $this->parseOptions($indexXml->options->children()) : [];
95 4
                    $flags     = isset($indexXml['flags']) ? explode(',', (string) $indexXml['flags']) : [];
96
97 4
                    $tableMetadata->addIndex([
98 4
                        'name'    => $indexName,
99 4
                        'columns' => $columns,
100 4
                        'unique'  => $isUnique,
101 4
                        'options' => $options,
102 4
                        'flags'   => $flags,
103
                    ]);
104
                }
105
            }
106
107
            // Evaluate <unique-constraints..>
108
109 30
            if (isset($xmlRoot->{'unique-constraints'})) {
110 3
                foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
111 3
                    $indexName = isset($uniqueXml['name']) ? (string) $uniqueXml['name'] : null;
112 3
                    $columns   = explode(',', (string) $uniqueXml['columns']);
113 3
                    $options   = isset($uniqueXml->options) ? $this->parseOptions($uniqueXml->options->children()) : [];
114 3
                    $flags     = isset($uniqueXml['flags']) ? explode(',', (string) $uniqueXml['flags']) : [];
115
116 3
                    $tableMetadata->addUniqueConstraint([
117 3
                        'name'    => $indexName,
118 3
                        'columns' => $columns,
119 3
                        'options' => $options,
120 3
                        'flags'   => $flags,
121
                    ]);
122
                }
123
            }
124
125 30
            $metadata->setTable($tableMetadata);
126
        }
127
128
        // Evaluate second level cache
129 30
        if (isset($xmlRoot->cache)) {
130 2
            $cache = $this->convertCacheElementToCacheMetadata($xmlRoot->cache, $metadata);
131
132 2
            $metadata->setCache($cache);
133
        }
134
135
        // Evaluate native named queries
136 30
        if (isset($xmlRoot->{'named-native-queries'})) {
137 3
            foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) {
138 3
                $metadata->addNamedNativeQuery(
139 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

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

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