Failed Conditions
Push — master ( b07393...21bc80 )
by Guilherme
09:49
created

XmlDriver::convertTableElementToTableAnnotation()   B

Complexity

Conditions 8
Paths 32

Size

Total Lines 42
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 8

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 18
c 1
b 0
f 0
nc 32
nop 1
dl 0
loc 42
ccs 19
cts 19
cp 1
crap 8
rs 8.4444
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\ORM\Annotation;
9
use Doctrine\ORM\Events;
10
use Doctrine\ORM\Mapping;
11
use Doctrine\ORM\Mapping\Builder;
12
use InvalidArgumentException;
13
use SimpleXMLElement;
14
use function class_exists;
15
use function constant;
16
use function explode;
17
use function file_get_contents;
18
use function in_array;
19
use function simplexml_load_string;
20
use function str_replace;
21
use function strtoupper;
22
23
/**
24
 * XmlDriver is a metadata driver that enables mapping through XML files.
25
 */
26
class XmlDriver extends FileDriver
27
{
28
    public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
29
30
    /**
31
     * {@inheritDoc}
32
     */
33 41
    public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
34
    {
35 41
        parent::__construct($locator, $fileExtension);
36 41
    }
37
38 36
    public function loadMetadataForClass(
39
        string $className,
40
        ?Mapping\ComponentMetadata $parent,
41
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
42
    ) : Mapping\ComponentMetadata {
43
        /** @var SimpleXMLElement $xmlRoot */
44 36
        $xmlRoot       = $this->getElement($className);
45 34
        $classBuilder  = new Builder\ClassMetadataBuilder($metadataBuildingContext);
46
        $classMetadata = $classBuilder
47 34
            ->withClassName($className)
48 34
            ->withParentMetadata($parent)
49 34
            ->withEntityAnnotation(
50 34
                $xmlRoot->getName() === 'entity'
51 34
                    ? $this->convertEntityElementToEntityAnnotation($xmlRoot)
52 34
                    : null
53
            )
54 34
            ->withMappedSuperclassAnnotation(
55 34
                $xmlRoot->getName() === 'mapped-superclass'
56 5
                    ? $this->convertMappedSuperclassElementToMappedSuperclassAnnotation($xmlRoot)
57 34
                    : null
58
            )
59 34
            ->withEmbeddableAnnotation(
60 34
                $xmlRoot->getName() === 'embeddable'
61
                    ? null
62 34
                    : null
63
            )
64 34
            ->withTableAnnotation(
65
                // @todo guilhermeblanco Is this the proper check to build Table annotation?
66 34
                $xmlRoot->getName() === 'entity'
67 34
                    ? $this->convertTableElementToTableAnnotation($xmlRoot)
68 34
                    : null
69
            )
70 34
            ->withInheritanceTypeAnnotation(
71 34
                isset($xmlRoot['inheritance-type'])
72 10
                    ? $this->convertInheritanceTypeElementToInheritanceTypeAnnotation($xmlRoot)
73 34
                    : null
74
            )
75 34
            ->withDiscriminatorColumnAnnotation(
76 34
                isset($xmlRoot->{'discriminator-column'})
77 8
                    ? $this->convertDiscrimininatorColumnElementToDiscriminatorColumnAnnotation($xmlRoot->{'discriminator-column'})
78 34
                    : null
79
            )
80 34
            ->withDiscriminatorMapAnnotation(
81 34
                isset($xmlRoot->{'discriminator-map'})
82 10
                    ? $this->convertDiscriminatorMapElementToDiscriminatorMapAnnotation($xmlRoot->{'discriminator-map'})
83 34
                    : null
84
            )
85 34
            ->withChangeTrackingPolicyAnnotation(
86 34
                isset($xmlRoot['change-tracking-policy'])
87
                    ? $this->convertChangeTrackingPolicyElementToChangeTrackingPolicyAnnotation($xmlRoot)
88 34
                    : null
89
            )
90 34
            ->withCacheAnnotation(
91 34
                isset($xmlRoot->cache)
92 2
                    ? $this->convertCacheElementToCacheAnnotation($xmlRoot->cache)
93 34
                    : null
94
            )
95 34
            ->build();
96
97 34
        $propertyBuilder = new Builder\PropertyMetadataBuilder($metadataBuildingContext);
98
99 34
        $propertyBuilder->withComponentMetadata($classMetadata);
100
101
        // Evaluate <field ...> mappings
102 34
        if (isset($xmlRoot->field)) {
103 20
            foreach ($xmlRoot->field as $fieldElement) {
104
                $propertyBuilder
105 20
                    ->withFieldName((string) $fieldElement['name'])
106 20
                    ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($fieldElement))
107 20
                    ->withIdAnnotation(null)
108 20
                    ->withVersionAnnotation(
109 20
                        isset($fieldElement['version']) && $this->evaluateBoolean($fieldElement['version'])
110 3
                            ? new Annotation\Version()
111 20
                            : null
112
                    );
113
114 20
                $classMetadata->addProperty($propertyBuilder->build());
115
            }
116
        }
117
118 34
        if (isset($xmlRoot->embedded)) {
119
            foreach ($xmlRoot->embedded as $embeddedMapping) {
120
                $columnPrefix = isset($embeddedMapping['column-prefix'])
121
                    ? (string) $embeddedMapping['column-prefix']
122
                    : null;
123
124
                $useColumnPrefix = isset($embeddedMapping['use-column-prefix'])
125
                    ? $this->evaluateBoolean($embeddedMapping['use-column-prefix'])
126
                    : true;
127
128
                $mapping = [
129
                    'fieldName'    => (string) $embeddedMapping['name'],
130
                    'class'        => (string) $embeddedMapping['class'],
131
                    'columnPrefix' => $useColumnPrefix ? $columnPrefix : false,
132
                ];
133
134
                $classMetadata->mapEmbedded($mapping);
135
            }
136
        }
137
138
        // Evaluate <id ...> mappings
139 34
        $associationIds = [];
140
141 34
        foreach ($xmlRoot->id as $idElement) {
142 29
            $fieldName = (string) $idElement['name'];
143
144 29
            if (isset($idElement['association-key']) && $this->evaluateBoolean($idElement['association-key'])) {
145 2
                $associationIds[$fieldName] = true;
146
147 2
                continue;
148
            }
149
150
            $propertyBuilder
151 28
                ->withFieldName($fieldName)
152 28
                ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($idElement))
153 28
                ->withIdAnnotation(new Annotation\Id())
154 28
                ->withVersionAnnotation(
155 28
                    isset($idElement['version']) && $this->evaluateBoolean($idElement['version'])
156
                        ? new Annotation\Version()
157 28
                        : null
158
                )
159 28
                ->withGeneratedValueAnnotation(
160 28
                    isset($idElement->generator)
161 27
                        ? $this->convertGeneratorElementToGeneratedValueAnnotation($idElement->generator)
162 28
                        : null
163
                )
164 28
                ->withSequenceGeneratorAnnotation(
165 28
                    isset($idElement->{'sequence-generator'})
166 3
                        ? $this->convertSequenceGeneratorElementToSequenceGeneratorAnnotation($idElement->{'sequence-generator'})
167 28
                        : null
168
                )
169 28
                ->withCustomIdGeneratorAnnotation(
170 28
                    isset($idElement->{'custom-id-generator'})
171 2
                        ? $this->convertCustomIdGeneratorElementToCustomIdGeneratorAnnotation($idElement->{'custom-id-generator'})
172 28
                        : null
173
                );
174
175 28
            $classMetadata->addProperty($propertyBuilder->build());
176
        }
177
178
        // Evaluate <one-to-one ...> mappings
179 34
        if (isset($xmlRoot->{'one-to-one'})) {
180 7
            foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
181 7
                $fieldName = (string) $oneToOneElement['field'];
182
183
                $propertyBuilder
184 7
                    ->withFieldName($fieldName)
185 7
                    ->withOneToOneAnnotation($this->convertOneToOneElementToOneToOneAnnotation($oneToOneElement))
186 7
                    ->withIdAnnotation(isset($associationIds[$fieldName]) ? new Annotation\Id() : null)
187 7
                    ->withVersionAnnotation(null)
188 7
                    ->withCacheAnnotation(
189 7
                        isset($oneToOneElement->cache)
190
                            ? $this->convertCacheElementToCacheAnnotation($oneToOneElement->cache)
191 7
                            : null
192
                    )
193 7
                    ->withJoinColumnAnnotation(
194 7
                        isset($oneToOneElement->{'join-column'})
195 5
                            ? $this->convertJoinColumnElementToJoinColumnAnnotation($oneToOneElement->{'join-column'})
196 7
                            : null
197
                    )
198 7
                    ->withJoinColumnsAnnotation(
199 7
                        isset($oneToOneElement->{'join-columns'})
200
                            ? $this->convertJoinColumnsElementToJoinColumnsAnnotation($oneToOneElement->{'join-columns'})
201 7
                            : null
202
                    );
203
204 7
                $classMetadata->addProperty($propertyBuilder->build());
205
            }
206
        }
207
208
        // Evaluate <many-to-one ...> mappings
209 34
        if (isset($xmlRoot->{'many-to-one'})) {
210 8
            foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
211 8
                $fieldName = (string) $manyToOneElement['field'];
212
213
                $propertyBuilder
214 8
                    ->withFieldName($fieldName)
215 8
                    ->withManyToOneAnnotation($this->convertManyToOneElementToManyToOneAnnotation($manyToOneElement))
216 8
                    ->withIdAnnotation(isset($associationIds[$fieldName]) ? new Annotation\Id() : null)
217 8
                    ->withVersionAnnotation(null)
218 8
                    ->withCacheAnnotation(
219 8
                        isset($manyToOneElement->cache)
220 1
                            ? $this->convertCacheElementToCacheAnnotation($manyToOneElement->cache)
221 8
                            : null
222
                    )
223 8
                    ->withJoinColumnAnnotation(
224 8
                        isset($manyToOneElement->{'join-column'})
225 7
                            ? $this->convertJoinColumnElementToJoinColumnAnnotation($manyToOneElement->{'join-column'})
226 8
                            : null
227
                    )
228 8
                    ->withJoinColumnsAnnotation(
229 8
                        isset($manyToOneElement->{'join-columns'})
230 1
                            ? $this->convertJoinColumnsElementToJoinColumnsAnnotation($manyToOneElement->{'join-columns'})
231 8
                            : null
232
                    );
233
234 8
                $classMetadata->addProperty($propertyBuilder->build());
235
            }
236
        }
237
238
        // Evaluate <one-to-many ...> mappings
239 33
        if (isset($xmlRoot->{'one-to-many'})) {
240 9
            foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
241 9
                $fieldName = (string) $oneToManyElement['field'];
242
243
                $propertyBuilder
244 9
                    ->withFieldName($fieldName)
245 9
                    ->withOneToManyAnnotation($this->convertOneToManyElementToOneToManyAnnotation($oneToManyElement))
246 9
                    ->withIdAnnotation(isset($associationIds[$fieldName]) ? new Annotation\Id() : null)
247 9
                    ->withVersionAnnotation(null)
248 9
                    ->withCacheAnnotation(
249 9
                        isset($oneToManyElement->cache)
250 1
                            ? $this->convertCacheElementToCacheAnnotation($oneToManyElement->cache)
251 9
                            : null
252
                    )
253 9
                    ->withOrderByAnnotation(
254 9
                        isset($oneToManyElement->{'order-by'})
255 5
                            ? $this->convertOrderByElementToOrderByAnnotation($oneToManyElement->{'order-by'})
256 9
                            : null
257
                    );
258
259 9
                $classMetadata->addProperty($propertyBuilder->build());
260
            }
261
        }
262
263
        // Evaluate <many-to-many ...> mappings
264 33
        if (isset($xmlRoot->{'many-to-many'})) {
265 14
            foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
266 14
                $fieldName = (string) $manyToManyElement['field'];
267
268
                $propertyBuilder
269 14
                    ->withFieldName($fieldName)
270 14
                    ->withManyToManyAnnotation($this->convertManyToManyElementToManyToManyAnnotation($manyToManyElement))
271 14
                    ->withIdAnnotation(isset($associationIds[$fieldName]) ? new Annotation\Id() : null)
272 14
                    ->withVersionAnnotation(null)
273 14
                    ->withCacheAnnotation(
274 14
                        isset($manyToManyElement->cache)
275
                            ? $this->convertCacheElementToCacheAnnotation($manyToManyElement->cache)
276 14
                            : null
277
                    )
278 14
                    ->withJoinTableAnnotation(
279 14
                        isset($manyToManyElement->{'join-table'})
280 8
                            ? $this->convertJoinTableElementToJoinTableAnnotation($manyToManyElement->{'join-table'})
281 14
                            : null
282
                    )
283 14
                    ->withOrderByAnnotation(
284 14
                        isset($manyToManyElement->{'order-by'})
285 1
                            ? $this->convertOrderByElementToOrderByAnnotation($manyToManyElement->{'order-by'})
286 14
                            : null
287
                    );
288
289 14
                $classMetadata->addProperty($propertyBuilder->build());
290
            }
291
        }
292
293
        // Evaluate association-overrides
294 33
        if (isset($xmlRoot->{'attribute-overrides'})) {
295 2
            $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext);
296
297
            $fieldBuilder
298 2
                ->withComponentMetadata($classMetadata);
299
300 2
            foreach ($xmlRoot->{'attribute-overrides'}->{'attribute-override'} as $overrideElement) {
301 2
                $fieldName = (string) $overrideElement['name'];
302 2
                $property  = $classMetadata->getProperty($fieldName);
303
304 2
                if (! $property) {
305
                    throw Mapping\MappingException::invalidOverrideFieldName($classMetadata->getClassName(), $fieldName);
306
                }
307
308 2
                foreach ($overrideElement->field as $fieldElement) {
309 2
                    $versionAnnotation = isset($fieldElement['version']) && $this->evaluateBoolean($fieldElement['version'])
310
                        ? new Annotation\Version()
311 2
                        : null;
312
313
                    $fieldBuilder
314 2
                        ->withFieldName($fieldName)
315 2
                        ->withColumnAnnotation($this->convertFieldElementToColumnAnnotation($fieldElement))
316 2
                        ->withIdAnnotation(null)
317 2
                        ->withVersionAnnotation($versionAnnotation);
318
319 2
                    $fieldMetadata = $fieldBuilder->build();
320 2
                    $columnName    = $fieldMetadata->getColumnName();
321
322
                    // Prevent column duplication
323 2
                    if ($classMetadata->checkPropertyDuplication($columnName)) {
0 ignored issues
show
Bug introduced by
It seems like $columnName can also be of type null; however, parameter $columnName of Doctrine\ORM\Mapping\Cla...ckPropertyDuplication() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

323
                    if ($classMetadata->checkPropertyDuplication(/** @scrutinizer ignore-type */ $columnName)) {
Loading history...
324
                        throw Mapping\MappingException::duplicateColumnName($classMetadata->getClassName(), $columnName);
325
                    }
326
327 2
                    $classMetadata->setPropertyOverride($fieldMetadata);
328
                }
329
            }
330
        }
331
332
        // Evaluate association-overrides
333 33
        if (isset($xmlRoot->{'association-overrides'})) {
334 4
            foreach ($xmlRoot->{'association-overrides'}->{'association-override'} as $overrideElement) {
335 4
                $fieldName = (string) $overrideElement['name'];
336 4
                $property  = $classMetadata->getProperty($fieldName);
337
338 4
                if (! $property) {
339
                    throw Mapping\MappingException::invalidOverrideFieldName($classMetadata->getClassName(), $fieldName);
340
                }
341
342 4
                $override = clone $property;
343
344
                // Check for join-columns
345 4
                if (isset($overrideElement->{'join-columns'})) {
346 2
                    $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
347
348
                    $joinColumnBuilder
349 2
                        ->withComponentMetadata($classMetadata)
350 2
                        ->withFieldName($override->getName());
351
352 2
                    $joinColumns = [];
353
354 2
                    foreach ($overrideElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
355 2
                        $joinColumnBuilder->withJoinColumnAnnotation(
356 2
                            $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement)
357
                        );
358
359 2
                        $joinColumnMetadata = $joinColumnBuilder->build();
360 2
                        $columnName         = $joinColumnMetadata->getColumnName();
0 ignored issues
show
Unused Code introduced by
The assignment to $columnName is dead and can be removed.
Loading history...
361
362
                        // @todo guilhermeblanco Open an issue to discuss making this scenario impossible.
363
                        //if ($metadata->checkPropertyDuplication($columnName)) {
364
                        //    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
365
                        //}
366
367 2
                        $joinColumns[] = $joinColumnMetadata;
368
                    }
369
370 2
                    $override->setJoinColumns($joinColumns);
0 ignored issues
show
Bug introduced by
The method setJoinColumns() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\ToOneAssociationMetadata. ( Ignorable by Annotation )

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

370
                    $override->/** @scrutinizer ignore-call */ 
371
                               setJoinColumns($joinColumns);
Loading history...
371
                }
372
373
                // Check for join-table
374 4
                if ($overrideElement->{'join-table'}) {
375 2
                    $joinTableElement    = $overrideElement->{'join-table'};
376 2
                    $joinTableAnnotation = $this->convertJoinTableElementToJoinTableAnnotation($joinTableElement);
377 2
                    $joinTableBuilder    = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
378
379
                    $joinTableBuilder
380 2
                        ->withComponentMetadata($classMetadata)
381 2
                        ->withFieldName($property->getName())
382 2
                        ->withTargetEntity($property->getTargetEntity())
0 ignored issues
show
Bug introduced by
The method getTargetEntity() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

382
                        ->withTargetEntity($property->/** @scrutinizer ignore-call */ getTargetEntity())
Loading history...
383 2
                        ->withJoinTableAnnotation($joinTableAnnotation);
384
385 2
                    $override->setJoinTable($joinTableBuilder->build());
0 ignored issues
show
Bug introduced by
The method setJoinTable() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\ManyToManyAssociationMetadata. ( Ignorable by Annotation )

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

385
                    $override->/** @scrutinizer ignore-call */ 
386
                               setJoinTable($joinTableBuilder->build());
Loading history...
386
                }
387
388
                // Check for inversed-by
389 4
                if (isset($overrideElement->{'inversed-by'})) {
390 1
                    $override->setInversedBy((string) $overrideElement->{'inversed-by'}['name']);
0 ignored issues
show
Bug introduced by
The method setInversedBy() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

390
                    $override->/** @scrutinizer ignore-call */ 
391
                               setInversedBy((string) $overrideElement->{'inversed-by'}['name']);
Loading history...
391
                }
392
393
                // Check for fetch
394 4
                if (isset($overrideElement['fetch'])) {
395 1
                    $override->setFetchMode(
0 ignored issues
show
Bug introduced by
The method setFetchMode() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

395
                    $override->/** @scrutinizer ignore-call */ 
396
                               setFetchMode(
Loading history...
396 1
                        constant('Doctrine\ORM\Mapping\FetchMode::' . (string) $overrideElement['fetch'])
397
                    );
398
                }
399
400 4
                $classMetadata->setPropertyOverride($override);
401
            }
402
        }
403
404
        // Evaluate <lifecycle-callbacks...>
405 33
        if (isset($xmlRoot->{'lifecycle-callbacks'})) {
406 3
            foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
407 3
                $eventName  = constant(Events::class . '::' . (string) $lifecycleCallback['type']);
408 3
                $methodName = (string) $lifecycleCallback['method'];
409
410 3
                $classMetadata->addLifecycleCallback($eventName, $methodName);
411
            }
412
        }
413
414
        // Evaluate entity listener
415 33
        if (isset($xmlRoot->{'entity-listeners'})) {
416 2
            foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) {
417 2
                $listenerClassName = (string) $listenerElement['class'];
418
419 2
                if (! class_exists($listenerClassName)) {
420
                    throw Mapping\MappingException::entityListenerClassNotFound(
421
                        $listenerClassName,
422
                        $classMetadata->getClassName()
423
                    );
424
                }
425
426 2
                foreach ($listenerElement as $callbackElement) {
427 2
                    $eventName  = (string) $callbackElement['type'];
428 2
                    $methodName = (string) $callbackElement['method'];
429
430 2
                    $classMetadata->addEntityListener($eventName, $listenerClassName, $methodName);
431
                }
432
            }
433
        }
434
435 33
        return $classMetadata;
436
    }
437
438
    /**
439
     * {@inheritDoc}
440
     */
441 36
    protected function loadMappingFile($file)
442
    {
443 36
        $result = [];
444
        // Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577
445 36
        $xmlElement = simplexml_load_string(file_get_contents($file));
446
447 36
        if (isset($xmlElement->entity)) {
448 35
            foreach ($xmlElement->entity as $entityElement) {
449 35
                $entityName          = (string) $entityElement['name'];
450 35
                $result[$entityName] = $entityElement;
451
            }
452 6
        } elseif (isset($xmlElement->{'mapped-superclass'})) {
453 5
            foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) {
454 5
                $className          = (string) $mappedSuperClass['name'];
455 5
                $result[$className] = $mappedSuperClass;
456
            }
457 1
        } elseif (isset($xmlElement->embeddable)) {
458
            foreach ($xmlElement->embeddable as $embeddableElement) {
459
                $embeddableName          = (string) $embeddableElement['name'];
460
                $result[$embeddableName] = $embeddableElement;
461
            }
462
        }
463
464 36
        return $result;
465
    }
466
467 34
    private function convertEntityElementToEntityAnnotation(
468
        SimpleXMLElement $entityElement
469
    ) : Annotation\Entity {
470 34
        $entityAnnotation = new Annotation\Entity();
471
472 34
        if (isset($entityElement['repository-class'])) {
473
            $entityAnnotation->repositoryClass = (string) $entityElement['repository-class'];
474
        }
475
476 34
        if (isset($entityElement['read-only'])) {
477
            $entityAnnotation->readOnly = $this->evaluateBoolean($entityElement['read-only']);
478
        }
479
480 34
        return $entityAnnotation;
481
    }
482
483 5
    private function convertMappedSuperclassElementToMappedSuperclassAnnotation(
484
        SimpleXMLElement $mappedSuperclassElement
485
    ) : Annotation\MappedSuperclass {
486 5
        $mappedSuperclassAnnotation = new Annotation\MappedSuperclass();
487
488 5
        if (isset($mappedSuperclassElement['repository-class'])) {
489 1
            $mappedSuperclassAnnotation->repositoryClass = (string) $mappedSuperclassElement['repository-class'];
490
        }
491
492 5
        return $mappedSuperclassAnnotation;
493
    }
494
495 34
    private function convertTableElementToTableAnnotation(
496
        SimpleXMLElement $tableElement
497
    ) : Annotation\Table {
498 34
        $tableAnnotation = new Annotation\Table();
499
500
        // Evaluate <entity...> attributes
501 34
        if (isset($tableElement['table'])) {
502 12
            $tableAnnotation->name = (string) $tableElement['table'];
503
        }
504
505 34
        if (isset($tableElement['schema'])) {
506 2
            $tableAnnotation->schema = (string) $tableElement['schema'];
507
        }
508
509
        // Evaluate <indexes...>
510 34
        if (isset($tableElement->indexes)) {
511 4
            $indexes = [];
512
513
            /** @var SimpleXMLElement $indexElement */
514 4
            foreach ($tableElement->indexes->children() as $indexElement) {
515 4
                $indexes[] = $this->convertIndexElementToIndexAnnotation($indexElement);
516
            }
517
518 4
            $tableAnnotation->indexes = $indexes;
519
        }
520
521
        // Evaluate <unique-constraints..>
522 34
        if (isset($tableElement->{'unique-constraints'})) {
523 3
            $uniqueConstraints = [];
524
525 3
            foreach ($tableElement->{'unique-constraints'}->children() as $uniqueConstraintElement) {
526 3
                $uniqueConstraints[] = $this->convertUniqueConstraintElementToUniqueConstraintAnnotation($uniqueConstraintElement);
527
            }
528
529 3
            $tableAnnotation->uniqueConstraints = $uniqueConstraints;
530
        }
531
532 34
        if (isset($tableElement->options)) {
533 3
            $tableAnnotation->options = $this->parseOptions($tableElement->options->children());
534
        }
535
536 34
        return $tableAnnotation;
537
    }
538
539 4
    private function convertIndexElementToIndexAnnotation(
540
        SimpleXMLElement $indexElement
541
    ) : Annotation\Index {
542 4
        $indexAnnotation = new Annotation\Index();
543
544 4
        $indexAnnotation->columns = explode(',', (string) $indexElement['columns']);
545 4
        $indexAnnotation->options = isset($indexElement->options) ? $this->parseOptions($indexElement->options->children()) : [];
546 4
        $indexAnnotation->flags   = isset($indexElement['flags']) ? explode(',', (string) $indexElement['flags']) : [];
547
548 4
        if (isset($indexElement['name'])) {
549 3
            $indexAnnotation->name = (string) $indexElement['name'];
550
        }
551
552 4
        if (isset($indexElement['unique'])) {
553
            $indexAnnotation->unique = $this->evaluateBoolean($indexElement['unique']);
554
        }
555
556 4
        return $indexAnnotation;
557
    }
558
559 3
    private function convertUniqueConstraintElementToUniqueConstraintAnnotation(
560
        SimpleXMLElement $uniqueConstraintElement
561
    ) : Annotation\UniqueConstraint {
562 3
        $uniqueConstraintAnnotation = new Annotation\UniqueConstraint();
563
564 3
        $uniqueConstraintAnnotation->columns = explode(',', (string) $uniqueConstraintElement['columns']);
565 3
        $uniqueConstraintAnnotation->options = isset($uniqueConstraintElement->options) ? $this->parseOptions($uniqueConstraintElement->options->children()) : [];
566 3
        $uniqueConstraintAnnotation->flags   = isset($uniqueConstraintElement['flags']) ? explode(',', (string) $uniqueConstraintElement['flags']) : [];
567
568 3
        if (isset($uniqueConstraintElement['name'])) {
569 3
            $uniqueConstraintAnnotation->name = (string) $uniqueConstraintElement['name'];
570
        }
571
572 3
        return $uniqueConstraintAnnotation;
573
    }
574
575 10
    private function convertInheritanceTypeElementToInheritanceTypeAnnotation(
576
        SimpleXMLElement $inheritanceTypeElement
577
    ) : Annotation\InheritanceType {
578 10
        $inheritanceTypeAnnotation = new Annotation\InheritanceType();
579
580 10
        $inheritanceTypeAnnotation->value = strtoupper((string) $inheritanceTypeElement['inheritance-type']);
581
582 10
        return $inheritanceTypeAnnotation;
583
    }
584
585
    private function convertChangeTrackingPolicyElementToChangeTrackingPolicyAnnotation(
586
        SimpleXMLElement $changeTrackingPolicyElement
587
    ) : Annotation\ChangeTrackingPolicy {
588
        $changeTrackingPolicyAnnotation = new Annotation\ChangeTrackingPolicy();
589
590
        $changeTrackingPolicyAnnotation->value = strtoupper((string) $changeTrackingPolicyElement['change-tracking-policy']);
591
592
        return $changeTrackingPolicyAnnotation;
593
    }
594
595 8
    private function convertDiscrimininatorColumnElementToDiscriminatorColumnAnnotation(
596
        SimpleXMLElement $discriminatorColumnElement
597
    ) : Annotation\DiscriminatorColumn {
598 8
        $discriminatorColumnAnnotation = new Annotation\DiscriminatorColumn();
599
600 8
        $discriminatorColumnAnnotation->type = (string) ($discriminatorColumnElement['type'] ?? 'string');
601 8
        $discriminatorColumnAnnotation->name = (string) $discriminatorColumnElement['name'];
602
603 8
        if (isset($discriminatorColumnElement['column-definition'])) {
604 1
            $discriminatorColumnAnnotation->columnDefinition = (string) $discriminatorColumnElement['column-definition'];
605
        }
606
607 8
        if (isset($discriminatorColumnElement['length'])) {
608 3
            $discriminatorColumnAnnotation->length = (int) $discriminatorColumnElement['length'];
609
        }
610
611 8
        return $discriminatorColumnAnnotation;
612
    }
613
614 10
    private function convertDiscriminatorMapElementToDiscriminatorMapAnnotation(
615
        SimpleXMLElement $discriminatorMapElement
616
    ) : Annotation\DiscriminatorMap {
617 10
        $discriminatorMapAnnotation = new Annotation\DiscriminatorMap();
618 10
        $discriminatorMap           = [];
619
620 10
        foreach ($discriminatorMapElement->{'discriminator-mapping'} as $discriminatorMapElement) {
621 10
            $discriminatorMap[(string) $discriminatorMapElement['value']] = (string) $discriminatorMapElement['class'];
622
        }
623
624 10
        $discriminatorMapAnnotation->value = $discriminatorMap;
625
626 10
        return $discriminatorMapAnnotation;
627
    }
628
629 2
    private function convertCacheElementToCacheAnnotation(
630
        SimpleXMLElement $cacheElement
631
    ) : Annotation\Cache {
632 2
        $cacheAnnotation = new Annotation\Cache();
633
634 2
        if (isset($cacheElement['region'])) {
635
            $cacheAnnotation->region = (string) $cacheElement['region'];
636
        }
637
638 2
        if (isset($cacheElement['usage'])) {
639 2
            $cacheAnnotation->usage = strtoupper((string) $cacheElement['usage']);
640
        }
641
642 2
        return $cacheAnnotation;
643
    }
644
645 30
    private function convertFieldElementToColumnAnnotation(
646
        SimpleXMLElement $fieldElement
647
    ) : Annotation\Column {
648 30
        $columnAnnotation = new Annotation\Column();
649
650 30
        $columnAnnotation->type = isset($fieldElement['type']) ? (string) $fieldElement['type'] : 'string';
651
652 30
        if (isset($fieldElement['column'])) {
653 20
            $columnAnnotation->name = (string) $fieldElement['column'];
654
        }
655
656 30
        if (isset($fieldElement['length'])) {
657 6
            $columnAnnotation->length = (int) $fieldElement['length'];
658
        }
659
660 30
        if (isset($fieldElement['precision'])) {
661 1
            $columnAnnotation->precision = (int) $fieldElement['precision'];
662
        }
663
664 30
        if (isset($fieldElement['scale'])) {
665 1
            $columnAnnotation->scale = (int) $fieldElement['scale'];
666
        }
667
668 30
        if (isset($fieldElement['unique'])) {
669 7
            $columnAnnotation->unique = $this->evaluateBoolean($fieldElement['unique']);
670
        }
671
672 30
        if (isset($fieldElement['nullable'])) {
673 7
            $columnAnnotation->nullable = $this->evaluateBoolean($fieldElement['nullable']);
674
        }
675
676 30
        if (isset($fieldElement['column-definition'])) {
677 4
            $columnAnnotation->columnDefinition = (string) $fieldElement['column-definition'];
678
        }
679
680 30
        if (isset($fieldElement->options)) {
681 3
            $columnAnnotation->options = $this->parseOptions($fieldElement->options->children());
682
        }
683
684 30
        return $columnAnnotation;
685
    }
686
687 7
    private function convertOneToOneElementToOneToOneAnnotation(
688
        SimpleXMLElement $oneToOneElement
689
    ) : Annotation\OneToOne {
690 7
        $oneToOneAnnotation = new Annotation\OneToOne();
691
692 7
        $oneToOneAnnotation->targetEntity = (string) $oneToOneElement['target-entity'];
693
694 7
        if (isset($oneToOneElement['mapped-by'])) {
695 3
            $oneToOneAnnotation->mappedBy = (string) $oneToOneElement['mapped-by'];
696
        }
697
698 7
        if (isset($oneToOneElement['inversed-by'])) {
699 4
            $oneToOneAnnotation->inversedBy = (string) $oneToOneElement['inversed-by'];
700
        }
701
702 7
        if (isset($oneToOneElement['orphan-removal'])) {
703
            $oneToOneAnnotation->orphanRemoval = $this->evaluateBoolean($oneToOneElement['orphan-removal']);
704
        }
705
706 7
        if (isset($oneToOneElement['fetch'])) {
707 3
            $oneToOneAnnotation->fetch = (string) $oneToOneElement['fetch'];
708
        }
709
710 7
        if (isset($oneToOneElement->cascade)) {
711 7
            $oneToOneAnnotation->cascade = $this->getCascadeMappings($oneToOneElement->cascade);
712
        }
713
714 7
        return $oneToOneAnnotation;
715
    }
716
717 8
    private function convertManyToOneElementToManyToOneAnnotation(
718
        SimpleXMLElement $manyToOneElement
719
    ) : Annotation\ManyToOne {
720 8
        $manyToOneAnnotation = new Annotation\ManyToOne();
721
722 8
        $manyToOneAnnotation->targetEntity = (string) $manyToOneElement['target-entity'];
723
724 8
        if (isset($manyToOneElement['inversed-by'])) {
725 2
            $manyToOneAnnotation->inversedBy = (string) $manyToOneElement['inversed-by'];
726
        }
727
728 8
        if (isset($manyToOneElement['fetch'])) {
729
            $manyToOneAnnotation->fetch = (string) $manyToOneElement['fetch'];
730
        }
731
732 8
        if (isset($manyToOneElement->cascade)) {
733 4
            $manyToOneAnnotation->cascade = $this->getCascadeMappings($manyToOneElement->cascade);
734
        }
735
736 8
        return $manyToOneAnnotation;
737
    }
738
739 9
    private function convertOneToManyElementToOneToManyAnnotation(
740
        SimpleXMLElement $oneToManyElement
741
    ) : Annotation\OneToMany {
742 9
        $oneToManyAnnotation = new Annotation\OneToMany();
743
744 9
        $oneToManyAnnotation->targetEntity = (string) $oneToManyElement['target-entity'];
745
746 9
        if (isset($oneToManyElement['mapped-by'])) {
747 9
            $oneToManyAnnotation->mappedBy = (string) $oneToManyElement['mapped-by'];
748
        }
749
750 9
        if (isset($oneToManyElement['fetch'])) {
751
            $oneToManyAnnotation->fetch = (string) $oneToManyElement['fetch'];
752
        }
753
754 9
        if (isset($oneToManyElement->cascade)) {
755 6
            $oneToManyAnnotation->cascade = $this->getCascadeMappings($oneToManyElement->cascade);
756
        }
757
758 9
        if (isset($oneToManyElement['orphan-removal'])) {
759 3
            $oneToManyAnnotation->orphanRemoval = $this->evaluateBoolean($oneToManyElement['orphan-removal']);
760
        }
761
762 9
        if (isset($oneToManyElement['index-by'])) {
763 3
            $oneToManyAnnotation->indexBy = (string) $oneToManyElement['index-by'];
764 6
        } elseif (isset($oneToManyElement->{'index-by'})) {
765
            throw new InvalidArgumentException('<index-by /> is not a valid tag');
766
        }
767
768 9
        return $oneToManyAnnotation;
769
    }
770
771 14
    private function convertManyToManyElementToManyToManyAnnotation(
772
        SimpleXMLElement $manyToManyElement
773
    ) : Annotation\ManyToMany {
774 14
        $manyToManyAnnotation = new Annotation\ManyToMany();
775
776 14
        $manyToManyAnnotation->targetEntity = (string) $manyToManyElement['target-entity'];
777
778 14
        if (isset($manyToManyElement['mapped-by'])) {
779 5
            $manyToManyAnnotation->mappedBy = (string) $manyToManyElement['mapped-by'];
780
        }
781
782 14
        if (isset($manyToManyElement['inversed-by'])) {
783 6
            $manyToManyAnnotation->inversedBy = (string) $manyToManyElement['inversed-by'];
784
        }
785
786 14
        if (isset($manyToManyElement['fetch'])) {
787 4
            $manyToManyAnnotation->fetch = (string) $manyToManyElement['fetch'];
788
        }
789
790 14
        if (isset($manyToManyElement->cascade)) {
791 8
            $manyToManyAnnotation->cascade = $this->getCascadeMappings($manyToManyElement->cascade);
792
        }
793
794 14
        if (isset($manyToManyElement['orphan-removal'])) {
795
            $manyToManyAnnotation->orphanRemoval = $this->evaluateBoolean($manyToManyElement['orphan-removal']);
796
        }
797
798 14
        if (isset($manyToManyElement['index-by'])) {
799
            $manyToManyAnnotation->indexBy = (string) $manyToManyElement['index-by'];
800 14
        } elseif (isset($manyToManyElement->{'index-by'})) {
801
            throw new InvalidArgumentException('<index-by /> is not a valid tag');
802
        }
803
804 14
        return $manyToManyAnnotation;
805
    }
806
807
    /**
808
     * Constructs a JoinTable annotation based on the information
809
     * found in the given SimpleXMLElement.
810
     *
811
     * @param SimpleXMLElement $joinTableElement The XML element.
812
     */
813 8
    private function convertJoinTableElementToJoinTableAnnotation(
814
        SimpleXMLElement $joinTableElement
815
    ) : Annotation\JoinTable {
816 8
        $joinTableAnnotation = new Annotation\JoinTable();
817
818 8
        if (isset($joinTableElement['name'])) {
819 8
            $joinTableAnnotation->name = (string) $joinTableElement['name'];
820
        }
821
822 8
        if (isset($joinTableElement['schema'])) {
823
            $joinTableAnnotation->schema = (string) $joinTableElement['schema'];
824
        }
825
826 8
        if (isset($joinTableElement->{'join-columns'})) {
827 8
            $joinColumns = [];
828
829 8
            foreach ($joinTableElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
830 8
                $joinColumns[] = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
831
            }
832
833 8
            $joinTableAnnotation->joinColumns = $joinColumns;
834
        }
835
836 8
        if (isset($joinTableElement->{'inverse-join-columns'})) {
837 8
            $joinColumns = [];
838
839 8
            foreach ($joinTableElement->{'inverse-join-columns'}->{'join-column'} as $joinColumnElement) {
840 8
                $joinColumns[] = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
841
            }
842
843 8
            $joinTableAnnotation->inverseJoinColumns = $joinColumns;
844
        }
845
846 8
        return $joinTableAnnotation;
847
    }
848
849 1
    private function convertJoinColumnsElementToJoinColumnsAnnotation(
850
        SimpleXMLElement $joinColumnsElement
851
    ) : Annotation\JoinColumns {
852 1
        $joinColumnsAnnotation = new Annotation\JoinColumns();
853 1
        $joinColumns           = [];
854
855 1
        foreach ($joinColumnsElement->{'join-column'} as $joinColumnElement) {
856 1
            $joinColumns[] = $this->convertJoinColumnElementToJoinColumnAnnotation($joinColumnElement);
857
        }
858
859 1
        $joinColumnsAnnotation->value = $joinColumns;
860
861 1
        return $joinColumnsAnnotation;
862
    }
863
864
    /**
865
     * Constructs a JoinColumn annotation based on the information
866
     * found in the given SimpleXMLElement.
867
     *
868
     * @param SimpleXMLElement $joinColumnElement The XML element.
869
     */
870 13
    private function convertJoinColumnElementToJoinColumnAnnotation(
871
        SimpleXMLElement $joinColumnElement
872
    ) : Annotation\JoinColumn {
873 13
        $joinColumnAnnotation = new Annotation\JoinColumn();
874
875 13
        $joinColumnAnnotation->name                 = (string) $joinColumnElement['name'];
876 13
        $joinColumnAnnotation->referencedColumnName = (string) $joinColumnElement['referenced-column-name'];
877
878 13
        if (isset($joinColumnElement['column-definition'])) {
879 3
            $joinColumnAnnotation->columnDefinition = (string) $joinColumnElement['column-definition'];
880
        }
881
882 13
        if (isset($joinColumnElement['field-name'])) {
883
            $joinColumnAnnotation->fieldName = (string) $joinColumnElement['field-name'];
884
        }
885
886 13
        if (isset($joinColumnElement['nullable'])) {
887 4
            $joinColumnAnnotation->nullable = $this->evaluateBoolean($joinColumnElement['nullable']);
888
        }
889
890 13
        if (isset($joinColumnElement['unique'])) {
891 3
            $joinColumnAnnotation->unique = $this->evaluateBoolean($joinColumnElement['unique']);
892
        }
893
894 13
        if (isset($joinColumnElement['on-delete'])) {
895 3
            $joinColumnAnnotation->onDelete = strtoupper((string) $joinColumnElement['on-delete']);
896
        }
897
898 13
        return $joinColumnAnnotation;
899
    }
900
901 6
    private function convertOrderByElementToOrderByAnnotation(
902
        SimpleXMLElement $orderByElement
903
    ) : Annotation\OrderBy {
904 6
        $orderByAnnotation = new Annotation\OrderBy();
905 6
        $orderBy           = [];
906
907 6
        foreach ($orderByElement->{'order-by-field'} as $orderByField) {
908 6
            $orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
909 4
                ? (string) $orderByField['direction']
910 2
                : Criteria::ASC;
911
        }
912
913 6
        $orderByAnnotation->value = $orderBy;
914
915 6
        return $orderByAnnotation;
916
    }
917
918 27
    private function convertGeneratorElementToGeneratedValueAnnotation(
919
        SimpleXMLElement $generatorElement
920
    ) : Annotation\GeneratedValue {
921 27
        $generatedValueAnnotation = new Annotation\GeneratedValue();
922
923 27
        $generatedValueAnnotation->strategy = (string) ($generatorElement['strategy'] ?? 'AUTO');
924
925 27
        return $generatedValueAnnotation;
926
    }
927
928 3
    private function convertSequenceGeneratorElementToSequenceGeneratorAnnotation(
929
        SimpleXMLElement $sequenceGeneratorElement
930
    ) : Annotation\SequenceGenerator {
931 3
        $sequenceGeneratorAnnotation = new Annotation\SequenceGenerator();
932
933 3
        $sequenceGeneratorAnnotation->sequenceName   = (string) ($sequenceGeneratorElement['sequence-name'] ?? null);
934 3
        $sequenceGeneratorAnnotation->allocationSize = (int) ($sequenceGeneratorElement['allocation-size'] ?? 1);
935
936 3
        return $sequenceGeneratorAnnotation;
937
    }
938
939 2
    private function convertCustomIdGeneratorElementToCustomIdGeneratorAnnotation(
940
        SimpleXMLElement $customIdGeneratorElement
941
    ) : Annotation\CustomIdGenerator {
942 2
        $customIdGeneratorAnnotation = new Annotation\CustomIdGenerator();
943
944 2
        $customIdGeneratorAnnotation->class     = (string) $customIdGeneratorElement['class'];
945 2
        $customIdGeneratorAnnotation->arguments = [];
946
947 2
        return $customIdGeneratorAnnotation;
948
    }
949
950
    /**
951
     * Gathers a list of cascade options found in the given cascade element.
952
     *
953
     * @param SimpleXMLElement $cascadeElement The cascade element.
954
     *
955
     * @return string[] The list of cascade options.
956
     */
957 10
    private function getCascadeMappings(SimpleXMLElement $cascadeElement) : array
958
    {
959 10
        $cascades = [];
960
961
        /** @var SimpleXMLElement $action */
962 10
        foreach ($cascadeElement->children() as $action) {
963
            // According to the JPA specifications, XML uses "cascade-persist"
964
            // instead of "persist". Here, both variations are supported
965
            // because Annotation use "persist" and we want to make sure that
966
            // this driver doesn't need to know anything about the supported
967
            // cascading actions
968 10
            $cascades[] = str_replace('cascade-', '', $action->getName());
969
        }
970
971 10
        return $cascades;
972
    }
973
974
    /**
975
     * @param mixed $element
976
     *
977
     * @return bool
978
     */
979 9
    private function evaluateBoolean($element)
980
    {
981 9
        $flag = (string) $element;
982
983 9
        return $flag === 'true' || $flag === '1';
984
    }
985
986
    /**
987
     * Parses (nested) option elements.
988
     *
989
     * @param SimpleXMLElement $options The XML element.
990
     *
991
     * @return mixed[] The options array.
992
     */
993 4
    private function parseOptions(SimpleXMLElement $options) : array
994
    {
995 4
        $array = [];
996
997
        /** @var SimpleXMLElement $option */
998 4
        foreach ($options as $option) {
999 4
            if ($option->count()) {
1000 3
                $value = $this->parseOptions($option->children());
1001
            } else {
1002 4
                $value = (string) $option;
1003
            }
1004
1005 4
            $attributes = $option->attributes();
1006
1007 4
            if (isset($attributes->name)) {
1008 4
                $nameAttribute = (string) $attributes->name;
1009
1010 4
                $array[$nameAttribute] = in_array($nameAttribute, ['unsigned', 'fixed'], true)
1011 3
                    ? $this->evaluateBoolean($value)
1012 4
                    : $value;
1013
            } else {
1014
                $array[] = $value;
1015
            }
1016
        }
1017
1018 4
        return $array;
1019
    }
1020
}
1021