Failed Conditions
Push — master ( 6971e7...b11528 )
by Guilherme
08:44
created

convertJoinColumnElementToJoinColumnAnnotation()   A

Complexity

Conditions 6
Paths 32

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0106

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 32
nop 1
dl 0
loc 29
ccs 14
cts 15
cp 0.9333
crap 6.0106
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\DBAL\DBALException;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Annotation;
11
use Doctrine\ORM\Events;
12
use Doctrine\ORM\Mapping;
13
use Doctrine\ORM\Mapping\Builder;
14
use InvalidArgumentException;
15
use SimpleXMLElement;
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 strtoupper;
26
use function var_export;
27
28
/**
29
 * XmlDriver is a metadata driver that enables mapping through XML files.
30
 */
31
class XmlDriver extends FileDriver
32
{
33
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
34
35
    /**
36
     * {@inheritDoc}
37
     */
38 41
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
39
    {
40 41
        parent::__construct($locator, $fileExtension);
41 41
    }
42
43
    /**
44
     * {@inheritDoc}
45
     *
46
     * @throws DBALException
47
     */
48 36
    public function loadMetadataForClass(
49
        string $className,
50
        ?Mapping\ComponentMetadata $parent,
51
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
52
    ) : Mapping\ComponentMetadata {
53 36
        $metadata = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
54
55
        /** @var SimpleXMLElement $xmlRoot */
56 36
        $xmlRoot = $this->getElement($className);
57
58 34
        if ($xmlRoot->getName() === 'entity') {
59 34
            if (isset($xmlRoot['repository-class'])) {
60
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
61
            }
62
63 34
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
64 34
                $metadata->asReadOnly();
65
            }
66 5
        } elseif ($xmlRoot->getName() === 'mapped-superclass') {
67 5
            if (isset($xmlRoot['repository-class'])) {
68 1
                $metadata->setCustomRepositoryClassName((string) $xmlRoot['repository-class']);
69
            }
70
71 5
            $metadata->isMappedSuperclass = true;
72
        } elseif ($xmlRoot->getName() === 'embeddable') {
73
            $metadata->isEmbeddedClass = true;
74
        } else {
75
            throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
76
        }
77
78
        // Process table information
79 34
        $parent = $metadata->getParent();
80
81 34
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
82
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
83
            do {
84 2
                if (! $parent->isMappedSuperclass) {
85 2
                    $metadata->setTable($parent->table);
86
87 2
                    break;
88
                }
89
90
                $parent = $parent->getParent();
91 2
            } while ($parent !== null);
92
        } else {
93 34
            $tableAnnotation = new Annotation\Table();
94
95
            // Evaluate <entity...> attributes
96 34
            if (isset($xmlRoot['table'])) {
97 12
                $tableAnnotation->name = (string) $xmlRoot['table'];
98
            }
99
100 34
            if (isset($xmlRoot['schema'])) {
101 2
                $tableAnnotation->schema = (string) $xmlRoot['schema'];
102
            }
103
104
            // Evaluate <indexes...>
105 34
            if (isset($xmlRoot->indexes)) {
106 4
                $tableAnnotation->indexes = $this->parseIndexes($xmlRoot->indexes->children());
107
            }
108
109
            // Evaluate <unique-constraints..>
110 34
            if (isset($xmlRoot->{'unique-constraints'})) {
111 3
                $tableAnnotation->uniqueConstraints = $this->parseUniqueConstraints($xmlRoot->{'unique-constraints'}->children());
112
            }
113
114 34
            if (isset($xmlRoot->options)) {
115 3
                $tableAnnotation->options = $this->parseOptions($xmlRoot->options->children());
116
            }
117
118 34
            $tableBuilder = new Builder\TableMetadataBuilder($metadataBuildingContext);
119
120
            $tableBuilder
121 34
                ->withEntityClassMetadata($metadata)
122 34
                ->withTableAnnotation($tableAnnotation);
123
124 34
            $metadata->setTable($tableBuilder->build());
125
        }
126
127
        // Evaluate second level cache
128 34
        if (isset($xmlRoot->cache)) {
129 2
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
130
131
            $cacheBuilder
132 2
                ->withComponentMetadata($metadata)
133 2
                ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($xmlRoot->cache));
134
135 2
            $metadata->setCache($cacheBuilder->build());
136
        }
137
138 34
        if (isset($xmlRoot['inheritance-type'])) {
139 10
            $inheritanceType = strtoupper((string) $xmlRoot['inheritance-type']);
140
141 10
            $metadata->setInheritanceType(
142 10
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceType))
143
            );
144
145 10
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
146 10
                $discriminatorColumnBuilder = new Builder\DiscriminatorColumnMetadataBuilder($metadataBuildingContext);
147
148
                $discriminatorColumnBuilder
149 10
                    ->withComponentMetadata($metadata)
150 10
                    ->withDiscriminatorColumnAnnotation(
151 10
                        isset($xmlRoot->{'discriminator-column'})
152 8
                            ? $this->convertDiscriminiatorColumnElementToDiscriminatorColumnAnnotation($xmlRoot->{'discriminator-column'})
153 10
                            : null
154
                    );
155
156 10
                $metadata->setDiscriminatorColumn($discriminatorColumnBuilder->build());
157
158
                // Evaluate <discriminator-map...>
159 10
                if (isset($xmlRoot->{'discriminator-map'})) {
160 10
                    $map = [];
161
162 10
                    foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
163 10
                        $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
164
                    }
165
166 10
                    $metadata->setDiscriminatorMap($map);
167
                }
168
            }
169
        }
170
171
        // Evaluate <change-tracking-policy...>
172 34
        if (isset($xmlRoot['change-tracking-policy'])) {
173
            $changeTrackingPolicy = strtoupper((string) $xmlRoot['change-tracking-policy']);
174
175
            $metadata->setChangeTrackingPolicy(
176
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingPolicy))
177
            );
178
        }
179
180
        // Evaluate <field ...> mappings
181 34
        if (isset($xmlRoot->field)) {
182 20
            foreach ($xmlRoot->field as $fieldElement) {
183 20
                $fieldName     = (string) $fieldElement['name'];
184 20
                $fieldMetadata = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, $metadata, $metadataBuildingContext);
185
186 20
                $metadata->addProperty($fieldMetadata);
187
            }
188
        }
189
190 34
        if (isset($xmlRoot->embedded)) {
191
            foreach ($xmlRoot->embedded as $embeddedMapping) {
192
                $columnPrefix = isset($embeddedMapping['column-prefix'])
193
                    ? (string) $embeddedMapping['column-prefix']
194
                    : null;
195
196
                $useColumnPrefix = isset($embeddedMapping['use-column-prefix'])
197
                    ? $this->evaluateBoolean($embeddedMapping['use-column-prefix'])
198
                    : true;
199
200
                $mapping = [
201
                    'fieldName'    => (string) $embeddedMapping['name'],
202
                    'class'        => (string) $embeddedMapping['class'],
203
                    'columnPrefix' => $useColumnPrefix ? $columnPrefix : false,
204
                ];
205
206
                $metadata->mapEmbedded($mapping);
207
            }
208
        }
209
210
        // Evaluate <id ...> mappings
211 34
        $associationIds = [];
212
213 34
        foreach ($xmlRoot->id as $idElement) {
214 29
            $fieldName = (string) $idElement['name'];
215
216 29
            if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) {
217 2
                $associationIds[$fieldName] = true;
218
219 2
                continue;
220
            }
221
222 28
            $fieldMetadata = $this->convertFieldElementToFieldMetadata($idElement, $fieldName, $metadata, $metadataBuildingContext);
223
224 28
            $fieldMetadata->setPrimaryKey(true);
225
226
            // Prevent PK and version on same field
227 28
            if ($fieldMetadata->isVersioned()) {
228
                throw Mapping\MappingException::cannotVersionIdField($className, $fieldName);
229
            }
230
231 28
            if (isset($idElement->generator)) {
232 27
                $strategy = (string) ($idElement->generator['strategy'] ?? 'AUTO');
233
234 27
                $idGeneratorType = constant(sprintf('%s::%s', Mapping\GeneratorType::class, strtoupper($strategy)));
235
236 27
                if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
237 20
                    $idGeneratorDefinition = [];
238
239
                    // Check for SequenceGenerator/TableGenerator definition
240 20
                    if (isset($idElement->{'sequence-generator'})) {
241 3
                        $seqGenerator          = $idElement->{'sequence-generator'};
242
                        $idGeneratorDefinition = [
243 3
                            'sequenceName'   => (string) $seqGenerator['sequence-name'],
244 3
                            'allocationSize' => (string) $seqGenerator['allocation-size'],
245
                        ];
246 17
                    } elseif (isset($idElement->{'custom-id-generator'})) {
247 2
                        $customGenerator = $idElement->{'custom-id-generator'};
248
249
                        $idGeneratorDefinition = [
250 2
                            'class'     => (string) $customGenerator['class'],
251
                            'arguments' => [],
252
                        ];
253
254 2
                        if (! isset($idGeneratorDefinition['class'])) {
255
                            throw new Mapping\MappingException(
256
                                sprintf('Cannot instantiate custom generator, no class has been defined')
257
                            );
258
                        }
259
260 2
                        if (! class_exists($idGeneratorDefinition['class'])) {
261
                            throw new Mapping\MappingException(
262 2
                                sprintf('Cannot instantiate custom generator : %s', var_export($idGeneratorDefinition, true))
263
                            );
264
                        }
265 15
                    } elseif (isset($idElement->{'table-generator'})) {
266
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
267
                    }
268
269 20
                    $fieldMetadata->setValueGenerator(
270 20
                        new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition)
271
                    );
272
                }
273
            }
274
275 28
            $metadata->addProperty($fieldMetadata);
276
        }
277
278
        // Evaluate <one-to-one ...> mappings
279 34
        if (isset($xmlRoot->{'one-to-one'})) {
280 7
            foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
281 7
                $association  = new Mapping\OneToOneAssociationMetadata((string) $oneToOneElement['field']);
282 7
                $targetEntity = (string) $oneToOneElement['target-entity'];
283
284 7
                $association->setTargetEntity($targetEntity);
285
286 7
                if (isset($associationIds[$association->getName()])) {
287
                    $association->setPrimaryKey(true);
288
                }
289
290 7
                if (isset($oneToOneElement->cascade)) {
291 7
                    $association->setCascade($this->getCascadeMappings($oneToOneElement->cascade));
292
                }
293
294 7
                if (isset($oneToOneElement['orphan-removal'])) {
295
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToOneElement['orphan-removal']));
296
                }
297
298 7
                if (isset($oneToOneElement['fetch'])) {
299 3
                    $association->setFetchMode(
300 3
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToOneElement['fetch']))
301
                    );
302
                }
303
304 7
                if (isset($oneToOneElement['mapped-by'])) {
305 3
                    $association->setMappedBy((string) $oneToOneElement['mapped-by']);
306 3
                    $association->setOwningSide(false);
307
                }
308
309 7
                if (isset($oneToOneElement['inversed-by'])) {
310 4
                    $association->setInversedBy((string) $oneToOneElement['inversed-by']);
311 4
                    $association->setOwningSide(true);
312
                }
313
314
                // Evaluate second level cache
315 7
                if (isset($oneToOneElement->cache)) {
316
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
317
318
                    $cacheBuilder
319
                        ->withComponentMetadata($metadata)
320
                        ->withFieldName($association->getName())
321
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($oneToOneElement->cache));
322
323
                    $association->setCache($cacheBuilder->build());
324
                }
325
326
                // Check for owning side to consider join column
327 7
                if (! $association->isOwningSide()) {
328 3
                    $metadata->addProperty($association);
329
330 3
                    continue;
331
                }
332
333
                // Check for JoinColumn/JoinColumns annotations
334 5
                $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
335
336
                $joinColumnBuilder
337 5
                    ->withComponentMetadata($metadata)
338 5
                    ->withFieldName($association->getName());
339
340
                switch (true) {
341 5
                    case isset($oneToOneElement->{'join-column'}):
342 5
                        $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($oneToOneElement->{'join-column'});
343
344 5
                        $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
345
346 5
                        $association->addJoinColumn($joinColumnBuilder->build());
347 5
                        break;
348
349
                    case isset($oneToOneElement->{'join-columns'}):
350
                        foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
351
                            $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
352
353
                            $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
354
355
                            $association->addJoinColumn($joinColumnBuilder->build());
356
                        }
357
358
                        break;
359
360
                    default:
361
                        $association->addJoinColumn($joinColumnBuilder->build());
362
                        break;
363
                }
364
365 5
                $metadata->addProperty($association);
366
            }
367
        }
368
369
        // Evaluate <one-to-many ...> mappings
370 34
        if (isset($xmlRoot->{'one-to-many'})) {
371 9
            foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
372 9
                $association  = new Mapping\OneToManyAssociationMetadata((string) $oneToManyElement['field']);
373 9
                $targetEntity = (string) $oneToManyElement['target-entity'];
374
375 9
                $association->setTargetEntity($targetEntity);
376 9
                $association->setOwningSide(false);
377 9
                $association->setMappedBy((string) $oneToManyElement['mapped-by']);
378
379 9
                if (isset($associationIds[$association->getName()])) {
380
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
381
                }
382
383 9
                if (isset($oneToManyElement['fetch'])) {
384
                    $association->setFetchMode(
385
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToManyElement['fetch']))
386
                    );
387
                }
388
389 9
                if (isset($oneToManyElement->cascade)) {
390 6
                    $association->setCascade($this->getCascadeMappings($oneToManyElement->cascade));
391
                }
392
393 9
                if (isset($oneToManyElement['orphan-removal'])) {
394 3
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToManyElement['orphan-removal']));
395
                }
396
397 9
                if (isset($oneToManyElement->{'order-by'})) {
398 5
                    $orderBy = [];
399
400 5
                    foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
401 5
                        $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
402 4
                            ? (string) $orderByField['direction']
403 1
                            : Criteria::ASC;
404
                    }
405
406 5
                    $association->setOrderBy($orderBy);
407
                }
408
409 9
                if (isset($oneToManyElement['index-by'])) {
410 3
                    $association->setIndexedBy((string) $oneToManyElement['index-by']);
411 6
                } elseif (isset($oneToManyElement->{'index-by'})) {
412
                    throw new InvalidArgumentException('<index-by /> is not a valid tag');
413
                }
414
415
                // Evaluate second level cache
416 9
                if (isset($oneToManyElement->cache)) {
417 1
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
418
419
                    $cacheBuilder
420 1
                        ->withComponentMetadata($metadata)
421 1
                        ->withFieldName($association->getName())
422 1
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($oneToManyElement->cache));
423
424 1
                    $association->setCache($cacheBuilder->build());
425
                }
426
427 9
                $metadata->addProperty($association);
428
            }
429
        }
430
431
        // Evaluate <many-to-one ...> mappings
432 34
        if (isset($xmlRoot->{'many-to-one'})) {
433 8
            foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
434 8
                $association  = new Mapping\ManyToOneAssociationMetadata((string) $manyToOneElement['field']);
435 8
                $targetEntity = (string) $manyToOneElement['target-entity'];
436
437 8
                $association->setTargetEntity($targetEntity);
438
439 8
                if (isset($associationIds[$association->getName()])) {
440 2
                    $association->setPrimaryKey(true);
441
                }
442
443 8
                if (isset($manyToOneElement['fetch'])) {
444
                    $association->setFetchMode(
445
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $manyToOneElement['fetch'])
446
                    );
447
                }
448
449 8
                if (isset($manyToOneElement->cascade)) {
450 4
                    $association->setCascade($this->getCascadeMappings($manyToOneElement->cascade));
451
                }
452
453 8
                if (isset($manyToOneElement['inversed-by'])) {
454 2
                    $association->setInversedBy((string) $manyToOneElement['inversed-by']);
455
                }
456
457
                // Evaluate second level cache
458 8
                if (isset($manyToOneElement->cache)) {
459 1
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
460
461
                    $cacheBuilder
462 1
                        ->withComponentMetadata($metadata)
463 1
                        ->withFieldName($association->getName())
464 1
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($manyToOneElement->cache));
465
466 1
                    $association->setCache($cacheBuilder->build());
467
                }
468
469
                // Check for JoinColumn/JoinColumns annotations
470 8
                $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
471
472
                $joinColumnBuilder
473 8
                    ->withComponentMetadata($metadata)
474 8
                    ->withFieldName($association->getName());
475
476
                switch (true) {
477 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...
478
                        $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($oneToOneElement->{'join-column'});
479
480
                        $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
481
482
                        $association->addJoinColumn($joinColumnBuilder->build());
483
                        break;
484
485 8
                    case isset($oneToOneElement->{'join-columns'}):
486
                        foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
487
                            $joinColumnAnnotation = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
488
489
                            $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
490
491
                            $association->addJoinColumn($joinColumnBuilder->build());
492
                        }
493
494
                        break;
495
496
                    default:
497 8
                        $association->addJoinColumn($joinColumnBuilder->build());
498 8
                        break;
499
                }
500
501 8
                $metadata->addProperty($association);
502
            }
503
        }
504
505
        // Evaluate <many-to-many ...> mappings
506 33
        if (isset($xmlRoot->{'many-to-many'})) {
507 14
            foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
508 14
                $association  = new Mapping\ManyToManyAssociationMetadata((string) $manyToManyElement['field']);
509 14
                $targetEntity = (string) $manyToManyElement['target-entity'];
510
511 14
                $association->setTargetEntity($targetEntity);
512
513 14
                if (isset($associationIds[$association->getName()])) {
514
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
515
                }
516
517 14
                if (isset($manyToManyElement['fetch'])) {
518 4
                    $association->setFetchMode(
519 4
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $manyToManyElement['fetch']))
520
                    );
521
                }
522
523 14
                if (isset($manyToManyElement->cascade)) {
524 8
                    $association->setCascade($this->getCascadeMappings($manyToManyElement->cascade));
525
                }
526
527 14
                if (isset($manyToManyElement['orphan-removal'])) {
528
                    $association->setOrphanRemoval($this->evaluateBoolean($manyToManyElement['orphan-removal']));
529
                }
530
531 14
                if (isset($manyToManyElement['mapped-by'])) {
532 5
                    $association->setMappedBy((string) $manyToManyElement['mapped-by']);
533 5
                    $association->setOwningSide(false);
534
                }
535
536 14
                if (isset($manyToManyElement['inversed-by'])) {
537 6
                    $association->setInversedBy((string) $manyToManyElement['inversed-by']);
538
                }
539
540 14
                if (isset($manyToManyElement['index-by'])) {
541
                    $association->setIndexedBy((string) $manyToManyElement['index-by']);
542 14
                } elseif (isset($manyToManyElement->{'index-by'})) {
543
                    throw new InvalidArgumentException('<index-by /> is not a valid tag');
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
                // Check for cache
559 14
                if (isset($manyToManyElement->cache)) {
560
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
561
562
                    $cacheBuilder
563
                        ->withComponentMetadata($metadata)
564
                        ->withFieldName($association->getName())
565
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($manyToManyElement->cache));
566
567
                    $association->setCache($cacheBuilder->build());
568
                }
569
570
                // Check for owning side to consider join column
571 14
                if (! $association->isOwningSide()) {
572 5
                    $metadata->addProperty($association);
573
574 5
                    continue;
575
                }
576
577 10
                $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
578
579
                $joinTableBuilder
580 10
                    ->withComponentMetadata($metadata)
581 10
                    ->withFieldName($association->getName())
582 10
                    ->withTargetEntity($targetEntity);
583
584 10
                if (isset($manyToManyElement->{'join-table'})) {
585 8
                    $joinTableElement    = $manyToManyElement->{'join-table'};
586 8
                    $joinTableAnnotation = $this->convertJoinTableElementToJoinTableAnnotation($joinTableElement);
587
588 8
                    $joinTableBuilder->withJoinTableAnnotation($joinTableAnnotation);
589
                }
590
591 10
                $association->setJoinTable($joinTableBuilder->build());
592
593 10
                $metadata->addProperty($association);
594
            }
595
        }
596
597
        // Evaluate association-overrides
598 33
        if (isset($xmlRoot->{'attribute-overrides'})) {
599 2
            foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) {
600 2
                $fieldName = (string) $overrideElement['name'];
601
602 2
                foreach ($overrideElement->field as $fieldElement) {
603 2
                    $fieldMetadata = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, $metadata, $metadataBuildingContext);
604
605 2
                    $metadata->setPropertyOverride($fieldMetadata);
606
                }
607
            }
608
        }
609
610
        // Evaluate association-overrides
611 33
        if (isset($xmlRoot->{'association-overrides'})) {
612 4
            foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) {
613 4
                $fieldName = (string) $overrideElement['name'];
614 4
                $property  = $metadata->getProperty($fieldName);
615
616 4
                if (! $property) {
617
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
618
                }
619
620 4
                $existingClass = get_class($property);
621 4
                $override      = new $existingClass($fieldName);
622
623 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

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