Completed
Pull Request — 2.7 (#6728)
by Luís
11:01
created

XmlDriver   F

Complexity

Total Complexity 182

Size/Duplication

Total Lines 855
Duplicated Lines 0 %

Test Coverage

Coverage 95.1%

Importance

Changes 0
Metric Value
eloc 417
dl 0
loc 855
ccs 388
cts 408
cp 0.951
rs 2
c 0
b 0
f 0
wmc 182

10 Methods

Rating   Name   Duplication   Size   Complexity  
A joinColumnToArray() 0 24 5
A evaluateBoolean() 0 5 2
F loadMetadataForClass() 0 605 140
A validateMapping() 0 14 2
B loadMappingFile() 0 25 7
F columnToArray() 0 47 12
A _getCascadeMappings() 0 14 2
A __construct() 0 3 1
A cacheToArray() 0 16 6
A _parseOptions() 0 25 5

How to fix   Complexity   

Complex Class

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.

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Mapping\Driver;
21
22
use Doctrine\Common\Collections\Criteria;
23
use DOMDocument;
24
use SimpleXMLElement;
25
use Doctrine\Common\Persistence\Mapping\Driver\FileDriver;
26
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
27
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
28
use Doctrine\ORM\Mapping\MappingException;
29
use Doctrine\ORM\Mapping\ClassMetadata as Metadata;
30
31
/**
32
 * XmlDriver is a metadata driver that enables mapping through XML files.
33
 *
34
 * @license 	http://www.opensource.org/licenses/mit-license.php MIT
35
 * @link    	www.doctrine-project.org
36
 * @since   	2.0
37
 * @author		Benjamin Eberlei <[email protected]>
38
 * @author		Guilherme Blanco <[email protected]>
39
 * @author      Jonathan H. Wage <[email protected]>
40
 * @author      Roman Borschel <[email protected]>
41
 */
42
class XmlDriver extends FileDriver
43
{
44
    const DEFAULT_FILE_EXTENSION = '.dcm.xml';
45
46
    /**
47
     * {@inheritDoc}
48
     */
49 45
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
50
    {
51 45
        parent::__construct($locator, $fileExtension);
52 45
    }
53
54
    /**
55
     * {@inheritDoc}
56
     */
57 40
    public function loadMetadataForClass($className, ClassMetadata $metadata)
58
    {
59
        /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadataInfo */
60
        /* @var $xmlRoot SimpleXMLElement */
61 40
        $xmlRoot = $this->getElement($className);
62
63 38
        if ($xmlRoot->getName() == 'entity') {
64 37
            if (isset($xmlRoot['repository-class'])) {
65
                $metadata->setCustomRepositoryClass((string) $xmlRoot['repository-class']);
66
            }
67 37
            if (isset($xmlRoot['read-only']) && $this->evaluateBoolean($xmlRoot['read-only'])) {
68 37
                $metadata->markReadOnly();
69
            }
70 8
        } else if ($xmlRoot->getName() == 'mapped-superclass') {
71 5
            $metadata->setCustomRepositoryClass(
72 5
                isset($xmlRoot['repository-class']) ? (string) $xmlRoot['repository-class'] : null
73
            );
74 5
            $metadata->isMappedSuperclass = true;
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
75 3
        } else if ($xmlRoot->getName() == 'embeddable') {
76 3
            $metadata->isEmbeddedClass = true;
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedClass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
77
        } else {
78
            throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
79
        }
80
81
        // Evaluate <entity...> attributes
82 38
        $primaryTable = [];
83
84 38
        if (isset($xmlRoot['table'])) {
85 16
            $primaryTable['name'] = (string) $xmlRoot['table'];
86
        }
87
88 38
        if (isset($xmlRoot['schema'])) {
89 1
            $primaryTable['schema'] = (string) $xmlRoot['schema'];
90
        }
91
92 38
        $metadata->setPrimaryTable($primaryTable);
93
94
        // Evaluate second level cache
95 38
        if (isset($xmlRoot->cache)) {
96 2
            $metadata->enableCache($this->cacheToArray($xmlRoot->cache));
97
        }
98
99
        // Evaluate named queries
100 38
        if (isset($xmlRoot->{'named-queries'})) {
101 4
            foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) {
102 4
                $metadata->addNamedQuery(
103
                    [
104 4
                        'name'  => (string) $namedQueryElement['name'],
105 4
                        'query' => (string) $namedQueryElement['query']
106
                    ]
107
                );
108
            }
109
        }
110
111
        // Evaluate native named queries
112 38
        if (isset($xmlRoot->{'named-native-queries'})) {
113 3
            foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) {
114 3
                $metadata->addNamedNativeQuery(
115
                    [
116 3
                        'name'              => isset($nativeQueryElement['name']) ? (string) $nativeQueryElement['name'] : null,
117 3
                        'query'             => isset($nativeQueryElement->query) ? (string) $nativeQueryElement->query : null,
118 3
                        'resultClass'       => isset($nativeQueryElement['result-class']) ? (string) $nativeQueryElement['result-class'] : null,
119 3
                        'resultSetMapping'  => isset($nativeQueryElement['result-set-mapping']) ? (string) $nativeQueryElement['result-set-mapping'] : null,
120
                    ]
121
                );
122
            }
123
        }
124
125
        // Evaluate sql result set mapping
126 38
        if (isset($xmlRoot->{'sql-result-set-mappings'})) {
127 3
            foreach ($xmlRoot->{'sql-result-set-mappings'}->{'sql-result-set-mapping'} as $rsmElement) {
128 3
                $entities   = [];
129 3
                $columns    = [];
130 3
                foreach ($rsmElement as $entityElement) {
131
                    //<entity-result/>
132 3
                    if (isset($entityElement['entity-class'])) {
133
                        $entityResult = [
134 3
                            'fields'                => [],
135 3
                            'entityClass'           => (string) $entityElement['entity-class'],
136 3
                            'discriminatorColumn'   => isset($entityElement['discriminator-column']) ? (string) $entityElement['discriminator-column'] : null,
137
                        ];
138
139 3
                        foreach ($entityElement as $fieldElement) {
140 3
                            $entityResult['fields'][] = [
141 3
                                'name'      => isset($fieldElement['name']) ? (string) $fieldElement['name'] : null,
142 3
                                'column'    => isset($fieldElement['column']) ? (string) $fieldElement['column'] : null,
143
                            ];
144
                        }
145
146 3
                        $entities[] = $entityResult;
147
                    }
148
149
                    //<column-result/>
150 3
                    if (isset($entityElement['name'])) {
151 3
                        $columns[] = [
152 3
                            'name' => (string) $entityElement['name'],
153
                        ];
154
                    }
155
                }
156
157 3
                $metadata->addSqlResultSetMapping(
158
                    [
159 3
                        'name'          => (string) $rsmElement['name'],
160 3
                        'entities'      => $entities,
161 3
                        'columns'       => $columns
162
                    ]
163
                );
164
            }
165
        }
166
167 38
        if (isset($xmlRoot['inheritance-type'])) {
168 10
            $inheritanceType = (string) $xmlRoot['inheritance-type'];
169 10
            $metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType));
170
171 10
            if ($metadata->inheritanceType != Metadata::INHERITANCE_TYPE_NONE) {
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
172
                // Evaluate <discriminator-column...>
173 10
                if (isset($xmlRoot->{'discriminator-column'})) {
174 7
                    $discrColumn = $xmlRoot->{'discriminator-column'};
175 7
                    $metadata->setDiscriminatorColumn(
176
                        [
177 7
                            'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null,
178 7
                            'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
179 7
                            'length' => isset($discrColumn['length']) ? (string) $discrColumn['length'] : 255,
180 7
                            'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null
181
                        ]
182
                    );
183
                } else {
184 6
                    $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
185
                }
186
187
                // Evaluate <discriminator-map...>
188 10
                if (isset($xmlRoot->{'discriminator-map'})) {
189 10
                    $map = [];
190 10
                    foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} as $discrMapElement) {
191 10
                        $map[(string) $discrMapElement['value']] = (string) $discrMapElement['class'];
192
                    }
193 10
                    $metadata->setDiscriminatorMap($map);
194
                }
195
            }
196
        }
197
198
199
        // Evaluate <change-tracking-policy...>
200 38
        if (isset($xmlRoot['change-tracking-policy'])) {
201
            $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_'
202
                . strtoupper((string) $xmlRoot['change-tracking-policy'])));
203
        }
204
205
        // Evaluate <indexes...>
206 38
        if (isset($xmlRoot->indexes)) {
207 4
            $metadata->table['indexes'] = [];
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
208 4
            foreach ($xmlRoot->indexes->index as $indexXml) {
209 4
                $index = ['columns' => explode(',', (string) $indexXml['columns'])];
210
211 4
                if (isset($indexXml['flags'])) {
212 1
                    $index['flags'] = explode(',', (string) $indexXml['flags']);
213
                }
214
215 4
                if (isset($indexXml->options)) {
216 1
                    $index['options'] = $this->_parseOptions($indexXml->options->children());
217
                }
218
219 4
                if (isset($indexXml['name'])) {
220 3
                    $metadata->table['indexes'][(string) $indexXml['name']] = $index;
221
                } else {
222 4
                    $metadata->table['indexes'][] = $index;
223
                }
224
            }
225
        }
226
227
        // Evaluate <unique-constraints..>
228 38
        if (isset($xmlRoot->{'unique-constraints'})) {
229 3
            $metadata->table['uniqueConstraints'] = [];
230 3
            foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
231 3
                $unique = ['columns' => explode(',', (string) $uniqueXml['columns'])];
232
233 3
                if (isset($uniqueXml->options)) {
234 3
                    $unique['options'] = $this->_parseOptions($uniqueXml->options->children());
235
                }
236
237 3
                if (isset($uniqueXml['name'])) {
238 3
                    $metadata->table['uniqueConstraints'][(string) $uniqueXml['name']] = $unique;
239
                } else {
240 3
                    $metadata->table['uniqueConstraints'][] = $unique;
241
                }
242
            }
243
        }
244
245 38
        if (isset($xmlRoot->options)) {
246 5
            $metadata->table['options'] = $this->_parseOptions($xmlRoot->options->children());
247
        }
248
249
        // The mapping assignment is done in 2 times as a bug might occurs on some php/xml lib versions
250
        // The internal SimpleXmlIterator get resetted, to this generate a duplicate field exception
251 38
        $mappings = [];
252
        // Evaluate <field ...> mappings
253 38
        if (isset($xmlRoot->field)) {
254 24
            foreach ($xmlRoot->field as $fieldMapping) {
255 24
                $mapping = $this->columnToArray($fieldMapping);
256
257 24
                if (isset($mapping['version'])) {
258 3
                    $metadata->setVersionMapping($mapping);
259 3
                    unset($mapping['version']);
260
                }
261
262 24
                $metadata->mapField($mapping);
263
            }
264
        }
265
266 38
        if (isset($xmlRoot->embedded)) {
267 3
            foreach ($xmlRoot->embedded as $embeddedMapping) {
268 3
                $columnPrefix = isset($embeddedMapping['column-prefix'])
269 3
                    ? (string) $embeddedMapping['column-prefix']
270 3
                    : null;
271
272 3
                $useColumnPrefix = isset($embeddedMapping['use-column-prefix'])
273 2
                    ? $this->evaluateBoolean($embeddedMapping['use-column-prefix'])
274 3
                    : true;
275
276
                $mapping = [
277 3
                    'fieldName' => (string) $embeddedMapping['name'],
278 3
                    'class' => (string) $embeddedMapping['class'],
279 3
                    'columnPrefix' => $useColumnPrefix ? $columnPrefix : false
280
                ];
281
282 3
                $metadata->mapEmbedded($mapping);
283
            }
284
        }
285
286 38
        foreach ($mappings as $mapping) {
287
            if (isset($mapping['version'])) {
288
                $metadata->setVersionMapping($mapping);
289
            }
290
291
            $metadata->mapField($mapping);
292
        }
293
294
        // Evaluate <id ...> mappings
295 38
        $associationIds = [];
296 38
        foreach ($xmlRoot->id as $idElement) {
297 34
            if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) {
298 2
                $associationIds[(string) $idElement['name']] = true;
299 2
                continue;
300
            }
301
302
            $mapping = [
303 33
                'id' => true,
304 33
                'fieldName' => (string) $idElement['name']
305
            ];
306
307 33
            if (isset($idElement['type'])) {
308 21
                $mapping['type'] = (string) $idElement['type'];
309
            }
310
311 33
            if (isset($idElement['length'])) {
312 3
                $mapping['length'] = (string) $idElement['length'];
313
            }
314
315 33
            if (isset($idElement['column'])) {
316 24
                $mapping['columnName'] = (string) $idElement['column'];
317
            }
318
319 33
            if (isset($idElement['column-definition'])) {
320 1
                $mapping['columnDefinition'] = (string) $idElement['column-definition'];
321
            }
322
323 33
            if (isset($idElement->options)) {
324 3
                $mapping['options'] = $this->_parseOptions($idElement->options->children());
325
            }
326
327 33
            $metadata->mapField($mapping);
328
329 33
            if (isset($idElement->generator)) {
330 32
                $strategy = isset($idElement->generator['strategy']) ?
331 32
                        (string) $idElement->generator['strategy'] : 'AUTO';
332 32
                $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_'
333 32
                    . $strategy));
334
            }
335
336
            // Check for SequenceGenerator/TableGenerator definition
337 33
            if (isset($idElement->{'sequence-generator'})) {
338 3
                $seqGenerator = $idElement->{'sequence-generator'};
339 3
                $metadata->setSequenceGeneratorDefinition(
340
                    [
341 3
                        'sequenceName' => (string) $seqGenerator['sequence-name'],
342 3
                        'allocationSize' => (string) $seqGenerator['allocation-size'],
343 3
                        'initialValue' => (string) $seqGenerator['initial-value']
344
                    ]
345
                );
346 30
            } else if (isset($idElement->{'custom-id-generator'})) {
347 2
                $customGenerator = $idElement->{'custom-id-generator'};
348 2
                $metadata->setCustomGeneratorDefinition(
349
                    [
350 2
                        'class' => (string) $customGenerator['class']
351
                    ]
352
                );
353 28
            } else if (isset($idElement->{'table-generator'})) {
354 33
                throw MappingException::tableIdGeneratorNotImplemented($className);
355
            }
356
        }
357
358
        // Evaluate <one-to-one ...> mappings
359 38
        if (isset($xmlRoot->{'one-to-one'})) {
360 8
            foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
361
                $mapping = [
362 8
                    'fieldName' => (string) $oneToOneElement['field'],
363 8
                    'targetEntity' => (string) $oneToOneElement['target-entity']
364
                ];
365
366 8
                if (isset($associationIds[$mapping['fieldName']])) {
367
                    $mapping['id'] = true;
368
                }
369
370 8
                if (isset($oneToOneElement['fetch'])) {
371 2
                    $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToOneElement['fetch']);
372
                }
373
374 8
                if (isset($oneToOneElement['mapped-by'])) {
375 2
                    $mapping['mappedBy'] = (string) $oneToOneElement['mapped-by'];
376
                } else {
377 8
                    if (isset($oneToOneElement['inversed-by'])) {
378 8
                        $mapping['inversedBy'] = (string) $oneToOneElement['inversed-by'];
379
                    }
380 8
                    $joinColumns = [];
381
382 8
                    if (isset($oneToOneElement->{'join-column'})) {
383 7
                        $joinColumns[] = $this->joinColumnToArray($oneToOneElement->{'join-column'});
384 1
                    } else if (isset($oneToOneElement->{'join-columns'})) {
385 1
                        foreach ($oneToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
386 1
                            $joinColumns[] = $this->joinColumnToArray($joinColumnElement);
387
                        }
388
                    }
389
390 8
                    $mapping['joinColumns'] = $joinColumns;
391
                }
392
393 8
                if (isset($oneToOneElement->cascade)) {
394 6
                    $mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement->cascade);
395
                }
396
397 8
                if (isset($oneToOneElement['orphan-removal'])) {
398 3
                    $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToOneElement['orphan-removal']);
399
                }
400
401
                // Evaluate second level cache
402 8
                if (isset($oneToOneElement->cache)) {
403
                    $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache));
0 ignored issues
show
Bug introduced by
The method getAssociationCacheDefaults() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getAssociationNames()? ( Ignorable by Annotation )

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

403
                    /** @scrutinizer ignore-call */ 
404
                    $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToOneElement->cache));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
404
                }
405
406 8
                $metadata->mapOneToOne($mapping);
407
            }
408
        }
409
410
        // Evaluate <one-to-many ...> mappings
411 38
        if (isset($xmlRoot->{'one-to-many'})) {
412 9
            foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
413
                $mapping = [
414 9
                    'fieldName' => (string) $oneToManyElement['field'],
415 9
                    'targetEntity' => (string) $oneToManyElement['target-entity'],
416 9
                    'mappedBy' => (string) $oneToManyElement['mapped-by']
417
                ];
418
419 9
                if (isset($oneToManyElement['fetch'])) {
420 2
                    $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToManyElement['fetch']);
421
                }
422
423 9
                if (isset($oneToManyElement->cascade)) {
424 6
                    $mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement->cascade);
425
                }
426
427 9
                if (isset($oneToManyElement['orphan-removal'])) {
428 6
                    $mapping['orphanRemoval'] = $this->evaluateBoolean($oneToManyElement['orphan-removal']);
429
                }
430
431 9
                if (isset($oneToManyElement->{'order-by'})) {
432 7
                    $orderBy = [];
433 7
                    foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
434 7
                        $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
435 6
                            ? (string) $orderByField['direction']
436 7
                            : Criteria::ASC
437
                        ;
438
                    }
439 7
                    $mapping['orderBy'] = $orderBy;
440
                }
441
442 9
                if (isset($oneToManyElement['index-by'])) {
443 3
                    $mapping['indexBy'] = (string) $oneToManyElement['index-by'];
444 6
                } else if (isset($oneToManyElement->{'index-by'})) {
445
                    throw new \InvalidArgumentException("<index-by /> is not a valid tag");
446
                }
447
448
                // Evaluate second level cache
449 9
                if (isset($oneToManyElement->cache)) {
450 1
                    $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($oneToManyElement->cache));
451
                }
452
453 9
                $metadata->mapOneToMany($mapping);
454
            }
455
        }
456
457
        // Evaluate <many-to-one ...> mappings
458 38
        if (isset($xmlRoot->{'many-to-one'})) {
459 8
            foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
460
                $mapping = [
461 8
                    'fieldName' => (string) $manyToOneElement['field'],
462 8
                    'targetEntity' => (string) $manyToOneElement['target-entity']
463
                ];
464
465 8
                if (isset($associationIds[$mapping['fieldName']])) {
466 2
                    $mapping['id'] = true;
467
                }
468
469 8
                if (isset($manyToOneElement['fetch'])) {
470 1
                    $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToOneElement['fetch']);
471
                }
472
473 8
                if (isset($manyToOneElement['inversed-by'])) {
474 1
                    $mapping['inversedBy'] = (string) $manyToOneElement['inversed-by'];
475
                }
476
477 8
                $joinColumns = [];
478
479 8
                if (isset($manyToOneElement->{'join-column'})) {
480 5
                    $joinColumns[] = $this->joinColumnToArray($manyToOneElement->{'join-column'});
481 3
                } else if (isset($manyToOneElement->{'join-columns'})) {
482 2
                    foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
483 2
                        $joinColumns[] = $this->joinColumnToArray($joinColumnElement);
484
                    }
485
                }
486
487 8
                $mapping['joinColumns'] = $joinColumns;
488
489 8
                if (isset($manyToOneElement->cascade)) {
490 2
                    $mapping['cascade'] = $this->_getCascadeMappings($manyToOneElement->cascade);
491
                }
492
493
                // Evaluate second level cache
494 8
                if (isset($manyToOneElement->cache)) {
495 1
                    $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToOneElement->cache));
496
                }
497
498 8
                $metadata->mapManyToOne($mapping);
499
500
            }
501
        }
502
503
        // Evaluate <many-to-many ...> mappings
504 37
        if (isset($xmlRoot->{'many-to-many'})) {
505 12
            foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
506
                $mapping = [
507 12
                    'fieldName' => (string) $manyToManyElement['field'],
508 12
                    'targetEntity' => (string) $manyToManyElement['target-entity']
509
                ];
510
511 12
                if (isset($manyToManyElement['fetch'])) {
512 3
                    $mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToManyElement['fetch']);
513
                }
514
515 12
                if (isset($manyToManyElement['orphan-removal'])) {
516
                    $mapping['orphanRemoval'] = $this->evaluateBoolean($manyToManyElement['orphan-removal']);
517
                }
518
519 12
                if (isset($manyToManyElement['mapped-by'])) {
520 2
                    $mapping['mappedBy'] = (string) $manyToManyElement['mapped-by'];
521 10
                } else if (isset($manyToManyElement->{'join-table'})) {
522 8
                    if (isset($manyToManyElement['inversed-by'])) {
523 2
                        $mapping['inversedBy'] = (string) $manyToManyElement['inversed-by'];
524
                    }
525
526 8
                    $joinTableElement = $manyToManyElement->{'join-table'};
527
                    $joinTable = [
528 8
                        'name' => (string) $joinTableElement['name']
529
                    ];
530
531 8
                    if (isset($joinTableElement['schema'])) {
532
                        $joinTable['schema'] = (string) $joinTableElement['schema'];
533
                    }
534
535 8
                    foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
536 8
                        $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
537
                    }
538
539 8
                    foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
540 8
                        $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
541
                    }
542
543 8
                    $mapping['joinTable'] = $joinTable;
544
                }
545
546 12
                if (isset($manyToManyElement->cascade)) {
547 8
                    $mapping['cascade'] = $this->_getCascadeMappings($manyToManyElement->cascade);
548
                }
549
550 12
                if (isset($manyToManyElement->{'order-by'})) {
551
                    $orderBy = [];
552
                    foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
553
                        $orderBy[(string) $orderByField['name']] = (string) $orderByField['direction'];
554
                    }
555
                    $mapping['orderBy'] = $orderBy;
556
                }
557
558 12
                if (isset($manyToManyElement['index-by'])) {
559
                    $mapping['indexBy'] = (string) $manyToManyElement['index-by'];
560 12
                } else if (isset($manyToManyElement->{'index-by'})) {
561
                    throw new \InvalidArgumentException("<index-by /> is not a valid tag");
562
                }
563
564
                // Evaluate second level cache
565 12
                if (isset($manyToManyElement->cache)) {
566
                    $mapping['cache'] = $metadata->getAssociationCacheDefaults($mapping['fieldName'], $this->cacheToArray($manyToManyElement->cache));
567
                }
568
569 12
                $metadata->mapManyToMany($mapping);
570
            }
571
        }
572
573
        // Evaluate association-overrides
574 37
        if (isset($xmlRoot->{'attribute-overrides'})) {
575 2
            foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) {
576 2
                $fieldName = (string) $overrideElement['name'];
577 2
                foreach ($overrideElement->field as $field) {
578 2
                    $mapping = $this->columnToArray($field);
579 2
                    $mapping['fieldName'] = $fieldName;
580 2
                    $metadata->setAttributeOverride($fieldName, $mapping);
581
                }
582
            }
583
        }
584
585
        // Evaluate association-overrides
586 37
        if (isset($xmlRoot->{'association-overrides'})) {
587 4
            foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) {
588 4
                $fieldName  = (string) $overrideElement['name'];
589 4
                $override   = [];
590
591
                // Check for join-columns
592 4
                if (isset($overrideElement->{'join-columns'})) {
593 2
                    $joinColumns = [];
594 2
                    foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
595 2
                        $joinColumns[] = $this->joinColumnToArray($joinColumnElement);
596
                    }
597 2
                    $override['joinColumns'] = $joinColumns;
598
                }
599
600
                // Check for join-table
601 4
                if ($overrideElement->{'join-table'}) {
602 2
                    $joinTable          = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $joinTable is dead and can be removed.
Loading history...
603 2
                    $joinTableElement   = $overrideElement->{'join-table'};
604
605
                    $joinTable = [
606 2
                        'name'      => (string) $joinTableElement['name'],
607 2
                        'schema'    => (string) $joinTableElement['schema']
608
                    ];
609
610 2
                    if (isset($joinTableElement->{'join-columns'})) {
611 2
                        foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
612 2
                            $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
613
                        }
614
                    }
615
616 2
                    if (isset($joinTableElement->{'inverse-join-columns'})) {
617 2
                        foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
618 2
                            $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
619
                        }
620
                    }
621
622 2
                    $override['joinTable'] = $joinTable;
623
                }
624
625
                // Check for inversed-by
626 4
                if (isset($overrideElement->{'inversed-by'})) {
627 1
                    $override['inversedBy'] = (string) $overrideElement->{'inversed-by'}['name'];
628
                }
629
630
                // Check for `fetch`
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
631 4
                if (isset($overrideElement['fetch'])) {
632 1
                    $override['fetch'] = constant(Metadata::class . '::FETCH_' . (string) $overrideElement['fetch']);
633
                }
634
635 4
                $metadata->setAssociationOverride($fieldName, $override);
636
            }
637
        }
638
639
        // Evaluate <lifecycle-callbacks...>
640 37
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
641 5
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
642 5
                $metadata->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ORM\Events::' . (string) $lifecycleCallback['type']));
643
            }
644
        }
645
646
        // Evaluate entity listener
647 37
        if (isset($xmlRoot->{'entity-listeners'})) {
648 6
            foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) {
649 6
                $className = (string) $listenerElement['class'];
650
                // Evaluate the listener using naming convention.
651 6
                if ($listenerElement->count() === 0) {
652 2
                    EntityListenerBuilder::bindEntityListener($metadata, $className);
653
654 2
                    continue;
655
                }
656
657 4
                foreach ($listenerElement as $callbackElement) {
658 4
                    $eventName   = (string) $callbackElement['type'];
659 4
                    $methodName  = (string) $callbackElement['method'];
660
661 4
                    $metadata->addEntityListener($eventName, $className, $methodName);
662
                }
663
            }
664
        }
665 37
    }
666
667
    /**
668
     * Parses (nested) option elements.
669
     *
670
     * @param SimpleXMLElement $options The XML element.
671
     *
672
     * @return array The options array.
673
     */
674 6
    private function _parseOptions(SimpleXMLElement $options)
675
    {
676 6
        $array = [];
677
678
        /* @var $option SimpleXMLElement */
679 6
        foreach ($options as $option) {
680 6
            if ($option->count()) {
681 5
                $value = $this->_parseOptions($option->children());
682
            } else {
683 6
                $value = (string) $option;
684
            }
685
686 6
            $attributes = $option->attributes();
687
688 6
            if (isset($attributes->name)) {
689 6
                $nameAttribute = (string) $attributes->name;
690 6
                $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'])
691 5
                    ? $this->evaluateBoolean($value)
692 6
                    : $value;
693
            } else {
694 6
                $array[] = $value;
695
            }
696
        }
697
698 6
        return $array;
699
    }
700
701
    /**
702
     * Constructs a joinColumn mapping array based on the information
703
     * found in the given SimpleXMLElement.
704
     *
705
     * @param SimpleXMLElement $joinColumnElement The XML element.
706
     *
707
     * @return array The mapping array.
708
     */
709 14
    private function joinColumnToArray(SimpleXMLElement $joinColumnElement)
710
    {
711
        $joinColumn = [
712 14
            'name' => (string) $joinColumnElement['name'],
713 14
            'referencedColumnName' => (string) $joinColumnElement['referenced-column-name']
714
        ];
715
716 14
        if (isset($joinColumnElement['unique'])) {
717 4
            $joinColumn['unique'] = $this->evaluateBoolean($joinColumnElement['unique']);
718
        }
719
720 14
        if (isset($joinColumnElement['nullable'])) {
721 6
            $joinColumn['nullable'] = $this->evaluateBoolean($joinColumnElement['nullable']);
722
        }
723
724 14
        if (isset($joinColumnElement['on-delete'])) {
725 6
            $joinColumn['onDelete'] = (string) $joinColumnElement['on-delete'];
726
        }
727
728 14
        if (isset($joinColumnElement['column-definition'])) {
729 5
            $joinColumn['columnDefinition'] = (string) $joinColumnElement['column-definition'];
730
        }
731
732 14
        return $joinColumn;
733
    }
734
735
     /**
736
     * Parses the given field as array.
737
     *
738
     * @param SimpleXMLElement $fieldMapping
739
     *
740
     * @return array
741
     */
742 24
    private function columnToArray(SimpleXMLElement $fieldMapping)
743
    {
744
        $mapping = [
745 24
            'fieldName' => (string) $fieldMapping['name'],
746
        ];
747
748 24
        if (isset($fieldMapping['type'])) {
749 20
            $mapping['type'] = (string) $fieldMapping['type'];
750
        }
751
752 24
        if (isset($fieldMapping['column'])) {
753 16
            $mapping['columnName'] = (string) $fieldMapping['column'];
754
        }
755
756 24
        if (isset($fieldMapping['length'])) {
757 11
            $mapping['length'] = (int) $fieldMapping['length'];
758
        }
759
760 24
        if (isset($fieldMapping['precision'])) {
761 1
            $mapping['precision'] = (int) $fieldMapping['precision'];
762
        }
763
764 24
        if (isset($fieldMapping['scale'])) {
765 1
            $mapping['scale'] = (int) $fieldMapping['scale'];
766
        }
767
768 24
        if (isset($fieldMapping['unique'])) {
769 10
            $mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']);
770
        }
771
772 24
        if (isset($fieldMapping['nullable'])) {
773 9
            $mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']);
774
        }
775
776 24
        if (isset($fieldMapping['version']) && $fieldMapping['version']) {
777 3
            $mapping['version'] = $this->evaluateBoolean($fieldMapping['version']);
778
        }
779
780 24
        if (isset($fieldMapping['column-definition'])) {
781 6
            $mapping['columnDefinition'] = (string) $fieldMapping['column-definition'];
782
        }
783
784 24
        if (isset($fieldMapping->options)) {
785 5
            $mapping['options'] = $this->_parseOptions($fieldMapping->options->children());
786
        }
787
788 24
        return $mapping;
789
    }
790
791
    /**
792
     * Parse / Normalize the cache configuration
793
     *
794
     * @param SimpleXMLElement $cacheMapping
795
     *
796
     * @return array
797
     */
798 2
    private function cacheToArray(SimpleXMLElement $cacheMapping)
799
    {
800 2
        $region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null;
801 2
        $usage  = isset($cacheMapping['usage']) ? strtoupper($cacheMapping['usage']) : null;
802
803 2
        if ($usage && ! defined('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage)) {
804
            throw new \InvalidArgumentException(sprintf('Invalid cache usage "%s"', $usage));
805
        }
806
807 2
        if ($usage) {
808 2
            $usage = constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage);
809
        }
810
811
        return [
812 2
            'usage'  => $usage,
813 2
            'region' => $region,
814
        ];
815
    }
816
817
    /**
818
     * Gathers a list of cascade options found in the given cascade element.
819
     *
820
     * @param SimpleXMLElement $cascadeElement The cascade element.
821
     *
822
     * @return array The list of cascade options.
823
     */
824 8
    private function _getCascadeMappings(SimpleXMLElement $cascadeElement)
825
    {
826 8
        $cascades = [];
827
        /* @var $action SimpleXmlElement */
828 8
        foreach ($cascadeElement->children() as $action) {
829
            // According to the JPA specifications, XML uses "cascade-persist"
830
            // instead of "persist". Here, both variations
831
            // are supported because both YAML and Annotation use "persist"
832
            // and we want to make sure that this driver doesn't need to know
833
            // anything about the supported cascading actions
834 8
            $cascades[] = str_replace('cascade-', '', $action->getName());
835
        }
836
837 8
        return $cascades;
838
    }
839
840
    /**
841
     * {@inheritDoc}
842
     */
843 40
    protected function loadMappingFile($file)
844
    {
845 40
        $this->validateMapping($file);
846 39
        $result = [];
847
        // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577
848 39
        $xmlElement = simplexml_load_string(file_get_contents($file));
849
850 39
        if (isset($xmlElement->entity)) {
851 38
            foreach ($xmlElement->entity as $entityElement) {
852 38
                $entityName = (string) $entityElement['name'];
853 38
                $result[$entityName] = $entityElement;
854
            }
855 8
        } else if (isset($xmlElement->{'mapped-superclass'})) {
856 5
            foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) {
857 5
                $className = (string) $mappedSuperClass['name'];
858 5
                $result[$className] = $mappedSuperClass;
859
            }
860 3
        } else if (isset($xmlElement->embeddable)) {
861 3
            foreach ($xmlElement->embeddable as $embeddableElement) {
862 3
                $embeddableName = (string) $embeddableElement['name'];
863 3
                $result[$embeddableName] = $embeddableElement;
864
            }
865
        }
866
867 39
        return $result;
868
    }
869
870 40
    private function validateMapping(string $file) : void
871
    {
872 40
        $backedUpErrorSetting = libxml_use_internal_errors(true);
873
874 40
        $document = new DOMDocument();
875 40
        $document->load($file);
876
877
        try {
878 40
            if ( ! $document->schemaValidate(__DIR__ . '/../../../../../doctrine-mapping.xsd')) {
879 1
                throw MappingException::fromLibXmlErrors(libxml_get_errors());
880
            }
881 39
        } finally {
882 40
            libxml_clear_errors();
883 40
            libxml_use_internal_errors($backedUpErrorSetting);
884
        }
885 39
    }
886
887
    /**
888
     * @param mixed $element
889
     *
890
     * @return bool
891
     */
892 14
    protected function evaluateBoolean($element)
893
    {
894 14
        $flag = (string) $element;
895
896 14
        return ($flag == "true" || $flag == "1");
897
    }
898
}
899