Failed Conditions
Push — master ( 4f8027...fa7802 )
by Guilherme
09:14
created

XmlDriver::convertFieldElementToColumnAnnotation()   D

Complexity

Conditions 10
Paths 512

Size

Total Lines 40
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 19
nc 512
nop 1
dl 0
loc 40
ccs 20
cts 20
cp 1
crap 10
rs 4.1777
c 0
b 0
f 0

How to fix   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\DBALException;
9
use Doctrine\ORM\Annotation;
10
use Doctrine\ORM\Events;
11
use Doctrine\ORM\Mapping;
12
use Doctrine\ORM\Mapping\Builder;
13
use InvalidArgumentException;
14
use SimpleXMLElement;
15
use function class_exists;
16
use function constant;
17
use function explode;
18
use function file_get_contents;
19
use function get_class;
20
use function in_array;
21
use function simplexml_load_string;
22
use function sprintf;
23
use function str_replace;
24
use function strtoupper;
25
use function var_export;
26
27
/**
28
 * XmlDriver is a metadata driver that enables mapping through XML files.
29
 */
30
class XmlDriver extends FileDriver
31
{
32
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
33
34
    /**
35
     * {@inheritDoc}
36
     */
37 41
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
38
    {
39 41
        parent::__construct($locator, $fileExtension);
40 41
    }
41
42
    /**
43
     * {@inheritDoc}
44
     *
45
     * @throws DBALException
46
     */
47 36
    public function loadMetadataForClass(
48
        string $className,
49
        ?Mapping\ComponentMetadata $parent,
50
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
51
    ) : Mapping\ComponentMetadata {
52 36
        $metadata = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
53
54
        /** @var SimpleXMLElement $xmlRoot */
55 36
        $xmlRoot = $this->getElement($className);
56
57 34
        if ($xmlRoot->getName() === 'entity') {
58 34
            if (isset($xmlRoot['repository-class'])) {
59
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
60
            }
61
62 34
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
63 34
                $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 34
        $parent = $metadata->getParent();
79
80 34
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
81
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
82
            do {
83 2
                if (! $parent->isMappedSuperclass) {
84 2
                    $metadata->setTable($parent->table);
85
86 2
                    break;
87
                }
88
89
                $parent = $parent->getParent();
90 2
            } while ($parent !== null);
91
        } else {
92 34
            $tableAnnotation = new Annotation\Table();
93
94
            // Evaluate <entity...> attributes
95 34
            if (isset($xmlRoot['table'])) {
96 12
                $tableAnnotation->name = (string) $xmlRoot['table'];
97
            }
98
99 34
            if (isset($xmlRoot['schema'])) {
100 2
                $tableAnnotation->schema = (string) $xmlRoot['schema'];
101
            }
102
103
            // Evaluate <indexes...>
104 34
            if (isset($xmlRoot->indexes)) {
105 4
                $tableAnnotation->indexes = $this->parseIndexes($xmlRoot->indexes->children());
106
            }
107
108
            // Evaluate <unique-constraints..>
109 34
            if (isset($xmlRoot->{'unique-constraints'})) {
110 3
                $tableAnnotation->uniqueConstraints = $this->parseUniqueConstraints($xmlRoot->{'unique-constraints'}->children());
111
            }
112
113 34
            if (isset($xmlRoot->options)) {
114 3
                $tableAnnotation->options = $this->parseOptions($xmlRoot->options->children());
115
            }
116
117 34
            $tableBuilder = new Builder\TableMetadataBuilder($metadataBuildingContext);
118
119
            $tableBuilder
120 34
                ->withEntityClassMetadata($metadata)
121 34
                ->withTableAnnotation($tableAnnotation);
122
123 34
            $metadata->setTable($tableBuilder->build());
124
        }
125
126
        // Evaluate second level cache
127 34
        if (isset($xmlRoot->cache)) {
128 2
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
129
130
            $cacheBuilder
131 2
                ->withComponentMetadata($metadata)
132 2
                ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($xmlRoot->cache));
133
134 2
            $metadata->setCache($cacheBuilder->build());
135
        }
136
137 34
        if (isset($xmlRoot['inheritance-type'])) {
138 10
            $inheritanceType = strtoupper((string) $xmlRoot['inheritance-type']);
139
140 10
            $metadata->setInheritanceType(
141 10
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceType))
142
            );
143
144 10
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
145 10
                $discriminatorColumnBuilder = new Builder\DiscriminatorColumnMetadataBuilder($metadataBuildingContext);
146
147
                $discriminatorColumnBuilder
148 10
                    ->withComponentMetadata($metadata)
149 10
                    ->withDiscriminatorColumnAnnotation(
150 10
                        isset($xmlRoot->{'discriminator-column'})
151 8
                            ? $this->convertDiscriminiatorColumnElementToDiscriminatorColumnAnnotation($xmlRoot->{'discriminator-column'})
152 10
                            : null
153
                    );
154
155 10
                $metadata->setDiscriminatorColumn($discriminatorColumnBuilder->build());
156
157
                // Evaluate <discriminator-map...>
158 10
                if (isset($xmlRoot->{'discriminator-map'})) {
159 10
                    $map = [];
160
161 10
                    foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
162 10
                        $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
163
                    }
164
165 10
                    $metadata->setDiscriminatorMap($map);
166
                }
167
            }
168
        }
169
170
        // Evaluate <change-tracking-policy...>
171 34
        if (isset($xmlRoot['change-tracking-policy'])) {
172
            $changeTrackingPolicy = strtoupper((string) $xmlRoot['change-tracking-policy']);
173
174
            $metadata->setChangeTrackingPolicy(
175
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingPolicy))
176
            );
177
        }
178
179
        // Evaluate <field ...> mappings
180 34
        if (isset($xmlRoot->field)) {
181 20
            $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext);
182
183
            $fieldBuilder
184 20
                ->withComponentMetadata($metadata);
185
186 20
            foreach ($xmlRoot->field as $fieldElement) {
187 20
                $versionAnnotation = isset($fieldElement['version']) && $this->evaluateBoolean($fieldElement['version'])
188 3
                    ? new Annotation\Version()
189 20
                    : null;
190
191
                $fieldBuilder
192 20
                    ->withFieldName((string) $fieldElement['name'])
193 20
                    ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($fieldElement))
194 20
                    ->withIdAnnotation(null)
195 20
                    ->withVersionAnnotation($versionAnnotation);
196
197 20
                $fieldMetadata = $fieldBuilder->build();
198
199
                // Prevent column duplication
200 20
                if ($metadata->checkPropertyDuplication($fieldMetadata->getColumnName())) {
0 ignored issues
show
Bug introduced by
It seems like $fieldMetadata->getColumnName() can also be of type null; however, parameter $columnName of Doctrine\ORM\Mapping\Cla...ckPropertyDuplication() 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

200
                if ($metadata->checkPropertyDuplication(/** @scrutinizer ignore-type */ $fieldMetadata->getColumnName())) {
Loading history...
201
                    throw Mapping\MappingException::duplicateColumnName(
202
                        $metadata->getClassName(),
203
                        $fieldMetadata->getColumnName()
204
                    );
205
                }
206
207 20
                $metadata->addProperty($fieldMetadata);
208
            }
209
        }
210
211 34
        if (isset($xmlRoot->embedded)) {
212
            foreach ($xmlRoot->embedded as $embeddedMapping) {
213
                $columnPrefix = isset($embeddedMapping['column-prefix'])
214
                    ? (string) $embeddedMapping['column-prefix']
215
                    : null;
216
217
                $useColumnPrefix = isset($embeddedMapping['use-column-prefix'])
218
                    ? $this->evaluateBoolean($embeddedMapping['use-column-prefix'])
219
                    : true;
220
221
                $mapping = [
222
                    'fieldName'    => (string) $embeddedMapping['name'],
223
                    'class'        => (string) $embeddedMapping['class'],
224
                    'columnPrefix' => $useColumnPrefix ? $columnPrefix : false,
225
                ];
226
227
                $metadata->mapEmbedded($mapping);
228
            }
229
        }
230
231
        // Evaluate <id ...> mappings
232 34
        $associationIds = [];
233
234 34
        $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext);
235
236
        $fieldBuilder
237 34
            ->withComponentMetadata($metadata);
238
239 34
        foreach ($xmlRoot->id as $idElement) {
240 29
            $fieldName = (string) $idElement['name'];
241
242 29
            if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) {
243 2
                $associationIds[$fieldName] = true;
244
245 2
                continue;
246
            }
247
248 28
            $versionAnnotation = isset($idElement['version']) && $this->evaluateBoolean($idElement['version'])
249
                ? new Annotation\Version()
250 28
                : null;
251
252
            $fieldBuilder
253 28
                ->withFieldName($fieldName)
254 28
                ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($idElement))
255 28
                ->withIdAnnotation(new Annotation\Id())
256 28
                ->withVersionAnnotation($versionAnnotation);
257
258 28
            $fieldMetadata = $fieldBuilder->build();
259
260
            // Prevent column duplication
261 28
            if ($metadata->checkPropertyDuplication($fieldMetadata->getColumnName())) {
262
                throw Mapping\MappingException::duplicateColumnName(
263
                    $metadata->getClassName(),
264
                    $fieldMetadata->getColumnName()
265
                );
266
            }
267
268 28
            if (isset($idElement->generator)) {
269 27
                $strategy = (string) ($idElement->generator['strategy'] ?? 'AUTO');
270
271 27
                $idGeneratorType = constant(sprintf('%s::%s', Mapping\GeneratorType::class, strtoupper($strategy)));
272
273 27
                if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
274 20
                    $idGeneratorDefinition = [];
275
276
                    // Check for SequenceGenerator/TableGenerator definition
277 20
                    if (isset($idElement->{'sequence-generator'})) {
278 3
                        $seqGenerator          = $idElement->{'sequence-generator'};
279
                        $idGeneratorDefinition = [
280 3
                            'sequenceName'   => (string) $seqGenerator['sequence-name'],
281 3
                            'allocationSize' => (string) $seqGenerator['allocation-size'],
282
                        ];
283 17
                    } elseif (isset($idElement->{'custom-id-generator'})) {
284 2
                        $customGenerator = $idElement->{'custom-id-generator'};
285
286
                        $idGeneratorDefinition = [
287 2
                            'class'     => (string) $customGenerator['class'],
288
                            'arguments' => [],
289
                        ];
290
291 2
                        if (! isset($idGeneratorDefinition['class'])) {
292
                            throw new Mapping\MappingException(
293
                                sprintf('Cannot instantiate custom generator, no class has been defined')
294
                            );
295
                        }
296
297 2
                        if (! class_exists($idGeneratorDefinition['class'])) {
298
                            throw new Mapping\MappingException(
299 2
                                sprintf('Cannot instantiate custom generator : %s', var_export($idGeneratorDefinition, true))
300
                            );
301
                        }
302 15
                    } elseif (isset($idElement->{'table-generator'})) {
303
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
304
                    }
305
306 20
                    $fieldMetadata->setValueGenerator(
307 20
                        new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition)
308
                    );
309
                }
310
            }
311
312 28
            $metadata->addProperty($fieldMetadata);
313
        }
314
315
        // Evaluate <one-to-one ...> mappings
316 34
        if (isset($xmlRoot->{'one-to-one'})) {
317 7
            foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
318 7
                $association  = new Mapping\OneToOneAssociationMetadata((string) $oneToOneElement['field']);
319 7
                $targetEntity = (string) $oneToOneElement['target-entity'];
320
321 7
                $association->setTargetEntity($targetEntity);
322
323 7
                if (isset($associationIds[$association->getName()])) {
324
                    $association->setPrimaryKey(true);
325
                }
326
327 7
                if (isset($oneToOneElement->cascade)) {
328 7
                    $association->setCascade($this->getCascadeMappings($oneToOneElement->cascade));
329
                }
330
331 7
                if (isset($oneToOneElement['orphan-removal'])) {
332
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToOneElement['orphan-removal']));
333
                }
334
335 7
                if (isset($oneToOneElement['fetch'])) {
336 3
                    $association->setFetchMode(
337 3
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToOneElement['fetch']))
338
                    );
339
                }
340
341 7
                if (isset($oneToOneElement['mapped-by'])) {
342 3
                    $association->setMappedBy((string) $oneToOneElement['mapped-by']);
343 3
                    $association->setOwningSide(false);
344
                }
345
346 7
                if (isset($oneToOneElement['inversed-by'])) {
347 4
                    $association->setInversedBy((string) $oneToOneElement['inversed-by']);
348 4
                    $association->setOwningSide(true);
349
                }
350
351
                // Evaluate second level cache
352 7
                if (isset($oneToOneElement->cache)) {
353
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
354
355
                    $cacheBuilder
356
                        ->withComponentMetadata($metadata)
357
                        ->withFieldName($association->getName())
358
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($oneToOneElement->cache));
359
360
                    $association->setCache($cacheBuilder->build());
361
                }
362
363
                // Check for owning side to consider join column
364 7
                if (! $association->isOwningSide()) {
365 3
                    $metadata->addProperty($association);
366
367 3
                    continue;
368
                }
369
370
                // Check for JoinColumn/JoinColumns annotations
371 5
                $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
372
373
                $joinColumnBuilder
374 5
                    ->withComponentMetadata($metadata)
375 5
                    ->withFieldName($association->getName());
376
377
                switch (true) {
378 5
                    case isset($oneToOneElement->{'join-column'}):
379 5
                        $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($oneToOneElement->{'join-column'});
380
381 5
                        $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
382
383 5
                        $association->addJoinColumn($joinColumnBuilder->build());
384 5
                        break;
385
386
                    case isset($oneToOneElement->{'join-columns'}):
387
                        foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
388
                            $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
389
390
                            $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
391
392
                            $association->addJoinColumn($joinColumnBuilder->build());
393
                        }
394
395
                        break;
396
397
                    default:
398
                        $association->addJoinColumn($joinColumnBuilder->build());
399
                        break;
400
                }
401
402 5
                $metadata->addProperty($association);
403
            }
404
        }
405
406
        // Evaluate <one-to-many ...> mappings
407 34
        if (isset($xmlRoot->{'one-to-many'})) {
408 9
            foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
409 9
                $association  = new Mapping\OneToManyAssociationMetadata((string) $oneToManyElement['field']);
410 9
                $targetEntity = (string) $oneToManyElement['target-entity'];
411
412 9
                $association->setTargetEntity($targetEntity);
413 9
                $association->setOwningSide(false);
414 9
                $association->setMappedBy((string) $oneToManyElement['mapped-by']);
415
416 9
                if (isset($associationIds[$association->getName()])) {
417
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
418
                }
419
420 9
                if (isset($oneToManyElement['fetch'])) {
421
                    $association->setFetchMode(
422
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToManyElement['fetch']))
423
                    );
424
                }
425
426 9
                if (isset($oneToManyElement->cascade)) {
427 6
                    $association->setCascade($this->getCascadeMappings($oneToManyElement->cascade));
428
                }
429
430 9
                if (isset($oneToManyElement['orphan-removal'])) {
431 3
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToManyElement['orphan-removal']));
432
                }
433
434 9
                if (isset($oneToManyElement->{'order-by'})) {
435 5
                    $orderBy = [];
436
437 5
                    foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
438 5
                        $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
439 4
                            ? (string) $orderByField['direction']
440 1
                            : Criteria::ASC;
441
                    }
442
443 5
                    $association->setOrderBy($orderBy);
444
                }
445
446 9
                if (isset($oneToManyElement['index-by'])) {
447 3
                    $association->setIndexedBy((string) $oneToManyElement['index-by']);
448 6
                } elseif (isset($oneToManyElement->{'index-by'})) {
449
                    throw new InvalidArgumentException('<index-by /> is not a valid tag');
450
                }
451
452
                // Evaluate second level cache
453 9
                if (isset($oneToManyElement->cache)) {
454 1
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
455
456
                    $cacheBuilder
457 1
                        ->withComponentMetadata($metadata)
458 1
                        ->withFieldName($association->getName())
459 1
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($oneToManyElement->cache));
460
461 1
                    $association->setCache($cacheBuilder->build());
462
                }
463
464 9
                $metadata->addProperty($association);
465
            }
466
        }
467
468
        // Evaluate <many-to-one ...> mappings
469 34
        if (isset($xmlRoot->{'many-to-one'})) {
470 8
            foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
471 8
                $association  = new Mapping\ManyToOneAssociationMetadata((string) $manyToOneElement['field']);
472 8
                $targetEntity = (string) $manyToOneElement['target-entity'];
473
474 8
                $association->setTargetEntity($targetEntity);
475
476 8
                if (isset($associationIds[$association->getName()])) {
477 2
                    $association->setPrimaryKey(true);
478
                }
479
480 8
                if (isset($manyToOneElement['fetch'])) {
481
                    $association->setFetchMode(
482
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $manyToOneElement['fetch'])
483
                    );
484
                }
485
486 8
                if (isset($manyToOneElement->cascade)) {
487 4
                    $association->setCascade($this->getCascadeMappings($manyToOneElement->cascade));
488
                }
489
490 8
                if (isset($manyToOneElement['inversed-by'])) {
491 2
                    $association->setInversedBy((string) $manyToOneElement['inversed-by']);
492
                }
493
494
                // Evaluate second level cache
495 8
                if (isset($manyToOneElement->cache)) {
496 1
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
497
498
                    $cacheBuilder
499 1
                        ->withComponentMetadata($metadata)
500 1
                        ->withFieldName($association->getName())
501 1
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($manyToOneElement->cache));
502
503 1
                    $association->setCache($cacheBuilder->build());
504
                }
505
506
                // Check for JoinColumn/JoinColumns annotations
507 8
                $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
508
509
                $joinColumnBuilder
510 8
                    ->withComponentMetadata($metadata)
511 8
                    ->withFieldName($association->getName());
512
513
                switch (true) {
514 8
                    case isset($oneToOneElement->{'join-column'}):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $oneToOneElement does not seem to be defined for all execution paths leading up to this point.
Loading history...
515
                        $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($oneToOneElement->{'join-column'});
516
517
                        $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
518
519
                        $association->addJoinColumn($joinColumnBuilder->build());
520
                        break;
521
522 8
                    case isset($oneToOneElement->{'join-columns'}):
523
                        foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
524
                            $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
525
526
                            $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
527
528
                            $association->addJoinColumn($joinColumnBuilder->build());
529
                        }
530
531
                        break;
532
533
                    default:
534 8
                        $association->addJoinColumn($joinColumnBuilder->build());
535 8
                        break;
536
                }
537
538 8
                $metadata->addProperty($association);
539
            }
540
        }
541
542
        // Evaluate <many-to-many ...> mappings
543 33
        if (isset($xmlRoot->{'many-to-many'})) {
544 14
            foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
545 14
                $association  = new Mapping\ManyToManyAssociationMetadata((string) $manyToManyElement['field']);
546 14
                $targetEntity = (string) $manyToManyElement['target-entity'];
547
548 14
                $association->setTargetEntity($targetEntity);
549
550 14
                if (isset($associationIds[$association->getName()])) {
551
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
552
                }
553
554 14
                if (isset($manyToManyElement['fetch'])) {
555 4
                    $association->setFetchMode(
556 4
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $manyToManyElement['fetch']))
557
                    );
558
                }
559
560 14
                if (isset($manyToManyElement->cascade)) {
561 8
                    $association->setCascade($this->getCascadeMappings($manyToManyElement->cascade));
562
                }
563
564 14
                if (isset($manyToManyElement['orphan-removal'])) {
565
                    $association->setOrphanRemoval($this->evaluateBoolean($manyToManyElement['orphan-removal']));
566
                }
567
568 14
                if (isset($manyToManyElement['mapped-by'])) {
569 5
                    $association->setMappedBy((string) $manyToManyElement['mapped-by']);
570 5
                    $association->setOwningSide(false);
571
                }
572
573 14
                if (isset($manyToManyElement['inversed-by'])) {
574 6
                    $association->setInversedBy((string) $manyToManyElement['inversed-by']);
575
                }
576
577 14
                if (isset($manyToManyElement['index-by'])) {
578
                    $association->setIndexedBy((string) $manyToManyElement['index-by']);
579 14
                } elseif (isset($manyToManyElement->{'index-by'})) {
580
                    throw new InvalidArgumentException('<index-by /> is not a valid tag');
581
                }
582
583 14
                if (isset($manyToManyElement->{'order-by'})) {
584 1
                    $orderBy = [];
585
586 1
                    foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
587 1
                        $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
588
                            ? (string) $orderByField['direction']
589 1
                            : Criteria::ASC;
590
                    }
591
592 1
                    $association->setOrderBy($orderBy);
593
                }
594
595
                // Check for cache
596 14
                if (isset($manyToManyElement->cache)) {
597
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
598
599
                    $cacheBuilder
600
                        ->withComponentMetadata($metadata)
601
                        ->withFieldName($association->getName())
602
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($manyToManyElement->cache));
603
604
                    $association->setCache($cacheBuilder->build());
605
                }
606
607
                // Check for owning side to consider join column
608 14
                if (! $association->isOwningSide()) {
609 5
                    $metadata->addProperty($association);
610
611 5
                    continue;
612
                }
613
614 10
                $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
615
616
                $joinTableBuilder
617 10
                    ->withComponentMetadata($metadata)
618 10
                    ->withFieldName($association->getName())
619 10
                    ->withTargetEntity($targetEntity);
620
621 10
                if (isset($manyToManyElement->{'join-table'})) {
622 8
                    $joinTableElement    = $manyToManyElement->{'join-table'};
623 8
                    $joinTableAnnotation = $this->convertJoinTableElementToJoinTableAnnotation($joinTableElement);
624
625 8
                    $joinTableBuilder->withJoinTableAnnotation($joinTableAnnotation);
626
                }
627
628 10
                $association->setJoinTable($joinTableBuilder->build());
629
630 10
                $metadata->addProperty($association);
631
            }
632
        }
633
634
        // Evaluate association-overrides
635 33
        if (isset($xmlRoot->{'attribute-overrides'})) {
636 2
            $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext);
637
638
            $fieldBuilder
639 2
                ->withComponentMetadata($metadata);
640
641 2
            foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) {
642 2
                $fieldName = (string) $overrideElement['name'];
643
644 2
                foreach ($overrideElement->field as $fieldElement) {
645 2
                    $versionAnnotation = isset($fieldElement['version']) && $this->evaluateBoolean($fieldElement['version'])
646
                        ? new Annotation\Version()
647 2
                        : null;
648
649
                    $fieldBuilder
650 2
                        ->withFieldName($fieldName)
651 2
                        ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($fieldElement))
652 2
                        ->withIdAnnotation(null)
653 2
                        ->withVersionAnnotation($versionAnnotation);
654
655 2
                    $fieldMetadata = $fieldBuilder->build();
656
657
                    // Prevent column duplication
658 2
                    if ($metadata->checkPropertyDuplication($fieldMetadata->getColumnName())) {
659
                        throw Mapping\MappingException::duplicateColumnName(
660
                            $metadata->getClassName(),
661
                            $fieldMetadata->getColumnName()
662
                        );
663
                    }
664
665 2
                    $metadata->setPropertyOverride($fieldMetadata);
666
                }
667
            }
668
        }
669
670
        // Evaluate association-overrides
671 33
        if (isset($xmlRoot->{'association-overrides'})) {
672 4
            foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) {
673 4
                $fieldName = (string) $overrideElement['name'];
674 4
                $property  = $metadata->getProperty($fieldName);
675
676 4
                if (! $property) {
677
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
678
                }
679
680 4
                $existingClass = get_class($property);
681 4
                $override      = new $existingClass($fieldName);
682
683 4
                $override->setTargetEntity($property->getTargetEntity());
0 ignored issues
show
Bug introduced by
The method getTargetEntity() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

683
                $override->setTargetEntity($property->/** @scrutinizer ignore-call */ getTargetEntity());
Loading history...
684
685
                // Check for join-columns
686 4
                if (isset($overrideElement->{'join-columns'})) {
687 2
                    $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
688
689
                    $joinColumnBuilder
690 2
                        ->withComponentMetadata($metadata)
691 2
                        ->withFieldName($override->getName());
692
693 2
                    foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
694 2
                        $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
695
696 2
                        $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
697
698 2
                        $override->addJoinColumn($joinColumnBuilder->build());
699
                    }
700
                }
701
702
                // Check for join-table
703 4
                if ($overrideElement->{'join-table'}) {
704 2
                    $joinTableElement    = $overrideElement->{'join-table'};
705 2
                    $joinTableAnnotation = $this->convertJoinTableElementToJoinTableAnnotation($joinTableElement);
706 2
                    $joinTableBuilder    = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
707
708
                    $joinTableBuilder
709 2
                        ->withComponentMetadata($metadata)
710 2
                        ->withFieldName($property->getName())
711 2
                        ->withTargetEntity($property->getTargetEntity())
712 2
                        ->withJoinTableAnnotation($joinTableAnnotation);
713
714 2
                    $override->setJoinTable($joinTableBuilder->build());
715
                }
716
717
                // Check for inversed-by
718 4
                if (isset($overrideElement->{'inversed-by'})) {
719 1
                    $override->setInversedBy((string) $overrideElement->{'inversed-by'}['name']);
720
                }
721
722
                // Check for fetch
723 4
                if (isset($overrideElement['fetch'])) {
724 1
                    $override->setFetchMode(
725 1
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $overrideElement['fetch'])
726
                    );
727
                }
728
729 4
                $metadata->setPropertyOverride($override);
730
            }
731
        }
732
733
        // Evaluate <lifecycle-callbacks...>
734 33
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
735 3
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
736 3
                $eventName  = constant(Events::class . '::' . (string) $lifecycleCallback['type']);
737 3
                $methodName = (string) $lifecycleCallback['method'];
738
739 3
                $metadata->addLifecycleCallback($eventName, $methodName);
740
            }
741
        }
742
743
        // Evaluate entity listener
744 33
        if (isset($xmlRoot->{'entity-listeners'})) {
745 2
            foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) {
746 2
                $listenerClassName = (string) $listenerElement['class'];
747
748 2
                if (! class_exists($listenerClassName)) {
749
                    throw Mapping\MappingException::entityListenerClassNotFound(
750
                        $listenerClassName,
751
                        $metadata->getClassName()
752
                    );
753
                }
754
755 2
                foreach ($listenerElement as $callbackElement) {
756 2
                    $eventName  = (string) $callbackElement['type'];
757 2
                    $methodName = (string) $callbackElement['method'];
758
759 2
                    $metadata->addEntityListener($eventName, $listenerClassName, $methodName);
760
                }
761
            }
762
        }
763
764 33
        return $metadata;
765
    }
766
767
    /**
768
     * Parses (nested) index elements.
769
     *
770
     * @param SimpleXMLElement $indexes The XML element.
771
     *
772
     * @return Annotation\Index[] The indexes array.
773
     */
774 4
    private function parseIndexes(SimpleXMLElement $indexes) : array
775
    {
776 4
        $array = [];
777
778
        /** @var SimpleXMLElement $index */
779 4
        foreach ($indexes as $index) {
780 4
            $indexAnnotation = new Annotation\Index();
781
782 4
            $indexAnnotation->columns = explode(',', (string) $index['columns']);
783 4
            $indexAnnotation->options = isset($index->options) ? $this->parseOptions($index->options->children()) : [];
784 4
            $indexAnnotation->flags   = isset($index['flags']) ? explode(',', (string) $index['flags']) : [];
785
786 4
            if (isset($index['name'])) {
787 3
                $indexAnnotation->name = (string) $index['name'];
788
            }
789
790 4
            if (isset($index['unique'])) {
791
                $indexAnnotation->unique = $this->evaluateBoolean($index['unique']);
792
            }
793
794 4
            $array[] = $indexAnnotation;
795
        }
796
797 4
        return $array;
798
    }
799
800
    /**
801
     * Parses (nested) unique constraint elements.
802
     *
803
     * @param SimpleXMLElement $uniqueConstraints The XML element.
804
     *
805
     * @return Annotation\UniqueConstraint[] The unique constraints array.
806
     */
807 3
    private function parseUniqueConstraints(SimpleXMLElement $uniqueConstraints) : array
808
    {
809 3
        $array = [];
810
811
        /** @var SimpleXMLElement $uniqueConstraint */
812 3
        foreach ($uniqueConstraints as $uniqueConstraint) {
813 3
            $uniqueConstraintAnnotation = new Annotation\UniqueConstraint();
814
815 3
            $uniqueConstraintAnnotation->columns = explode(',', (string) $uniqueConstraint['columns']);
816 3
            $uniqueConstraintAnnotation->options = isset($uniqueConstraint->options) ? $this->parseOptions($uniqueConstraint->options->children()) : [];
817 3
            $uniqueConstraintAnnotation->flags   = isset($uniqueConstraint['flags']) ? explode(',', (string) $uniqueConstraint['flags']) : [];
818
819 3
            if (isset($uniqueConstraint['name'])) {
820 3
                $uniqueConstraintAnnotation->name = (string) $uniqueConstraint['name'];
821
            }
822
823 3
            $array[] = $uniqueConstraintAnnotation;
824
        }
825
826 3
        return $array;
827
    }
828
829
    /**
830
     * Parses (nested) option elements.
831
     *
832
     * @param SimpleXMLElement $options The XML element.
833
     *
834
     * @return mixed[] The options array.
835
     */
836 4
    private function parseOptions(SimpleXMLElement $options) : array
837
    {
838 4
        $array = [];
839
840
        /** @var SimpleXMLElement $option */
841 4
        foreach ($options as $option) {
842 4
            if ($option->count()) {
843 3
                $value = $this->parseOptions($option->children());
844
            } else {
845 4
                $value = (string) $option;
846
            }
847
848 4
            $attributes = $option->attributes();
849
850 4
            if (isset($attributes->name)) {
851 4
                $nameAttribute = (string) $attributes->name;
852
853 4
                $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true)
854 3
                    ? $this->evaluateBoolean($value)
855 4
                    : $value;
856
            } else {
857
                $array[] = $value;
858
            }
859
        }
860
861 4
        return $array;
862
    }
863
864 30
    private function convertFieldElementToColumnAnnotation(
865
        SimpleXMLElement $fieldElement
866
    ) : Annotation\Column {
867 30
        $columnAnnotation = new Annotation\Column();
868
869 30
        $columnAnnotation->type = isset($fieldElement['type']) ? (string) $fieldElement['type'] : 'string';
870
871 30
        if (isset($fieldElement['column'])) {
872 20
            $columnAnnotation->name = (string) $fieldElement['column'];
873
        }
874
875 30
        if (isset($fieldElement['length'])) {
876 6
            $columnAnnotation->length = (int) $fieldElement['length'];
877
        }
878
879 30
        if (isset($fieldElement['precision'])) {
880 1
            $columnAnnotation->precision = (int) $fieldElement['precision'];
881
        }
882
883 30
        if (isset($fieldElement['scale'])) {
884 1
            $columnAnnotation->scale = (int) $fieldElement['scale'];
885
        }
886
887 30
        if (isset($fieldElement['unique'])) {
888 7
            $columnAnnotation->unique = $this->evaluateBoolean($fieldElement['unique']);
889
        }
890
891 30
        if (isset($fieldElement['nullable'])) {
892 7
            $columnAnnotation->nullable = $this->evaluateBoolean($fieldElement['nullable']);
893
        }
894
895 30
        if (isset($fieldElement['column-definition'])) {
896 4
            $columnAnnotation->columnDefinition = (string) $fieldElement['column-definition'];
897
        }
898
899 30
        if (isset($fieldElement->options)) {
900 3
            $columnAnnotation->options = $this->parseOptions($fieldElement->options->children());
901
        }
902
903 30
        return $columnAnnotation;
904
    }
905
906
    /**
907
     * Constructs a JoinTable annotation based on the information
908
     * found in the given SimpleXMLElement.
909
     *
910
     * @param SimpleXMLElement $joinTableElement The XML element.
911
     */
912 8
    private function convertJoinTableElementToJoinTableAnnotation(
913
        SimpleXMLElement $joinTableElement
914
    ) : Annotation\JoinTable {
915 8
        $joinTableAnnotation = new Annotation\JoinTable();
916
917 8
        if (isset($joinTableElement['name'])) {
918 8
            $joinTableAnnotation->name = (string) $joinTableElement['name'];
919
        }
920
921 8
        if (isset($joinTableElement['schema'])) {
922
            $joinTableAnnotation->schema = (string) $joinTableElement['schema'];
923
        }
924
925 8
        if (isset($joinTableElement->{'join-columns'})) {
926 8
            $joinColumns = [];
927
928 8
            foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
929 8
                $joinColumns[] = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
930
            }
931
932 8
            $joinTableAnnotation->joinColumns = $joinColumns;
933
        }
934
935 8
        if (isset($joinTableElement->{'inverse-join-columns'})) {
936 8
            $joinColumns = [];
937
938 8
            foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
939 8
                $joinColumns[] = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
940
            }
941
942 8
            $joinTableAnnotation->inverseJoinColumns = $joinColumns;
943
        }
944
945 8
        return $joinTableAnnotation;
946
    }
947
948
    /**
949
     * Constructs a JoinColumn annotation based on the information
950
     * found in the given SimpleXMLElement.
951
     *
952
     * @param SimpleXMLElement $joinColumnElement The XML element.
953
     */
954 9
    private function convertJoinColumnElementToJoinColumnAnnotation(
955
        SimpleXMLElement $joinColumnElement
956
    ) : Annotation\JoinColumn {
957 9
        $joinColumnAnnotation = new Annotation\JoinColumn();
958
959 9
        $joinColumnAnnotation->name                 = (string) $joinColumnElement['name'];
960 9
        $joinColumnAnnotation->referencedColumnName = (string) $joinColumnElement['referenced-column-name'];
961
962 9
        if (isset($joinColumnElement['column-definition'])) {
963 3
            $joinColumnAnnotation->columnDefinition = (string) $joinColumnElement['column-definition'];
964
        }
965
966 9
        if (isset($joinColumnElement['field-name'])) {
967
            $joinColumnAnnotation->fieldName = (string) $joinColumnElement['field-name'];
968
        }
969
970 9
        if (isset($joinColumnElement['nullable'])) {
971 3
            $joinColumnAnnotation->nullable = $this->evaluateBoolean($joinColumnElement['nullable']);
972
        }
973
974 9
        if (isset($joinColumnElement['unique'])) {
975 3
            $joinColumnAnnotation->unique = $this->evaluateBoolean($joinColumnElement['unique']);
976
        }
977
978 9
        if (isset($joinColumnElement['on-delete'])) {
979 3
            $joinColumnAnnotation->onDelete = strtoupper((string) $joinColumnElement['on-delete']);
980
        }
981
982 9
        return $joinColumnAnnotation;
983
    }
984
985
    /**
986
     * Parse the given Cache as CacheMetadata
987
     */
988 2
    private function convertCacheElementToCacheAnnotation(SimpleXMLElement $cacheMapping) : Annotation\Cache
989
    {
990 2
        $cacheAnnotation = new Annotation\Cache();
991
992 2
        if (isset($cacheMapping['region'])) {
993
            $cacheAnnotation->region = (string) $cacheMapping['region'];
994
        }
995
996 2
        if (isset($cacheMapping['usage'])) {
997 2
            $cacheAnnotation->usage = strtoupper((string) $cacheMapping['usage']);
998
        }
999
1000 2
        return $cacheAnnotation;
1001
    }
1002
1003 8
    private function convertDiscriminiatorColumnElementToDiscriminatorColumnAnnotation(
1004
        SimpleXMLElement $discriminatorColumnMapping
1005
    ) : Annotation\DiscriminatorColumn {
1006 8
        $discriminatorColumnAnnotation = new Annotation\DiscriminatorColumn();
1007
1008 8
        $discriminatorColumnAnnotation->type = (string) ($discriminatorColumnMapping['type'] ?? 'string');
1009 8
        $discriminatorColumnAnnotation->name = (string) $discriminatorColumnMapping['name'];
1010
1011 8
        if (isset($discriminatorColumnMapping['column-definition'])) {
1012 1
            $discriminatorColumnAnnotation->columnDefinition = (string) $discriminatorColumnMapping['column-definition'];
1013
        }
1014
1015 8
        if (isset($discriminatorColumnMapping['length'])) {
1016 3
            $discriminatorColumnAnnotation->length = (int) $discriminatorColumnMapping['length'];
1017
        }
1018
1019 8
        return $discriminatorColumnAnnotation;
1020
    }
1021
1022
    /**
1023
     * Gathers a list of cascade options found in the given cascade element.
1024
     *
1025
     * @param SimpleXMLElement $cascadeElement The cascade element.
1026
     *
1027
     * @return string[] The list of cascade options.
1028
     */
1029 10
    private function getCascadeMappings(SimpleXMLElement $cascadeElement) : array
1030
    {
1031 10
        $cascades = [];
1032
1033
        /** @var SimpleXMLElement $action */
1034 10
        foreach ($cascadeElement->children() as $action) {
1035
            // According to the JPA specifications, XML uses "cascade-persist"
1036
            // instead of "persist". Here, both variations are supported
1037
            // because Annotation use "persist" and we want to make sure that
1038
            // this driver doesn't need to know anything about the supported
1039
            // cascading actions
1040 10
            $cascades[] = str_replace('cascade-', '', $action->getName());
1041
        }
1042
1043 10
        return $cascades;
1044
    }
1045
1046
    /**
1047
     * {@inheritDoc}
1048
     */
1049 36
    protected function loadMappingFile($file)
1050
    {
1051 36
        $result = [];
1052
        // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577
1053 36
        $xmlElement = simplexml_load_string(file_get_contents($file));
1054
1055 36
        if (isset($xmlElement->entity)) {
1056 35
            foreach ($xmlElement->entity as $entityElement) {
1057 35
                $entityName          = (string) $entityElement['name'];
1058 35
                $result[$entityName] = $entityElement;
1059
            }
1060 6
        } elseif (isset($xmlElement->{'mapped-superclass'})) {
1061 5
            foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) {
1062 5
                $className          = (string) $mappedSuperClass['name'];
1063 5
                $result[$className] = $mappedSuperClass;
1064
            }
1065 1
        } elseif (isset($xmlElement->embeddable)) {
1066
            foreach ($xmlElement->embeddable as $embeddableElement) {
1067
                $embeddableName          = (string) $embeddableElement['name'];
1068
                $result[$embeddableName] = $embeddableElement;
1069
            }
1070
        }
1071
1072 36
        return $result;
1073
    }
1074
1075
    /**
1076
     * @param mixed $element
1077
     *
1078
     * @return bool
1079
     */
1080 9
    protected function evaluateBoolean($element)
1081
    {
1082 9
        $flag = (string) $element;
1083
1084 9
        return $flag === 'true' || $flag === '1';
1085
    }
1086
}
1087