Passed
Push — master ( 3cda3f...c35142 )
by Guilherme
09:24
created

XmlDriver::evaluateBoolean()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 2
rs 10
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
                ->withEntityClassMetadata($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
                $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
147
148 10
                $discriminatorColumn->setTableName($metadata->getTableName());
149 10
                $discriminatorColumn->setColumnName('dtype');
150 10
                $discriminatorColumn->setType(Type::getType('string'));
151 10
                $discriminatorColumn->setLength(255);
152
153
                // Evaluate <discriminator-column...>
154 10
                if (isset($xmlRoot->{'discriminator-column'})) {
155 8
                    $discriminatorColumnMapping = $xmlRoot->{'discriminator-column'};
156 8
                    $typeName                   = (string) ($discriminatorColumnMapping['type'] ?? 'string');
157
158 8
                    $discriminatorColumn->setType(Type::getType($typeName));
159 8
                    $discriminatorColumn->setColumnName((string) $discriminatorColumnMapping['name']);
160
161 8
                    if (isset($discriminatorColumnMapping['column-definition'])) {
162 1
                        $discriminatorColumn->setColumnDefinition((string) $discriminatorColumnMapping['column-definition']);
163
                    }
164
165 8
                    if (isset($discriminatorColumnMapping['length'])) {
166 3
                        $discriminatorColumn->setLength((int) $discriminatorColumnMapping['length']);
167
                    }
168
                }
169
170 10
                $metadata->setDiscriminatorColumn($discriminatorColumn);
171
172
                // Evaluate <discriminator-map...>
173 10
                if (isset($xmlRoot->{'discriminator-map'})) {
174 10
                    $map = [];
175
176 10
                    foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
177 10
                        $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
178
                    }
179
180 10
                    $metadata->setDiscriminatorMap($map);
181
                }
182
            }
183
        }
184
185
        // Evaluate <change-tracking-policy...>
186 34
        if (isset($xmlRoot['change-tracking-policy'])) {
187
            $changeTrackingPolicy = strtoupper((string) $xmlRoot['change-tracking-policy']);
188
189
            $metadata->setChangeTrackingPolicy(
190
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingPolicy))
191
            );
192
        }
193
194
        // Evaluate <field ...> mappings
195 34
        if (isset($xmlRoot->field)) {
196 20
            foreach ($xmlRoot->field as $fieldElement) {
197 20
                $fieldName     = (string) $fieldElement['name'];
198 20
                $fieldMetadata = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, $metadata, $metadataBuildingContext);
199
200 20
                $metadata->addProperty($fieldMetadata);
201
            }
202
        }
203
204 34
        if (isset($xmlRoot->embedded)) {
205
            foreach ($xmlRoot->embedded as $embeddedMapping) {
206
                $columnPrefix = isset($embeddedMapping['column-prefix'])
207
                    ? (string) $embeddedMapping['column-prefix']
208
                    : null;
209
210
                $useColumnPrefix = isset($embeddedMapping['use-column-prefix'])
211
                    ? $this->evaluateBoolean($embeddedMapping['use-column-prefix'])
212
                    : true;
213
214
                $mapping = [
215
                    'fieldName'    => (string) $embeddedMapping['name'],
216
                    'class'        => (string) $embeddedMapping['class'],
217
                    'columnPrefix' => $useColumnPrefix ? $columnPrefix : false,
218
                ];
219
220
                $metadata->mapEmbedded($mapping);
221
            }
222
        }
223
224
        // Evaluate <id ...> mappings
225 34
        $associationIds = [];
226
227 34
        foreach ($xmlRoot->id as $idElement) {
228 29
            $fieldName = (string) $idElement['name'];
229
230 29
            if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) {
231 2
                $associationIds[$fieldName] = true;
232
233 2
                continue;
234
            }
235
236 28
            $fieldMetadata = $this->convertFieldElementToFieldMetadata($idElement, $fieldName, $metadata, $metadataBuildingContext);
237
238 28
            $fieldMetadata->setPrimaryKey(true);
239
240
            // Prevent PK and version on same field
241 28
            if ($fieldMetadata->isVersioned()) {
242
                throw Mapping\MappingException::cannotVersionIdField($className, $fieldName);
243
            }
244
245 28
            if (isset($idElement->generator)) {
246 27
                $strategy = (string) ($idElement->generator['strategy'] ?? 'AUTO');
247
248 27
                $idGeneratorType = constant(sprintf('%s::%s', Mapping\GeneratorType::class, strtoupper($strategy)));
249
250 27
                if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
251 20
                    $idGeneratorDefinition = [];
252
253
                    // Check for SequenceGenerator/TableGenerator definition
254 20
                    if (isset($idElement->{'sequence-generator'})) {
255 3
                        $seqGenerator          = $idElement->{'sequence-generator'};
256
                        $idGeneratorDefinition = [
257 3
                            'sequenceName'   => (string) $seqGenerator['sequence-name'],
258 3
                            'allocationSize' => (string) $seqGenerator['allocation-size'],
259
                        ];
260 17
                    } elseif (isset($idElement->{'custom-id-generator'})) {
261 2
                        $customGenerator = $idElement->{'custom-id-generator'};
262
263
                        $idGeneratorDefinition = [
264 2
                            'class'     => (string) $customGenerator['class'],
265
                            'arguments' => [],
266
                        ];
267
268 2
                        if (! isset($idGeneratorDefinition['class'])) {
269
                            throw new Mapping\MappingException(
270
                                sprintf('Cannot instantiate custom generator, no class has been defined')
271
                            );
272
                        }
273
274 2
                        if (! class_exists($idGeneratorDefinition['class'])) {
275
                            throw new Mapping\MappingException(
276 2
                                sprintf('Cannot instantiate custom generator : %s', var_export($idGeneratorDefinition, true))
277
                            );
278
                        }
279 15
                    } elseif (isset($idElement->{'table-generator'})) {
280
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
281
                    }
282
283 20
                    $fieldMetadata->setValueGenerator(
284 20
                        new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition)
285
                    );
286
                }
287
            }
288
289 28
            $metadata->addProperty($fieldMetadata);
290
        }
291
292
        // Evaluate <one-to-one ...> mappings
293 34
        if (isset($xmlRoot->{'one-to-one'})) {
294 7
            foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
295 7
                $association  = new Mapping\OneToOneAssociationMetadata((string) $oneToOneElement['field']);
296 7
                $targetEntity = (string) $oneToOneElement['target-entity'];
297
298 7
                $association->setTargetEntity($targetEntity);
299
300 7
                if (isset($associationIds[$association->getName()])) {
301
                    $association->setPrimaryKey(true);
302
                }
303
304 7
                if (isset($oneToOneElement['fetch'])) {
305 3
                    $association->setFetchMode(
306 3
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToOneElement['fetch']))
307
                    );
308
                }
309
310 7
                if (isset($oneToOneElement['mapped-by'])) {
311 3
                    $association->setMappedBy((string) $oneToOneElement['mapped-by']);
312 3
                    $association->setOwningSide(false);
313
                } else {
314 5
                    if (isset($oneToOneElement['inversed-by'])) {
315 4
                        $association->setInversedBy((string) $oneToOneElement['inversed-by']);
316
                    }
317
318 5
                    $joinColumns = [];
319
320 5
                    if (isset($oneToOneElement->{'join-column'})) {
321 5
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($oneToOneElement->{'join-column'});
322
                    } elseif (isset($oneToOneElement->{'join-columns'})) {
323
                        foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
324
                            $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
325
                        }
326
                    }
327
328 5
                    $association->setJoinColumns($joinColumns);
329
                }
330
331 7
                if (isset($oneToOneElement->cascade)) {
332 7
                    $association->setCascade($this->getCascadeMappings($oneToOneElement->cascade));
333
                }
334
335 7
                if (isset($oneToOneElement['orphan-removal'])) {
336
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToOneElement['orphan-removal']));
337
                }
338
339
                // Evaluate second level cache
340 7
                if (isset($oneToOneElement->cache)) {
341
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
342
343
                    $cacheBuilder
344
                        ->withEntityClassMetadata($metadata)
345
                        ->withFieldName($association->getName())
346
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($oneToOneElement->cache));
347
348
                    $association->setCache($cacheBuilder->build());
349
                }
350
351 7
                $metadata->addProperty($association);
352
            }
353
        }
354
355
        // Evaluate <one-to-many ...> mappings
356 34
        if (isset($xmlRoot->{'one-to-many'})) {
357 9
            foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
358 9
                $association  = new Mapping\OneToManyAssociationMetadata((string) $oneToManyElement['field']);
359 9
                $targetEntity = (string) $oneToManyElement['target-entity'];
360
361 9
                $association->setTargetEntity($targetEntity);
362 9
                $association->setOwningSide(false);
363 9
                $association->setMappedBy((string) $oneToManyElement['mapped-by']);
364
365 9
                if (isset($associationIds[$association->getName()])) {
366
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
367
                }
368
369 9
                if (isset($oneToManyElement['fetch'])) {
370
                    $association->setFetchMode(
371
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToManyElement['fetch']))
372
                    );
373
                }
374
375 9
                if (isset($oneToManyElement->cascade)) {
376 6
                    $association->setCascade($this->getCascadeMappings($oneToManyElement->cascade));
377
                }
378
379 9
                if (isset($oneToManyElement['orphan-removal'])) {
380 3
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToManyElement['orphan-removal']));
381
                }
382
383 9
                if (isset($oneToManyElement->{'order-by'})) {
384 5
                    $orderBy = [];
385
386 5
                    foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
387 5
                        $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
388 4
                            ? (string) $orderByField['direction']
389 1
                            : Criteria::ASC;
390
                    }
391
392 5
                    $association->setOrderBy($orderBy);
393
                }
394
395 9
                if (isset($oneToManyElement['index-by'])) {
396 3
                    $association->setIndexedBy((string) $oneToManyElement['index-by']);
397 6
                } elseif (isset($oneToManyElement->{'index-by'})) {
398
                    throw new InvalidArgumentException('<index-by /> is not a valid tag');
399
                }
400
401
                // Evaluate second level cache
402 9
                if (isset($oneToManyElement->cache)) {
403 1
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
404
405
                    $cacheBuilder
406 1
                        ->withEntityClassMetadata($metadata)
407 1
                        ->withFieldName($association->getName())
408 1
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($oneToManyElement->cache));
409
410 1
                    $association->setCache($cacheBuilder->build());
411
                }
412
413 9
                $metadata->addProperty($association);
414
            }
415
        }
416
417
        // Evaluate <many-to-one ...> mappings
418 34
        if (isset($xmlRoot->{'many-to-one'})) {
419 8
            foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
420 8
                $association = new Mapping\ManyToOneAssociationMetadata((string) $manyToOneElement['field']);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

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

To visualize

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

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

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

will produce no issues.

Loading history...
421 8
                $targetEntity = (string) $manyToOneElement['target-entity'];
422
423 8
                $association->setTargetEntity($targetEntity);
424
425 8
                if (isset($associationIds[$association->getName()])) {
426 2
                    $association->setPrimaryKey(true);
427
                }
428
429 8
                if (isset($manyToOneElement['fetch'])) {
430
                    $association->setFetchMode(
431
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $manyToOneElement['fetch'])
432
                    );
433
                }
434
435 8
                if (isset($manyToOneElement['inversed-by'])) {
436 2
                    $association->setInversedBy((string) $manyToOneElement['inversed-by']);
437
                }
438
439 8
                $joinColumns = [];
440
441 8
                if (isset($manyToOneElement->{'join-column'})) {
442 7
                    $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($manyToOneElement->{'join-column'});
443 1
                } elseif (isset($manyToOneElement->{'join-columns'})) {
444 1
                    foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
445 1
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
446
                    }
447
                }
448
449 8
                $association->setJoinColumns($joinColumns);
450
451 8
                if (isset($manyToOneElement->cascade)) {
452 4
                    $association->setCascade($this->getCascadeMappings($manyToOneElement->cascade));
453
                }
454
455
                // Evaluate second level cache
456 8
                if (isset($manyToOneElement->cache)) {
457 1
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
458
459
                    $cacheBuilder
460 1
                        ->withEntityClassMetadata($metadata)
461 1
                        ->withFieldName($association->getName())
462 1
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($manyToOneElement->cache));
463
464 1
                    $association->setCache($cacheBuilder->build());
465
                }
466
467 8
                $metadata->addProperty($association);
468
            }
469
        }
470
471
        // Evaluate <many-to-many ...> mappings
472 33
        if (isset($xmlRoot->{'many-to-many'})) {
473 14
            foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
474 14
                $association  = new Mapping\ManyToManyAssociationMetadata((string) $manyToManyElement['field']);
475 14
                $targetEntity = (string) $manyToManyElement['target-entity'];
476
477 14
                $association->setTargetEntity($targetEntity);
478
479 14
                if (isset($associationIds[$association->getName()])) {
480
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
481
                }
482
483 14
                if (isset($manyToManyElement['fetch'])) {
484 4
                    $association->setFetchMode(
485 4
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $manyToManyElement['fetch']))
486
                    );
487
                }
488
489 14
                if (isset($manyToManyElement['orphan-removal'])) {
490
                    $association->setOrphanRemoval($this->evaluateBoolean($manyToManyElement['orphan-removal']));
491
                }
492
493 14
                if (isset($manyToManyElement['mapped-by'])) {
494 5
                    $association->setMappedBy((string) $manyToManyElement['mapped-by']);
495 5
                    $association->setOwningSide(false);
496 10
                } elseif (isset($manyToManyElement->{'join-table'})) {
497 8
                    if (isset($manyToManyElement['inversed-by'])) {
498 5
                        $association->setInversedBy((string) $manyToManyElement['inversed-by']);
499
                    }
500
501 8
                    $joinTableElement = $manyToManyElement->{'join-table'};
502 8
                    $joinTable        = new Mapping\JoinTableMetadata();
503
504 8
                    if (isset($joinTableElement['name'])) {
505 8
                        $joinTable->setName((string) $joinTableElement['name']);
506
                    }
507
508 8
                    if (isset($joinTableElement['schema'])) {
509
                        $joinTable->setSchema((string) $joinTableElement['schema']);
510
                    }
511
512 8
                    if (isset($joinTableElement->{'join-columns'})) {
513 8
                        foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
514 8
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
515
516 8
                            $joinTable->addJoinColumn($joinColumn);
517
                        }
518
                    }
519
520 8
                    if (isset($joinTableElement->{'inverse-join-columns'})) {
521 8
                        foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
522 8
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
523
524 8
                            $joinTable->addInverseJoinColumn($joinColumn);
525
                        }
526
                    }
527
528 8
                    $association->setJoinTable($joinTable);
529
                }
530
531 14
                if (isset($manyToManyElement->cascade)) {
532 8
                    $association->setCascade($this->getCascadeMappings($manyToManyElement->cascade));
533
                }
534
535 14
                if (isset($manyToManyElement->{'order-by'})) {
536 1
                    $orderBy = [];
537
538 1
                    foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
539 1
                        $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
540
                            ? (string) $orderByField['direction']
541 1
                            : Criteria::ASC;
542
                    }
543
544 1
                    $association->setOrderBy($orderBy);
545
                }
546
547 14
                if (isset($manyToManyElement['index-by'])) {
548
                    $association->setIndexedBy((string) $manyToManyElement['index-by']);
549 14
                } elseif (isset($manyToManyElement->{'index-by'})) {
550
                    throw new InvalidArgumentException('<index-by /> is not a valid tag');
551
                }
552
553
                // Evaluate second level cache
554 14
                if (isset($manyToManyElement->cache)) {
555
                    $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
556
557
                    $cacheBuilder
558
                        ->withEntityClassMetadata($metadata)
559
                        ->withFieldName($association->getName())
560
                        ->withCacheAnnotation($this->convertCacheElementToCacheAnnotation($manyToManyElement->cache));
561
562
                    $association->setCache($cacheBuilder->build());
563
                }
564
565 14
                $metadata->addProperty($association);
566
            }
567
        }
568
569
        // Evaluate association-overrides
570 33
        if (isset($xmlRoot->{'attribute-overrides'})) {
571 2
            foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) {
572 2
                $fieldName = (string) $overrideElement['name'];
573
574 2
                foreach ($overrideElement->field as $fieldElement) {
575 2
                    $fieldMetadata = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, $metadata, $metadataBuildingContext);
576
577 2
                    $metadata->setPropertyOverride($fieldMetadata);
578
                }
579
            }
580
        }
581
582
        // Evaluate association-overrides
583 33
        if (isset($xmlRoot->{'association-overrides'})) {
584 4
            foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) {
585 4
                $fieldName = (string) $overrideElement['name'];
586 4
                $property  = $metadata->getProperty($fieldName);
587
588 4
                if (! $property) {
589
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
590
                }
591
592 4
                $existingClass = get_class($property);
593 4
                $override      = new $existingClass($fieldName);
594
595
                // Check for join-columns
596 4
                if (isset($overrideElement->{'join-columns'})) {
597 2
                    $joinColumns = [];
598
599 2
                    foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
600 2
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
601
                    }
602
603 2
                    $override->setJoinColumns($joinColumns);
604
                }
605
606
                // Check for join-table
607 4
                if ($overrideElement->{'join-table'}) {
608 2
                    $joinTableElement = $overrideElement->{'join-table'};
609 2
                    $joinTable        = new Mapping\JoinTableMetadata();
610
611 2
                    if (isset($joinTableElement['name'])) {
612 2
                        $joinTable->setName((string) $joinTableElement['name']);
613
                    }
614
615 2
                    if (isset($joinTableElement['schema'])) {
616
                        $joinTable->setSchema((string) $joinTableElement['schema']);
617
                    }
618
619 2
                    if (isset($joinTableElement->{'join-columns'})) {
620 2
                        foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
621 2
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
622
623 2
                            $joinTable->addJoinColumn($joinColumn);
624
                        }
625
                    }
626
627 2
                    if (isset($joinTableElement->{'inverse-join-columns'})) {
628 2
                        foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
629 2
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
630
631 2
                            $joinTable->addInverseJoinColumn($joinColumn);
632
                        }
633
                    }
634
635 2
                    $override->setJoinTable($joinTable);
636
                }
637
638
                // Check for inversed-by
639 4
                if (isset($overrideElement->{'inversed-by'})) {
640 1
                    $override->setInversedBy((string) $overrideElement->{'inversed-by'}['name']);
641
                }
642
643
                // Check for fetch
644 4
                if (isset($overrideElement['fetch'])) {
645 1
                    $override->setFetchMode(
646 1
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $overrideElement['fetch'])
647
                    );
648
                }
649
650 4
                $metadata->setPropertyOverride($override);
651
            }
652
        }
653
654
        // Evaluate <lifecycle-callbacks...>
655 33
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
656 3
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
657 3
                $eventName  = constant(Events::class . '::' . (string) $lifecycleCallback['type']);
658 3
                $methodName = (string) $lifecycleCallback['method'];
659
660 3
                $metadata->addLifecycleCallback($eventName, $methodName);
661
            }
662
        }
663
664
        // Evaluate entity listener
665 33
        if (isset($xmlRoot->{'entity-listeners'})) {
666 2
            foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) {
667 2
                $listenerClassName = (string) $listenerElement['class'];
668
669 2
                if (! class_exists($listenerClassName)) {
670
                    throw Mapping\MappingException::entityListenerClassNotFound(
671
                        $listenerClassName,
672
                        $metadata->getClassName()
673
                    );
674
                }
675
676 2
                foreach ($listenerElement as $callbackElement) {
677 2
                    $eventName  = (string) $callbackElement['type'];
678 2
                    $methodName = (string) $callbackElement['method'];
679
680 2
                    $metadata->addEntityListener($eventName, $listenerClassName, $methodName);
681
                }
682
            }
683
        }
684
685 33
        return $metadata;
686
    }
687
688
    /**
689
     * Parses (nested) index elements.
690
     *
691
     * @param SimpleXMLElement $indexes The XML element.
692
     *
693
     * @return Annotation\Index[] The indexes array.
694
     */
695 4
    private function parseIndexes(SimpleXMLElement $indexes)
696
    {
697 4
        $array = [];
698
699
        /** @var SimpleXMLElement $index */
700 4
        foreach ($indexes as $index) {
701 4
            $indexAnnotation = new Annotation\Index();
702
703 4
            $indexAnnotation->columns = explode(',', (string) $index['columns']);
704 4
            $indexAnnotation->options = isset($index->options) ? $this->parseOptions($index->options->children()) : [];
705 4
            $indexAnnotation->flags   = isset($index['flags']) ? explode(',', (string) $index['flags']) : [];
706
707 4
            if (isset($index['name'])) {
708 3
                $indexAnnotation->name = (string) $index['name'];
709
            }
710
711 4
            if (isset($index['unique'])) {
712
                $indexAnnotation->unique = $this->evaluateBoolean($index['unique']);
713
            }
714
715 4
            $array[] = $indexAnnotation;
716
        }
717
718 4
        return $array;
719
    }
720
721
    /**
722
     * Parses (nested) unique constraint elements.
723
     *
724
     * @param SimpleXMLElement $uniqueConstraints The XML element.
725
     *
726
     * @return Annotation\UniqueConstraint[] The unique constraints array.
727
     */
728 3
    private function parseUniqueConstraints(SimpleXMLElement $uniqueConstraints)
729
    {
730 3
        $array = [];
731
732
        /** @var SimpleXMLElement $uniqueConstraint */
733 3
        foreach ($uniqueConstraints as $uniqueConstraint) {
734 3
            $uniqueConstraintAnnotation = new Annotation\UniqueConstraint();
735
736 3
            $uniqueConstraintAnnotation->columns = explode(',', (string) $uniqueConstraint['columns']);
737 3
            $uniqueConstraintAnnotation->options = isset($uniqueConstraint->options) ? $this->parseOptions($uniqueConstraint->options->children()) : [];
738 3
            $uniqueConstraintAnnotation->flags   = isset($uniqueConstraint['flags']) ? explode(',', (string) $uniqueConstraint['flags']) : [];
739
740 3
            if (isset($uniqueConstraint['name'])) {
741 3
                $uniqueConstraintAnnotation->name = (string) $uniqueConstraint['name'];
742
            }
743
744 3
            $array[] = $uniqueConstraintAnnotation;
745
        }
746
747 3
        return $array;
748
    }
749
750
    /**
751
     * Parses (nested) option elements.
752
     *
753
     * @param SimpleXMLElement $options The XML element.
754
     *
755
     * @return mixed[] The options array.
756
     */
757 4
    private function parseOptions(SimpleXMLElement $options)
758
    {
759 4
        $array = [];
760
761
        /** @var SimpleXMLElement $option */
762 4
        foreach ($options as $option) {
763 4
            if ($option->count()) {
764 3
                $value = $this->parseOptions($option->children());
765
            } else {
766 4
                $value = (string) $option;
767
            }
768
769 4
            $attributes = $option->attributes();
770
771 4
            if (isset($attributes->name)) {
772 4
                $nameAttribute = (string) $attributes->name;
773
774 4
                $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true)
775 3
                    ? $this->evaluateBoolean($value)
776 4
                    : $value;
777
            } else {
778
                $array[] = $value;
779
            }
780
        }
781
782 4
        return $array;
783
    }
784
785
    /**
786
     * @return Mapping\FieldMetadata
787
     *
788
     * @throws Mapping\MappingException
789
     */
790 30
    private function convertFieldElementToFieldMetadata(
791
        SimpleXMLElement $fieldElement,
792
        string $fieldName,
793
        Mapping\ClassMetadata $metadata,
794
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
795
    )
796
    {
0 ignored issues
show
Coding Style introduced by
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
797 30
        $className     = $metadata->getClassName();
798 30
        $isVersioned   = isset($fieldElement['version']) && $fieldElement['version'];
799 30
        $fieldMetadata = new Mapping\FieldMetadata($fieldName);
800 30
        $fieldType     = isset($fieldElement['type']) ? (string) $fieldElement['type'] : 'string';
801 30
        $columnName    = isset($fieldElement['column'])
802 20
            ? (string) $fieldElement['column']
803 30
            : $metadataBuildingContext->getNamingStrategy()->propertyToColumnName($fieldName, $className);
804
805 30
        $fieldMetadata->setType(Type::getType($fieldType));
806 30
        $fieldMetadata->setVersioned($isVersioned);
807 30
        $fieldMetadata->setColumnName($columnName);
808
809 30
        if (isset($fieldElement['length'])) {
810 6
            $fieldMetadata->setLength((int) $fieldElement['length']);
811
        }
812
813 30
        if (isset($fieldElement['precision'])) {
814 1
            $fieldMetadata->setPrecision((int) $fieldElement['precision']);
815
        }
816
817 30
        if (isset($fieldElement['scale'])) {
818 1
            $fieldMetadata->setScale((int) $fieldElement['scale']);
819
        }
820
821 30
        if (isset($fieldElement['unique'])) {
822 7
            $fieldMetadata->setUnique($this->evaluateBoolean($fieldElement['unique']));
823
        }
824
825 30
        if (isset($fieldElement['nullable'])) {
826 7
            $fieldMetadata->setNullable($this->evaluateBoolean($fieldElement['nullable']));
827
        }
828
829 30
        if (isset($fieldElement['column-definition'])) {
830 4
            $fieldMetadata->setColumnDefinition((string) $fieldElement['column-definition']);
831
        }
832
833 30
        if (isset($fieldElement->options)) {
834 3
            $fieldMetadata->setOptions($this->parseOptions($fieldElement->options->children()));
835
        }
836
837
        // Prevent column duplication
838 30
        if ($metadata->checkPropertyDuplication($columnName)) {
839
            throw Mapping\MappingException::duplicateColumnName($className, $columnName);
840
        }
841
842 30
        return $fieldMetadata;
843
    }
844
845
    /**
846
     * Constructs a joinColumn mapping array based on the information
847
     * found in the given SimpleXMLElement.
848
     *
849
     * @param SimpleXMLElement $joinColumnElement The XML element.
850
     *
851
     * @return Mapping\JoinColumnMetadata
852
     */
853 13
    private function convertJoinColumnElementToJoinColumnMetadata(SimpleXMLElement $joinColumnElement)
854
    {
855 13
        $joinColumnMetadata = new Mapping\JoinColumnMetadata();
856
857 13
        $joinColumnMetadata->setColumnName((string) $joinColumnElement['name']);
858 13
        $joinColumnMetadata->setReferencedColumnName((string) $joinColumnElement['referenced-column-name']);
859
860 13
        if (isset($joinColumnElement['column-definition'])) {
861 3
            $joinColumnMetadata->setColumnDefinition((string) $joinColumnElement['column-definition']);
862
        }
863
864 13
        if (isset($joinColumnElement['field-name'])) {
865
            $joinColumnMetadata->setAliasedName((string) $joinColumnElement['field-name']);
866
        }
867
868 13
        if (isset($joinColumnElement['nullable'])) {
869 4
            $joinColumnMetadata->setNullable($this->evaluateBoolean($joinColumnElement['nullable']));
870
        }
871
872 13
        if (isset($joinColumnElement['unique'])) {
873 3
            $joinColumnMetadata->setUnique($this->evaluateBoolean($joinColumnElement['unique']));
874
        }
875
876 13
        if (isset($joinColumnElement['on-delete'])) {
877 3
            $joinColumnMetadata->setOnDelete(strtoupper((string) $joinColumnElement['on-delete']));
878
        }
879
880 13
        return $joinColumnMetadata;
881
    }
882
883
    /**
884
     * Parse the given Cache as CacheMetadata
885
     */
886 2
    private function convertCacheElementToCacheAnnotation(SimpleXMLElement $cacheMapping): Annotation\Cache
0 ignored issues
show
introduced by
There must be exactly 1 whitespace between closing parenthesis and return type colon.
Loading history...
887
    {
888 2
        $cacheAnnotation = new Annotation\Cache();
889
890 2
        if (isset($cacheMapping['region'])) {
891
            $cacheAnnotation->region = (string) $cacheMapping['region'];
892
        }
893
894 2
        if (isset($cacheMapping['usage'])) {
895 2
            $cacheAnnotation->usage = strtoupper((string) $cacheMapping['usage']);
896
        }
897
898 2
        return $cacheAnnotation;
899
    }
900
901
    /**
902
     * Gathers a list of cascade options found in the given cascade element.
903
     *
904
     * @param SimpleXMLElement $cascadeElement The cascade element.
905
     *
906
     * @return string[] The list of cascade options.
907
     */
908 10
    private function getCascadeMappings(SimpleXMLElement $cascadeElement)
909
    {
910 10
        $cascades = [];
911
912
        /** @var SimpleXMLElement $action */
913 10
        foreach ($cascadeElement->children() as $action) {
914
            // According to the JPA specifications, XML uses "cascade-persist"
915
            // instead of "persist". Here, both variations are supported
916
            // because Annotation use "persist" and we want to make sure that
917
            // this driver doesn't need to know anything about the supported
918
            // cascading actions
919 10
            $cascades[] = str_replace('cascade-', '', $action->getName());
920
        }
921
922 10
        return $cascades;
923
    }
924
925
    /**
926
     * {@inheritDoc}
927
     */
928 36
    protected function loadMappingFile($file)
929
    {
930 36
        $result = [];
931
        // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577
932 36
        $xmlElement = simplexml_load_string(file_get_contents($file));
933
934 36
        if (isset($xmlElement->entity)) {
935 35
            foreach ($xmlElement->entity as $entityElement) {
936 35
                $entityName          = (string) $entityElement['name'];
937 35
                $result[$entityName] = $entityElement;
938
            }
939 6
        } elseif (isset($xmlElement->{'mapped-superclass'})) {
940 5
            foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) {
941 5
                $className          = (string) $mappedSuperClass['name'];
942 5
                $result[$className] = $mappedSuperClass;
943
            }
944 1
        } elseif (isset($xmlElement->embeddable)) {
945
            foreach ($xmlElement->embeddable as $embeddableElement) {
946
                $embeddableName          = (string) $embeddableElement['name'];
947
                $result[$embeddableName] = $embeddableElement;
948
            }
949
        }
950
951 36
        return $result;
952
    }
953
954
    /**
955
     * @param mixed $element
956
     *
957
     * @return bool
958
     */
959 9
    protected function evaluateBoolean($element)
960
    {
961 9
        $flag = (string) $element;
962
963 9
        return $flag === 'true' || $flag === '1';
964
    }
965
}
966