Test Failed
Pull Request — develop (#6728)
by Grégoire
65:40
created

XmlDriver   F

Complexity

Total Complexity 189

Size/Duplication

Total Lines 986
Duplicated Lines 9.53 %

Coupling/Cohesion

Components 1
Dependencies 18

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 189
lcom 1
cbo 18
dl 94
loc 986
ccs 368
cts 368
cp 1
rs 1.0434
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
F loadMetadataForClass() 94 709 148
B parseOptions() 0 27 5
F convertFieldElementToFieldMetadata() 0 47 11
B convertJoinColumnElementToJoinColumnMetadata() 0 29 6
A convertCacheElementToCacheMetadata() 0 17 4
A getMethodCallbacks() 0 17 1
A getCascadeMappings() 0 16 2
C loadMappingFile() 0 25 7
A validateMapping() 0 15 2
A evaluateBoolean() 0 6 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like XmlDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XmlDriver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\DBAL\Types\Type;
8
use Doctrine\ORM\Events;
9
use Doctrine\ORM\Mapping;
10
use SimpleXMLElement;
11
12
/**
13
 * XmlDriver is a metadata driver that enables mapping through XML files.
14
 *
15
 * @license 	http://www.opensource.org/licenses/mit-license.php MIT
16
 * @link    	www.doctrine-project.org
17
 * @since   	2.0
18
 * @author		Benjamin Eberlei <[email protected]>
19
 * @author		Guilherme Blanco <[email protected]>
20
 * @author      Jonathan H. Wage <[email protected]>
21
 * @author      Roman Borschel <[email protected]>
22
 */
23
class XmlDriver extends FileDriver
24
{
25
    const DEFAULT_FILE_EXTENSION = '.dcm.xml';
26
27
    /**
28
     * {@inheritDoc}
29
     */
30
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
31
    {
32
        parent::__construct($locator, $fileExtension);
33
    }
34
35
    /**
36
     * {@inheritDoc}
37
     */
38
    public function loadMetadataForClass(
39
        string $className,
40
        Mapping\ClassMetadata $metadata,
41
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
42
    )
43
    {
44
        /* @var \SimpleXMLElement $xmlRoot */
45
        $xmlRoot = $this->getElement($className);
46
47
        if ($xmlRoot->getName() === 'entity') {
48 41
            if (isset($xmlRoot['repository-class'])) {
49
                $metadata->setCustomRepositoryClassName(
50 41
                    $metadata->fullyQualifiedClassName((string) $xmlRoot['repository-class'])
51 41
                );
52
            }
53
54
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
55
                $metadata->asReadOnly();
56 36
            }
57
        } else if ($xmlRoot->getName() === 'mapped-superclass') {
58
            if (isset($xmlRoot['repository-class'])) {
59
                $metadata->setCustomRepositoryClassName(
60 36
                    $metadata->fullyQualifiedClassName((string) $xmlRoot['repository-class'])
61
                );
62 34
            }
63 33
64
            $metadata->isMappedSuperclass = true;
65
        } else if ($xmlRoot->getName() === 'embeddable') {
66 33
            $metadata->isEmbeddedClass = true;
67 33
        } else {
68
            throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
69 6
        }
70 5
71 5
        // Process table information
72
        $parent = $metadata->getParent();
73 5
74 1
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
0 ignored issues
show
Bug introduced by
The property inheritanceType does not seem to exist in Doctrine\ORM\Mapping\ComponentMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
75 1
            $metadata->setTable($parent->table);
0 ignored issues
show
Bug introduced by
The property table does not seem to exist in Doctrine\ORM\Mapping\ComponentMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
76
        } else {
77
            $namingStrategy = $metadataBuildingContext->getNamingStrategy();
78
            $tableMetadata  = new Mapping\TableMetadata();
79
80
            $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
81 34
82
            // Evaluate <entity...> attributes
83 34
            if (isset($xmlRoot['table'])) {
84 14
                $tableMetadata->setName((string) $xmlRoot['table']);
85
            }
86
87 34
            if (isset($xmlRoot['schema'])) {
88 1
                $tableMetadata->setSchema((string) $xmlRoot['schema']);
89
            }
90
91 34
            if (isset($xmlRoot->options)) {
92
                $options = $this->parseOptions($xmlRoot->options->children());
93
94 34
                foreach ($options as $optionName => $optionValue) {
95 2
                    $tableMetadata->addOption($optionName, $optionValue);
96
                }
97
            }
98
99 34
            // Evaluate <indexes...>
100 3
            if (isset($xmlRoot->indexes)) {
101 3
                foreach ($xmlRoot->indexes->index as $indexXml) {
102 3
                    $indexName = isset($indexXml['name']) ? (string) $indexXml['name'] : null;
103 3
                    $columns   = explode(',', (string) $indexXml['columns']);
104
                    $isUnique  = isset($indexXml['unique']) && $indexXml['unique'];
105
                    $options   = isset($indexXml->options) ? $this->parseOptions($indexXml->options->children()) : [];
106
                    $flags     = isset($indexXml['flags']) ? explode(',', (string) $indexXml['flags']) : [];
107
108
                    $tableMetadata->addIndex([
109 34
                        'name'    => $indexName,
110 3
                        'columns' => $columns,
111 3
                        'unique'  => $isUnique,
112 3
                        'options' => $options,
113 3
                        'flags'   => $flags,
114 3
                    ]);
115 3
                }
116
            }
117
118
            // Evaluate <unique-constraints..>
119
120
            if (isset($xmlRoot->{'unique-constraints'})) {
121 34
                foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
122 3
                    $indexName = isset($uniqueXml['name']) ? (string) $uniqueXml['name'] : null;
123 3
                    $columns   = explode(',', (string) $uniqueXml['columns']);
124 3
                    $options   = isset($uniqueXml->options) ? $this->parseOptions($uniqueXml->options->children()) : [];
125
                    $flags     = isset($uniqueXml['flags']) ? explode(',', (string) $uniqueXml['flags']) : [];
126 3
127
                    $tableMetadata->addUniqueConstraint([
128 3
                        'name'    => $indexName,
129
                        'columns' => $columns,
130 3
                        'options' => $options,
131 3
                        'flags'   => $flags,
132 3
                    ]);
133
                }
134
            }
135 3
136 3
            $metadata->setTable($tableMetadata);
137 3
        }
138 3
139
        // Evaluate second level cache
140
        if (isset($xmlRoot->cache)) {
141
            $cache = $this->convertCacheElementToCacheMetadata($xmlRoot->cache, $metadata);
142 3
143
            $metadata->setCache($cache);
144
        }
145
146 3
        // Evaluate named queries
147 3
        if (isset($xmlRoot->{'named-queries'})) {
148 3
            foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) {
149
                $metadata->addNamedQuery((string) $namedQueryElement['name'], (string) $namedQueryElement['query']);
150
            }
151
        }
152
153 3
        // Evaluate native named queries
154 3
        if (isset($xmlRoot->{'named-native-queries'})) {
155 3
            foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) {
156 3
                $metadata->addNamedNativeQuery(
157
                    isset($nativeQueryElement['name']) ? (string) $nativeQueryElement['name'] : null,
158
                    isset($nativeQueryElement->query) ? (string) $nativeQueryElement->query : null,
159
                    [
160
                        'resultClass'      => isset($nativeQueryElement['result-class']) ? (string) $nativeQueryElement['result-class'] : null,
161 34
                        'resultSetMapping' => isset($nativeQueryElement['result-set-mapping']) ? (string) $nativeQueryElement['result-set-mapping'] : null,
162 10
                    ]
163 10
                );
164
            }
165 10
        }
166 10
167
        // Evaluate sql result set mapping
168 10
        if (isset($xmlRoot->{'sql-result-set-mappings'})) {
169
            foreach ($xmlRoot->{'sql-result-set-mappings'}->{'sql-result-set-mapping'} as $rsmElement) {
170
                $entities   = [];
171 10
                $columns    = [];
172 7
173
                foreach ($rsmElement as $entityElement) {
174 7
                    //<entity-result/>
175 7
                    if (isset($entityElement['entity-class'])) {
176 7
                        $entityResult = [
177 7
                            'fields'                => [],
178
                            'entityClass'           => (string) $entityElement['entity-class'],
179 6
                            'discriminatorColumn'   => isset($entityElement['discriminator-column']) ? (string) $entityElement['discriminator-column'] : null,
180 6
                        ];
181 6
182
                        foreach ($entityElement as $fieldElement) {
183
                            $entityResult['fields'][] = [
184 10
                                'name'      => isset($fieldElement['name']) ? (string) $fieldElement['name'] : null,
185
                                'column'    => isset($fieldElement['column']) ? (string) $fieldElement['column'] : null,
186
                            ];
187 10
                        }
188 10
189
                        $entities[] = $entityResult;
190 10
                    }
191 10
192
                    //<column-result/>
193
                    if (isset($entityElement['name'])) {
194 10
                        $columns[] = [
195
                            'name' => (string) $entityElement['name'],
196
                        ];
197
                    }
198
                }
199
200
                $metadata->addSqlResultSetMapping(
201 34
                    [
202
                        'name'          => (string) $rsmElement['name'],
203
                        'entities'      => $entities,
204
                        'columns'       => $columns
205
                    ]
206
                );
207
            }
208 34
        }
209 3
210
        if (isset($xmlRoot['inheritance-type'])) {
211 3
            $inheritanceType = strtoupper((string) $xmlRoot['inheritance-type']);
212 3
213
            $metadata->setInheritanceType(
214 3
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceType))
215 1
            );
216
217
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
218 3
                $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
219 1
220
                $discriminatorColumn->setTableName($metadata->getTableName());
221
                $discriminatorColumn->setColumnName('dtype');
222 3
                $discriminatorColumn->setType(Type::getType('string'));
223 2
                $discriminatorColumn->setLength(255);
224
225 3
                // Evaluate <discriminator-column...>
226
                if (isset($xmlRoot->{'discriminator-column'})) {
227
                    $discriminatorColumnMapping = $xmlRoot->{'discriminator-column'};
228
                    $typeName                   = isset($discriminatorColumnMapping['type'])
229
                        ? (string) $discriminatorColumnMapping['type']
230
                        : 'string';
231 34
232 2
                    $discriminatorColumn->setType(Type::getType($typeName));
233
                    $discriminatorColumn->setColumnName((string) $discriminatorColumnMapping['name']);
234 2
235 2
                    if (isset($discriminatorColumnMapping['column-definition'])) {
236
                        $discriminatorColumn->setColumnDefinition((string) $discriminatorColumnMapping['column-definition']);
237
                    }
238 2
239 2
                    if (isset($discriminatorColumnMapping['length'])) {
240
                        $discriminatorColumn->setLength((int) $discriminatorColumnMapping['length']);
241
                    }
242 2
                }
243 2
244
                $metadata->setDiscriminatorColumn($discriminatorColumn);
245 2
246
                // Evaluate <discriminator-map...>
247
                if (isset($xmlRoot->{'discriminator-map'})) {
248
                    $map = [];
249
250 34
                    foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
251 4
                        $map[(string) $discrMapElement['value']] = $metadata->fullyQualifiedClassName((string) $discrMapElement['class']);
252
                    }
253
254
                    $metadata->setDiscriminatorMap($map);
255 34
                }
256 21
            }
257 21
        }
258 21
259 21
260 21
        // Evaluate <change-tracking-policy...>
261
        if (isset($xmlRoot['change-tracking-policy'])) {
262 21
            $changeTrackingPolicy = strtoupper((string) $xmlRoot['change-tracking-policy']);
263 2
264
            $metadata->setChangeTrackingPolicy(
265 21
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingPolicy))
266
            );
267
        }
268
269
        // Evaluate <field ...> mappings
270 34
        if (isset($xmlRoot->field)) {
271 3
            foreach ($xmlRoot->field as $fieldElement) {
272 3
                $fieldName        = (string) $fieldElement['name'];
273 3
                $isFieldVersioned = isset($fieldElement['version']) && $fieldElement['version'];
274 3
                $fieldMetadata    = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, $isFieldVersioned);
275
276 3
                $metadata->addProperty($fieldMetadata);
277 2
            }
278 3
        }
279
280
        if (isset($xmlRoot->embedded)) {
281 3
            foreach ($xmlRoot->embedded as $embeddedMapping) {
282 3
                $columnPrefix = isset($embeddedMapping['column-prefix'])
283 3
                    ? (string) $embeddedMapping['column-prefix']
284
                    : null;
285
286 3
                $useColumnPrefix = isset($embeddedMapping['use-column-prefix'])
287
                    ? $this->evaluateBoolean($embeddedMapping['use-column-prefix'])
288
                    : true;
289
290
                $mapping = [
291 34
                    'fieldName' => (string) $embeddedMapping['name'],
292
                    'class' => (string) $embeddedMapping['class'],
293 34
                    'columnPrefix' => $useColumnPrefix ? $columnPrefix : false
294 31
                ];
295 2
296
                $metadata->mapEmbedded($mapping);
297 2
            }
298
        }
299
300 30
        // Evaluate <id ...> mappings
301 30
        $associationIds = [];
302 30
303
        foreach ($xmlRoot->id as $idElement) {
304 30
            $fieldName = (string) $idElement['name'];
305
306 30
            if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) {
307
                $associationIds[$fieldName] = true;
308 30
309 29
                continue;
310 29
            }
311 29
312
            $fieldMetadata = $this->convertFieldElementToFieldMetadata($idElement, $fieldName, false);
313 29
314 29
            $fieldMetadata->setPrimaryKey(true);
315
316
            if (isset($idElement->generator)) {
317
                $strategy = isset($idElement->generator['strategy'])
318
                    ? (string) $idElement->generator['strategy']
319 30
                    : 'AUTO';
320 2
321
                $idGeneratorType = constant(sprintf('%s::%s', Mapping\GeneratorType::class, strtoupper($strategy)));
322 2
323 2
                if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
324 2
                    $idGeneratorDefinition = [];
325 2
326
                    // Check for SequenceGenerator/TableGenerator definition
327 28
                    if (isset($idElement->{'sequence-generator'})) {
328 2
                        $seqGenerator = $idElement->{'sequence-generator'};
329
            $idGeneratorDefinition = [
330 2
                            'sequenceName' => (string) $seqGenerator['sequence-name'],
331 2
                            'allocationSize' => (string) $seqGenerator['allocation-size'],
332
                        ];
333 26
                    } elseif (isset($idElement->{'custom-id-generator'})) {
334 30
                        $customGenerator = $idElement->{'custom-id-generator'};
335
336
                        $idGeneratorDefinition = [
337
                            'class' => (string) $customGenerator['class'],
338
                            'arguments' => [],
339 34
                        ];
340 7
                    } elseif (isset($idElement->{'table-generator'})) {
341
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
342 7
                    }
343 7
344
                    $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
345
                }
346 7
            }
347
348
            $metadata->addProperty($fieldMetadata);
349
        }
350 7
351 2
        // Evaluate <one-to-one ...> mappings
352
        if (isset($xmlRoot->{'one-to-one'})) {
353
            foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
354 7
                $association  = new Mapping\OneToOneAssociationMetadata((string) $oneToOneElement['field']);
355 2
                $targetEntity = $metadata->fullyQualifiedClassName((string) $oneToOneElement['target-entity']);
356
357 7
                $association->setTargetEntity($targetEntity);
358 7
359
                if (isset($associationIds[$association->getName()])) {
360
                    $association->setPrimaryKey(true);
361 7
                }
362
363 7 View Code Duplication
                if (isset($oneToOneElement['fetch'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
364 6
                    $association->setFetchMode(
365 1
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToOneElement['fetch']))
366 1
                    );
367 1
                }
368
369
                if (isset($oneToOneElement['mapped-by'])) {
370
                    $association->setMappedBy((string) $oneToOneElement['mapped-by']);
371 7
                } else {
372
                    if (isset($oneToOneElement['inversed-by'])) {
373
                        $association->setInversedBy((string) $oneToOneElement['inversed-by']);
374 7
                    }
375 5
376
                    $joinColumns = [];
377
378 7 View Code Duplication
                    if (isset($oneToOneElement->{'join-column'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
379 3
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($oneToOneElement->{'join-column'});
380
                    } else if (isset($oneToOneElement->{'join-columns'})) {
381
                        foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
382
                            $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
383 7
                        }
384
                    }
385
386
                    $association->setJoinColumns($joinColumns);
387 7
                }
388
389
                if (isset($oneToOneElement->cascade)) {
390
                    $association->setCascade($this->getCascadeMappings($oneToOneElement->cascade));
391
                }
392 34
393 7
                if (isset($oneToOneElement['orphan-removal'])) {
394
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToOneElement['orphan-removal']));
395 7
                }
396 7
397 7
                // Evaluate second level cache
398
                if (isset($oneToOneElement->cache)) {
399
                    $association->setCache(
400 7
                        $this->convertCacheElementToCacheMetadata(
401 2
                            $oneToOneElement->cache,
402
                            $metadata,
403
                            $association->getName()
404 7
                        )
405 5
                    );
406
                }
407
408 7
                $metadata->addProperty($association);
409 5
            }
410
        }
411
412 7
        // Evaluate <one-to-many ...> mappings
413 5
        if (isset($xmlRoot->{'one-to-many'})) {
414 5
            foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
415 5
                $association  = new Mapping\OneToManyAssociationMetadata((string) $oneToManyElement['field']);
416
                $targetEntity = $metadata->fullyQualifiedClassName((string) $oneToManyElement['target-entity']);
417 5
418
                $association->setTargetEntity($targetEntity);
419
                $association->setMappedBy((string) $oneToManyElement['mapped-by']);
420 7
421 2
                if (isset($associationIds[$association->getName()])) {
422 5
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
423
                }
424
425 View Code Duplication
                if (isset($oneToManyElement['fetch'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
426
                    $association->setFetchMode(
427 7
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $oneToManyElement['fetch']))
428 1
                    );
429
                }
430
431 7
                if (isset($oneToManyElement->cascade)) {
432
                    $association->setCascade($this->getCascadeMappings($oneToManyElement->cascade));
433
                }
434
435
                if (isset($oneToManyElement['orphan-removal'])) {
436 34
                    $association->setOrphanRemoval($this->evaluateBoolean($oneToManyElement['orphan-removal']));
437 7
                }
438
439 7 View Code Duplication
                if (isset($oneToManyElement->{'order-by'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
440 7
                    $orderBy = [];
441
442
                    foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
443 7
                        $orderBy[(string) $orderByField['name']] = (string) $orderByField['direction'];
444 2
                    }
445
446
                    $association->setOrderBy($orderBy);
447 7
                }
448 1
449 View Code Duplication
                if (isset($oneToManyElement['index-by'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
450
                    $association->setIndexedBy((string) $oneToManyElement['index-by']);
451 7
                } else if (isset($oneToManyElement->{'index-by'})) {
452 1
                    throw new \InvalidArgumentException("<index-by /> is not a valid tag");
453
                }
454
455 7
                // Evaluate second level cache
456
                if (isset($oneToManyElement->cache)) {
457 7
                    $association->setCache(
458 4
                        $this->convertCacheElementToCacheMetadata(
459 3
                            $oneToManyElement->cache,
460 2
                            $metadata,
461 2
                            $association->getName()
462
                        )
463
                    );
464
                }
465 7
466
                $metadata->addProperty($association);
467 7
            }
468 2
        }
469
470
        // Evaluate <many-to-one ...> mappings
471
        if (isset($xmlRoot->{'many-to-one'})) {
472 7
            foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
473 1
                $association  = new Mapping\ManyToOneAssociationMetadata((string) $manyToOneElement['field']);
474
                $targetEntity = $metadata->fullyQualifiedClassName((string) $manyToOneElement['target-entity']);
475
476 7
                $association->setTargetEntity($targetEntity);
477
478
                if (isset($associationIds[$association->getName()])) {
479
                    $association->setPrimaryKey(true);
480
                }
481
482 33
                if (isset($manyToOneElement['fetch'])) {
483 10
                    $association->setFetchMode(
484
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $manyToOneElement['fetch'])
485 10
                    );
486 10
                }
487
488
                if (isset($manyToOneElement['inversed-by'])) {
489 10
                    $association->setInversedBy((string) $manyToOneElement['inversed-by']);
490 2
                }
491
492
                $joinColumns = [];
493 10
494 View Code Duplication
                if (isset($manyToOneElement->{'join-column'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
495
                    $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($manyToOneElement->{'join-column'});
496
                } else if (isset($manyToOneElement->{'join-columns'})) {
497 10
                    foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
498 2
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
499 8
                    }
500 7
                }
501 2
502
                $association->setJoinColumns($joinColumns);
503
504 7
                if (isset($manyToOneElement->cascade)) {
505
                    $association->setCascade($this->getCascadeMappings($manyToOneElement->cascade));
506 7
                }
507
508
                // Evaluate second level cache
509 7
                if (isset($manyToOneElement->cache)) {
510
                    $association->setCache(
511
                        $this->convertCacheElementToCacheMetadata(
512
                            $manyToOneElement->cache,
513 7
                            $metadata,
514 7
                            $association->getName()
515
                        )
516
                    );
517 7
                }
518 7
519
                $metadata->addProperty($association);
520
            }
521 7
        }
522
523
        // Evaluate <many-to-many ...> mappings
524 10
        if (isset($xmlRoot->{'many-to-many'})) {
525 7
            foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
526
                $association  = new Mapping\ManyToManyAssociationMetadata((string) $manyToManyElement['field']);
527
                $targetEntity = $metadata->fullyQualifiedClassName((string) $manyToManyElement['target-entity']);
528 10
529
                $association->setTargetEntity($targetEntity);
530
531
                if (isset($associationIds[$association->getName()])) {
532
                    throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $association->getName());
533
                }
534
535 View Code Duplication
                if (isset($manyToManyElement['fetch'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
536
                    $association->setFetchMode(
537
                        constant(sprintf('%s::%s', Mapping\FetchMode::class, (string) $manyToManyElement['fetch']))
538 10
                    );
539
                }
540 10
541
                if (isset($manyToManyElement['orphan-removal'])) {
542
                    $association->setOrphanRemoval($this->evaluateBoolean($manyToManyElement['orphan-removal']));
543
                }
544
545 10
                if (isset($manyToManyElement['mapped-by'])) {
546
                    $association->setMappedBy((string) $manyToManyElement['mapped-by']);
547
                } else if (isset($manyToManyElement->{'join-table'})) {
548
                    if (isset($manyToManyElement['inversed-by'])) {
549 10
                        $association->setInversedBy((string) $manyToManyElement['inversed-by']);
550
                    }
551
552
                    $joinTableElement = $manyToManyElement->{'join-table'};
553
                    $joinTable        = new Mapping\JoinTableMetadata();
554 33
555 2
                    if (isset($joinTableElement['name'])) {
556 2
                        $joinTable->setName((string) $joinTableElement['name']);
557
                    }
558 2
559 2
                    if (isset($joinTableElement['schema'])) {
560
                        $joinTable->setSchema((string) $joinTableElement['schema']);
561 2
                    }
562
563 View Code Duplication
                    if (isset($joinTableElement->{'join-columns'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
564
                        foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
565
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
566
567 33
                            $joinTable->addJoinColumn($joinColumn);
568 3
                        }
569 3
                    }
570 3
571 View Code Duplication
                    if (isset($joinTableElement->{'inverse-join-columns'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
572
                        foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
573 3
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
574 2
575
                            $joinTable->addInverseJoinColumn($joinColumn);
576 2
                        }
577 2
                    }
578
579
                    $association->setJoinTable($joinTable);
580 2
                }
581
582
                if (isset($manyToManyElement->cascade)) {
583
                    $association->setCascade($this->getCascadeMappings($manyToManyElement->cascade));
584 3
                }
585 2
586 2 View Code Duplication
                if (isset($manyToManyElement->{'order-by'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
587
                    $orderBy = [];
588
589 2
                    foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
590 2
                        $orderBy[(string) $orderByField['name']] = (string) $orderByField['direction'];
591
                    }
592
593 2
                    $association->setOrderBy($orderBy);
594 2
                }
595 2
596 View Code Duplication
                if (isset($manyToManyElement['index-by'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
597
                    $association->setIndexedBy((string) $manyToManyElement['index-by']);
598
                } else if (isset($manyToManyElement->{'index-by'})) {
599 2
                    throw new \InvalidArgumentException("<index-by /> is not a valid tag");
600 2
                }
601 2
602
                // Evaluate second level cache
603
                if (isset($manyToManyElement->cache)) {
604
                    $association->setCache(
605 2
                        $this->convertCacheElementToCacheMetadata(
606
                            $manyToManyElement->cache,
607
                            $metadata,
608
                            $association->getName()
609 3
                        )
610 1
                    );
611
                }
612
613 3
                $metadata->addProperty($association);
614
            }
615
        }
616
617
        // Evaluate association-overrides
618 33
        if (isset($xmlRoot->{'attribute-overrides'})) {
619 4
            foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) {
620 4
                $fieldName = (string) $overrideElement['name'];
621
622
                foreach ($overrideElement->field as $fieldElement) {
623
                    $fieldMetadata = $this->convertFieldElementToFieldMetadata($fieldElement, $fieldName, false);
624
625 33
                    $metadata->setPropertyOverride($fieldMetadata);
626 4
                }
627 4
            }
628
        }
629
630 4
        // Evaluate association-overrides
631 2
        if (isset($xmlRoot->{'association-overrides'})) {
632
            foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) {
633 2
                $fieldName = (string) $overrideElement['name'];
634
                $property  = $metadata->getProperty($fieldName);
635
636 2
                if (! $property) {
637 2
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
638 2
                }
639
640 2
                $existingClass = get_class($property);
641
                $override      = new $existingClass($fieldName);
642
643
                // Check for join-columns
644 33 View Code Duplication
                if (isset($overrideElement->{'join-columns'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
645
                    $joinColumns = [];
646
647
                    foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
648
                        $joinColumns[] = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
649
                    }
650
651
                    $override->setJoinColumns($joinColumns);
652
                }
653 5
654
                // Check for join-table
655 5
                if ($overrideElement->{'join-table'}) {
656
                    $joinTableElement   = $overrideElement->{'join-table'};
657
                    $joinTable          = new Mapping\JoinTableMetadata();
658 5
659 5
                    if (isset($joinTableElement['name'])) {
660 4
                        $joinTable->setName((string) $joinTableElement['name']);
661
                    }
662 5
663
                    if (isset($joinTableElement['schema'])) {
664
                        $joinTable->setSchema((string) $joinTableElement['schema']);
665 5
                    }
666
667 5 View Code Duplication
                    if (isset($joinTableElement->{'join-columns'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
668 5
                        foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
669
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
670 5
671
                            $joinTable->addJoinColumn($joinColumn);
672
                        }
673
                    }
674 5
675 View Code Duplication
                    if (isset($joinTableElement->{'inverse-join-columns'})) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
676
                        foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
677
                            $joinColumn = $this->convertJoinColumnElementToJoinColumnMetadata($joinColumnElement);
678
679
                            $joinTable->addInverseJoinColumn($joinColumn);
680
                        }
681
                    }
682
683
                    $override->setJoinTable($joinTable);
684
                }
685 12
686
                // Check for inversed-by
687
                if (isset($overrideElement->{'inversed-by'})) {
688 12
                    $override->setInversedBy((string) $overrideElement->{'inversed-by'}['name']);
689 12
                }
690
691
                // Check for fetch
692 12
                if (isset($overrideElement['fetch'])) {
693 3
                    $override->setFetchMode(
694
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $overrideElement['fetch'])
695
                    );
696 12
                }
697 5
698
                $metadata->setPropertyOverride($override);
699
            }
700 12
        }
701 4
702
        // Evaluate <lifecycle-callbacks...>
703
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
704 12
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
705 4
                $eventName   = constant(Events::class . '::' . (string) $lifecycleCallback['type']);
706
                $methodName  = (string) $lifecycleCallback['method'];
707
708 12
                $metadata->addLifecycleCallback($methodName, $eventName);
709
            }
710
        }
711
712
        // Evaluate entity listener
713
        if (isset($xmlRoot->{'entity-listeners'})) {
714
            foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) {
715
                $listenerClassName = $metadata->fullyQualifiedClassName((string) $listenerElement['class']);
716
717
                if (! class_exists($listenerClassName)) {
718 33
                    throw Mapping\MappingException::entityListenerClassNotFound(
719
                        $listenerClassName,
720
                        $metadata->getClassName()
721 33
                    );
722
                }
723
724 33
                $listenerClass = new \ReflectionClass($listenerClassName);
725 21
726
                // Evaluate the listener using naming convention.
727 21
                if ($listenerElement->count() === 0) {
728
                    /* @var $method \ReflectionMethod */
729
                    foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
730
                        foreach ($this->getMethodCallbacks($method) as $callback) {
731 21
                            $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
732
                        }
733
                    }
734 33
735 23
                    continue;
736
                }
737
738 33
                foreach ($listenerElement as $callbackElement) {
739 10
                    $eventName   = (string) $callbackElement['type'];
740
                    $methodName  = (string) $callbackElement['method'];
741
742 33
                    $metadata->addEntityListener($eventName, $listenerClassName, $methodName);
743 1
                }
744
            }
745
        }
746 33
    }
747 1
748
    /**
749
     * Parses (nested) option elements.
750 33
     *
751 9
     * @param SimpleXMLElement $options The XML element.
752
     *
753
     * @return array The options array.
754 33
     */
755 8
    private function parseOptions(SimpleXMLElement $options)
756
    {
757
        $array = [];
758 33
759 2
        /* @var $option SimpleXMLElement */
760
        foreach ($options as $option) {
761
            if ($option->count()) {
762 33
                $value = $this->parseOptions($option->children());
763 5
            } else {
764
                $value = (string) $option;
765
            }
766 33
767 4
            $attributes = $option->attributes();
768
769
            if (isset($attributes->name)) {
770 33
                $nameAttribute = (string) $attributes->name;
771
                $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'])
772
                    ? $this->evaluateBoolean($value)
773
                    : $value;
774
            } else {
775
                $array[] = $value;
776
            }
777
778
        }
779
780 2
        return $array;
781
    }
782 2
783 2
    /**
784
     * @param SimpleXMLElement $fieldElement
785 2
     * @param string           $fieldName
786
     * @param bool             $isVersioned
787
     *
788
     * @return Mapping\FieldMetadata
789 2
     */
790 2
    private function convertFieldElementToFieldMetadata(SimpleXMLElement $fieldElement, string $fieldName, bool $isVersioned)
791
    {
792
        $fieldMetadata = $isVersioned
793
            ? new Mapping\VersionFieldMetadata($fieldName)
794 2
            : new Mapping\FieldMetadata($fieldName)
795 2
        ;
796
797
        $fieldMetadata->setType(Type::getType('string'));
798
799
        if (isset($fieldElement['type'])) {
800
            $fieldMetadata->setType(Type::getType((string) $fieldElement['type']));
801
        }
802
803
        if (isset($fieldElement['column'])) {
804
            $fieldMetadata->setColumnName((string) $fieldElement['column']);
805
        }
806 7
807
        if (isset($fieldElement['length'])) {
808 7
            $fieldMetadata->setLength((int) $fieldElement['length']);
809
        }
810
811 7
        if (isset($fieldElement['precision'])) {
812
            $fieldMetadata->setPrecision((int) $fieldElement['precision']);
813
        }
814
815
        if (isset($fieldElement['scale'])) {
816
            $fieldMetadata->setScale((int) $fieldElement['scale']);
817 7
        }
818
819
        if (isset($fieldElement['unique'])) {
820 7
            $fieldMetadata->setUnique($this->evaluateBoolean($fieldElement['unique']));
821
        }
822
823
        if (isset($fieldElement['nullable'])) {
824
            $fieldMetadata->setNullable($this->evaluateBoolean($fieldElement['nullable']));
825
        }
826 36
827
        if (isset($fieldElement['column-definition'])) {
828 36
            $fieldMetadata->setColumnDefinition((string) $fieldElement['column-definition']);
829 36
        }
830
831 36
        if (isset($fieldElement->options)) {
832 34
            $fieldMetadata->setOptions($this->parseOptions($fieldElement->options->children()));
833 34
        }
834 34
835
        return $fieldMetadata;
836 7
    }
837 5
838 5
    /**
839 5
     * Constructs a joinColumn mapping array based on the information
840
     * found in the given SimpleXMLElement.
841 2
     *
842 1
     * @param SimpleXMLElement $joinColumnElement The XML element.
843 1
     *
844 1
     * @return Mapping\JoinColumnMetadata
845
     */
846
    private function convertJoinColumnElementToJoinColumnMetadata(SimpleXMLElement $joinColumnElement)
847
    {
848 36
        $joinColumnMetadata = new Mapping\JoinColumnMetadata();
849
850
        $joinColumnMetadata->setColumnName((string) $joinColumnElement['name']);
851
        $joinColumnMetadata->setReferencedColumnName((string) $joinColumnElement['referenced-column-name']);
852
853
        if (isset($joinColumnElement['column-definition'])) {
854
            $joinColumnMetadata->setColumnDefinition((string) $joinColumnElement['column-definition']);
855
        }
856 13
857
        if (isset($joinColumnElement['field-name'])) {
858 13
            $joinColumnMetadata->setAliasedName((string) $joinColumnElement['field-name']);
859
        }
860 13
861
        if (isset($joinColumnElement['nullable'])) {
862
            $joinColumnMetadata->setNullable($this->evaluateBoolean($joinColumnElement['nullable']));
863
        }
864
865
        if (isset($joinColumnElement['unique'])) {
866
            $joinColumnMetadata->setUnique($this->evaluateBoolean($joinColumnElement['unique']));
867
        }
868
869
        if (isset($joinColumnElement['on-delete'])) {
870
            $joinColumnMetadata->setOnDelete(strtoupper((string) $joinColumnElement['on-delete']));
871
        }
872
873
        return $joinColumnMetadata;
874
    }
875
876
    /**
877
     * Parse the given Cache as CacheMetadata
878
     *
879
     * @param \SimpleXMLElement     $cacheMapping
880
     * @param Mapping\ClassMetadata $metadata
881
     * @param null|string           $fieldName
882
     *
883
     * @return Mapping\CacheMetadata
884
     */
885
    private function convertCacheElementToCacheMetadata(
886
        SimpleXMLElement $cacheMapping,
887
        Mapping\ClassMetadata $metadata,
888
        $fieldName = null
889
    )
890
    {
891
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
892
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
893
894
        $region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : $defaultRegion;
895
        $usage  = isset($cacheMapping['usage'])
896
            ? constant(sprintf('%s::%s', Mapping\CacheUsage::class, strtoupper((string) $cacheMapping['usage'])))
897
            : Mapping\CacheUsage::READ_ONLY
898
        ;
899
900
        return new Mapping\CacheMetadata($usage, $region);
901
    }
902
903
    /**
904
     * Parses the given method.
905
     *
906
     * @param \ReflectionMethod $method
907
     *
908
     * @return array
909
     */
910
    private function getMethodCallbacks(\ReflectionMethod $method)
911
    {
912
        $events = [
913
            Events::prePersist,
914
            Events::postPersist,
915
            Events::preUpdate,
916
            Events::postUpdate,
917
            Events::preRemove,
918
            Events::postRemove,
919
            Events::postLoad,
920
            Events::preFlush,
921
        ];
922
923
        return array_filter($events, function ($eventName) use ($method) {
924
            return $eventName === $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
925
        });
926
    }
927
928
    /**
929
     * Gathers a list of cascade options found in the given cascade element.
930
     *
931
     * @param SimpleXMLElement $cascadeElement The cascade element.
932
     *
933
     * @return array The list of cascade options.
934
     */
935
    private function getCascadeMappings(SimpleXMLElement $cascadeElement)
936
    {
937
        $cascades = [];
938
939
        /* @var $action SimpleXmlElement */
940
        foreach ($cascadeElement->children() as $action) {
941
            // According to the JPA specifications, XML uses "cascade-persist"
942
            // instead of "persist". Here, both variations are supported
943
            // because Annotation use "persist" and we want to make sure that
944
            // this driver doesn't need to know anything about the supported
945
            // cascading actions
946
            $cascades[] = str_replace('cascade-', '', $action->getName());
947
        }
948
949
        return $cascades;
950
    }
951
952
    /**
953
     * {@inheritDoc}
954
     */
955
    protected function loadMappingFile($file)
956
    {
957
        $this->validateMapping($file);
958
        $result = [];
959
        $xmlElement = simplexml_load_file($file);
960
961
        if (isset($xmlElement->entity)) {
962
            foreach ($xmlElement->entity as $entityElement) {
963
                $entityName = (string) $entityElement['name'];
964
                $result[$entityName] = $entityElement;
965
            }
966
        } else if (isset($xmlElement->{'mapped-superclass'})) {
967
            foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) {
968
                $className = (string) $mappedSuperClass['name'];
969
                $result[$className] = $mappedSuperClass;
970
            }
971
        } else if (isset($xmlElement->embeddable)) {
972
            foreach ($xmlElement->embeddable as $embeddableElement) {
973
                $embeddableName = (string) $embeddableElement['name'];
974
                $result[$embeddableName] = $embeddableElement;
975
            }
976
        }
977
978
        return $result;
979
    }
980
981
    private function validateMapping(string $file): void
982
    {
983
        $backedUpErrorSetting = libxml_use_internal_errors(true);
984
        $document = new \DOMDocument();
985
        $document->load($file);
986
        try {
987
            if ( ! $document->schemaValidate(__DIR__ . '/../../../../../doctrine-mapping.xsd')) {
988
                $errors = libxml_get_errors();
989
                throw MappingException::fromLibXmlErrors($errors);
990
            }
991
        } finally {
992
            libxml_clear_errors();
993
            libxml_use_internal_errors($backedUpErrorSetting);
994
        }
995
    }
996
997
    /**
998
     * @param mixed $element
999
     *
1000
     * @return bool
1001
     */
1002
    protected function evaluateBoolean($element)
1003
    {
1004
        $flag = (string) $element;
1005
1006
        return ($flag == "true" || $flag == "1");
1007
    }
1008
}
1009