Failed Conditions
Pull Request — develop (#6903)
by Michael
203:41 queued 196:43
created

XmlDriver::convertCacheElementToCacheMetadata()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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

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

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