Failed Conditions
Push — master ( becf73...b9880b )
by Guilherme
09:53
created

XmlDriver::loadMetadataForClass()   F

Complexity

Conditions 133
Paths > 20000

Size

Total Lines 663
Code Lines 354

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 293
CRAP Score 202.7709

Importance

Changes 0
Metric Value
cc 133
eloc 354
nc 1493509249
nop 3
dl 0
loc 663
ccs 293
cts 348
cp 0.842
crap 202.7709
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\DBAL\Types\Type;
9
use Doctrine\ORM\Events;
10
use Doctrine\ORM\Mapping;
11
use InvalidArgumentException;
12
use ReflectionClass;
13
use ReflectionMethod;
14
use SimpleXMLElement;
15
use function array_filter;
16
use function class_exists;
17
use function constant;
18
use function explode;
19
use function file_get_contents;
20
use function get_class;
21
use function in_array;
22
use function simplexml_load_string;
23
use function sprintf;
24
use function str_replace;
25
use function strtolower;
26
use function strtoupper;
27
use function var_export;
28
29
/**
30
 * XmlDriver is a metadata driver that enables mapping through XML files.
31
 */
32
class XmlDriver extends FileDriver
33
{
34
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
35
36
    /**
37
     * {@inheritDoc}
38
     */
39 42
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
40
    {
41 42
        parent::__construct($locator, $fileExtension);
42 42
    }
43
44
    /**
45
     * {@inheritDoc}
46
     */
47 37
    public function loadMetadataForClass(
48
        string $className,
49
        ?Mapping\ComponentMetadata $parent,
50
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
51
    ) : Mapping\ComponentMetadata {
52 37
        $metadata = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
53
54
        /** @var SimpleXMLElement $xmlRoot */
55 37
        $xmlRoot = $this->getElement($className);
56
57 35
        if ($xmlRoot->getName() === 'entity') {
58 35
            if (isset($xmlRoot['repository-class'])) {
59
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
60
            }
61
62 35
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
63 35
                $metadata->asReadOnly();
64
            }
65 5
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
66 5
            if (isset($xmlRoot['repository-class'])) {
67 1
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
68
            }
69
70 5
            $metadata->isMappedSuperclass = true;
71
        } elseif ($xmlRoot->getName() === 'embeddable') {
72
            $metadata->isEmbeddedClass = true;
73
        } else {
74
            throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
75
        }
76
77
        // Process table information
78 35
        $parent = $metadata->getParent();
79
80 35
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
81 2
            $metadata->setTable($parent->table);
82
        } else {
83 35
            $namingStrategy = $metadataBuildingContext->getNamingStrategy();
84 35
            $tableMetadata  = new Mapping\TableMetadata();
85
86 35
            $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
87
88
            // Evaluate <entity...> attributes
89 35
            if (isset($xmlRoot['table'])) {
90 13
                $tableMetadata->setName((string) $xmlRoot['table']);
91
            }
92
93 35
            if (isset($xmlRoot['schema'])) {
94 2
                $tableMetadata->setSchema((string) $xmlRoot['schema']);
95
            }
96
97 35
            if (isset($xmlRoot->options)) {
98 3
                $options = $this->parseOptions($xmlRoot->options->children());
99
100 3
                foreach ($options as $optionName => $optionValue) {
101 3
                    $tableMetadata->addOption($optionName, $optionValue);
102
                }
103
            }
104
105
            // Evaluate <indexes...>
106 35
            if (isset($xmlRoot->indexes)) {
107 4
                foreach ($xmlRoot->indexes->index as $indexXml) {
108 4
                    $indexName = isset($indexXml['name']) ? (string) $indexXml['name'] : null;
109 4
                    $columns   = explode(',', (string) $indexXml['columns']);
110 4
                    $isUnique  = isset($indexXml['unique']) && $indexXml['unique'];
111 4
                    $options   = isset($indexXml->options) ? $this->parseOptions($indexXml->options->children()) : [];
112 4
                    $flags     = isset($indexXml['flags']) ? explode(',', (string) $indexXml['flags']) : [];
113
114 4
                    $tableMetadata->addIndex([
115 4
                        'name'    => $indexName,
116 4
                        'columns' => $columns,
117 4
                        'unique'  => $isUnique,
118 4
                        'options' => $options,
119 4
                        'flags'   => $flags,
120
                    ]);
121
                }
122
            }
123
124
            // Evaluate <unique-constraints..>
125
126 35
            if (isset($xmlRoot->{'unique-constraints'})) {
127 3
                foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
128 3
                    $indexName = isset($uniqueXml['name']) ? (string) $uniqueXml['name'] : null;
129 3
                    $columns   = explode(',', (string) $uniqueXml['columns']);
130 3
                    $options   = isset($uniqueXml->options) ? $this->parseOptions($uniqueXml->options->children()) : [];
131 3
                    $flags     = isset($uniqueXml['flags']) ? explode(',', (string) $uniqueXml['flags']) : [];
132
133 3
                    $tableMetadata->addUniqueConstraint([
134 3
                        'name'    => $indexName,
135 3
                        'columns' => $columns,
136 3
                        'options' => $options,
137 3
                        'flags'   => $flags,
138
                    ]);
139
                }
140
            }
141
142 35
            $metadata->setTable($tableMetadata);
143
        }
144
145
        // Evaluate second level cache
146 35
        if (isset($xmlRoot->cache)) {
147 2
            $cache = $this->convertCacheElementToCacheMetadata($xmlRoot->cache, $metadata);
148
149 2
            $metadata->setCache($cache);
150
        }
151
152 35
        if (isset($xmlRoot['inheritance-type'])) {
153 10
            $inheritanceType = strtoupper((string) $xmlRoot['inheritance-type']);
154
155 10
            $metadata->setInheritanceType(
156 10
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceType))
157
            );
158
159 10
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
160 10
                $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
161
162 10
                $discriminatorColumn->setTableName($metadata->getTableName());
163 10
                $discriminatorColumn->setColumnName('dtype');
164 10
                $discriminatorColumn->setType(Type::getType('string'));
165 10
                $discriminatorColumn->setLength(255);
166
167
                // Evaluate <discriminator-column...>
168 10
                if (isset($xmlRoot->{'discriminator-column'})) {
169 8
                    $discriminatorColumnMapping = $xmlRoot->{'discriminator-column'};
170 8
                    $typeName                   = (string) ($discriminatorColumnMapping['type'] ?? 'string');
171
172 8
                    $discriminatorColumn->setType(Type::getType($typeName));
173 8
                    $discriminatorColumn->setColumnName((string) $discriminatorColumnMapping['name']);
174
175 8
                    if (isset($discriminatorColumnMapping['column-definition'])) {
176 1
                        $discriminatorColumn->setColumnDefinition((string) $discriminatorColumnMapping['column-definition']);
177
                    }
178
179 8
                    if (isset($discriminatorColumnMapping['length'])) {
180 3
                        $discriminatorColumn->setLength((int) $discriminatorColumnMapping['length']);
181
                    }
182
                }
183
184 10
                $metadata->setDiscriminatorColumn($discriminatorColumn);
185
186
                // Evaluate <discriminator-map...>
187 10
                if (isset($xmlRoot->{'discriminator-map'})) {
188 10
                    $map = [];
189
190 10
                    foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
191 10
                        $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
192
                    }
193
194 10
                    $metadata->setDiscriminatorMap($map);
195
                }
196
            }
197
        }
198
199
        // Evaluate <change-tracking-policy...>
200 35
        if (isset($xmlRoot['change-tracking-policy'])) {
201
            $changeTrackingPolicy = strtoupper((string) $xmlRoot['change-tracking-policy']);
202
203
            $metadata->setChangeTrackingPolicy(
204
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingPolicy))
205
            );
206
        }
207
208
        // Evaluate <field ...> mappings
209 35
        if (isset($xmlRoot->field)) {
210 21
            foreach ($xmlRoot->field as $fieldElement) {
211 21
                $fieldName        = (string) $fieldElement['name'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 8 spaces

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
212 21
                $fieldMetadata    = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, $metadata, $metadataBuildingContext);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 1 space but found 4 spaces

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

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