Failed Conditions
Push — master ( ee4e26...e98654 )
by Marco
13:06
created

convertJoinColumnElementToJoinColumnMetadata()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0106

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 32
nop 1
dl 0
loc 28
ccs 14
cts 15
cp 0.9333
crap 6.0106
rs 8.439
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 && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
62 2
            $metadata->setTable($parent->table);
63
        } else {
64 30
            $namingStrategy = $metadataBuildingContext->getNamingStrategy();
65 30
            $tableMetadata  = new Mapping\TableMetadata();
66
67 30
            $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
68
69
            // Evaluate <entity...> attributes
70 30
            if (isset($xmlRoot['table'])) {
71 11
                $tableMetadata->setName((string) $xmlRoot['table']);
72
            }
73
74 30
            if (isset($xmlRoot['schema'])) {
75 2
                $tableMetadata->setSchema((string) $xmlRoot['schema']);
76
            }
77
78 30
            if (isset($xmlRoot->options)) {
79 3
                $options = $this->parseOptions($xmlRoot->options->children());
80
81 3
                foreach ($options as $optionName => $optionValue) {
82 3
                    $tableMetadata->addOption($optionName, $optionValue);
83
                }
84
            }
85
86
            // Evaluate <indexes...>
87 30
            if (isset($xmlRoot->indexes)) {
88 4
                foreach ($xmlRoot->indexes->index as $indexXml) {
89 4
                    $indexName = isset($indexXml['name']) ? (string) $indexXml['name'] : null;
90 4
                    $columns   = explode(',', (string) $indexXml['columns']);
91 4
                    $isUnique  = isset($indexXml['unique']) && $indexXml['unique'];
92 4
                    $options   = isset($indexXml->options) ? $this->parseOptions($indexXml->options->children()) : [];
93 4
                    $flags     = isset($indexXml['flags']) ? explode(',', (string) $indexXml['flags']) : [];
94
95 4
                    $tableMetadata->addIndex([
96 4
                        'name'    => $indexName,
97 4
                        'columns' => $columns,
98 4
                        'unique'  => $isUnique,
99 4
                        'options' => $options,
100 4
                        'flags'   => $flags,
101
                    ]);
102
                }
103
            }
104
105
            // Evaluate <unique-constraints..>
106
107 30
            if (isset($xmlRoot->{'unique-constraints'})) {
108 3
                foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
109 3
                    $indexName = isset($uniqueXml['name']) ? (string) $uniqueXml['name'] : null;
110 3
                    $columns   = explode(',', (string) $uniqueXml['columns']);
111 3
                    $options   = isset($uniqueXml->options) ? $this->parseOptions($uniqueXml->options->children()) : [];
112 3
                    $flags     = isset($uniqueXml['flags']) ? explode(',', (string) $uniqueXml['flags']) : [];
113
114 3
                    $tableMetadata->addUniqueConstraint([
115 3
                        'name'    => $indexName,
116 3
                        'columns' => $columns,
117 3
                        'options' => $options,
118 3
                        'flags'   => $flags,
119
                    ]);
120
                }
121
            }
122
123 30
            $metadata->setTable($tableMetadata);
124
        }
125
126
        // Evaluate second level cache
127 30
        if (isset($xmlRoot->cache)) {
128 2
            $cache = $this->convertCacheElementToCacheMetadata($xmlRoot->cache, $metadata);
129
130 2
            $metadata->setCache($cache);
131
        }
132
133
        // Evaluate native named queries
134 30
        if (isset($xmlRoot->{'named-native-queries'})) {
135 3
            foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) {
136 3
                $metadata->addNamedNativeQuery(
137 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

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

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