Failed Conditions
Push — master ( ddb3cd...4476ec )
by Marco
11:47
created

AnnotationDriver   F

Complexity

Total Complexity 178

Size/Duplication

Total Lines 1424
Duplicated Lines 0 %

Test Coverage

Coverage 91.22%

Importance

Changes 0
Metric Value
dl 0
loc 1424
ccs 530
cts 581
cp 0.9122
rs 0.6314
c 0
b 0
f 0
wmc 178

42 Methods

Rating   Name   Duplication   Size   Complexity  
A getCascade() 0 16 3
C getAllClassNames() 0 62 12
B convertTableAnnotationToTableMetadata() 0 32 6
B convertReflectionPropertyToManyToOneAssociationMetadata() 0 50 6
A attachAssociationPropertyCache() 0 16 2
A create() 0 7 2
B convertJoinTableAnnotationToJoinTableMetadata() 0 26 5
A addPaths() 0 3 1
A setFileExtension() 0 3 1
A isTransient() 0 10 3
A getExcludePaths() 0 3 1
A getPaths() 0 3 1
A convertClassAnnotationsToEmbeddableClassMetadata() 0 9 1
B convertSqlResultSetMapping() 0 33 4
B convertReflectionPropertyToOneToManyAssociationMetadata() 0 39 5
A getReader() 0 3 1
A convertClassAnnotationsToMappedSuperClassMetadata() 0 21 2
B convertJoinColumnAnnotationToJoinColumnMetadata() 0 30 6
B convertClassAnnotationsToEntityClassMetadata() 0 50 6
C convertPropertyAnnotationsToProperty() 0 46 7
B attachEntityListeners() 0 24 6
C convertReflectionPropertyToFieldMetadata() 0 62 8
A convertCacheAnnotationToCacheMetadata() 0 12 3
B convertColumnAnnotationToFieldMetadata() 0 34 6
A __construct() 0 5 2
A getFileExtension() 0 3 1
C convertReflectionPropertyToManyToManyAssociationMetadata() 0 51 7
B attachTable() 0 36 6
C attachPropertyOverrides() 0 67 11
A attachLifecycleCallbacks() 0 11 4
A getPropertyAnnotations() 0 13 3
A getClassAnnotations() 0 13 3
A addExcludePaths() 0 3 1
B attachDiscriminatorColumn() 0 40 6
A getMethodAnnotations() 0 13 3
B attachNamedNativeQueries() 0 29 5
B loadMetadataForClass() 0 53 6
B convertReflectionPropertyToOneToOneAssociationMetadata() 0 55 7
B attachNamedQueries() 0 19 5
A getFetchMode() 0 9 2
B getMethodCallbacks() 0 24 4
B convertClassAnnotationsToClassMetadata() 0 33 4

How to fix   Complexity   

Complex Class

Complex classes like AnnotationDriver 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 AnnotationDriver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\Reader;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Annotation;
11
use Doctrine\ORM\Events;
12
use Doctrine\ORM\Mapping;
13
14
/**
15
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
16
 *
17
 */
18
class AnnotationDriver implements MappingDriver
19
{
20
    /**
21
     * @var int[]
22
     */
23
    protected $entityAnnotationClasses = [
24
        Annotation\Entity::class           => 1,
25
        Annotation\MappedSuperclass::class => 2,
26
    ];
27
28
    /**
29
     * The AnnotationReader.
30
     *
31
     * @var AnnotationReader
32
     */
33
    protected $reader;
34
35
    /**
36
     * The paths where to look for mapping files.
37
     *
38
     * @var string[]
39
     */
40
    protected $paths = [];
41
42
    /**
43
     * The paths excluded from path where to look for mapping files.
44
     *
45
     * @var string[]
46
     */
47
    protected $excludePaths = [];
48
49
    /**
50
     * The file extension of mapping documents.
51
     *
52
     * @var string
53
     */
54
    protected $fileExtension = '.php';
55
56
    /**
57
     * Cache for AnnotationDriver#getAllClassNames().
58
     *
59
     * @var string[]|null
60
     */
61
    protected $classNames;
62
63
    /**
64
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
65
     * docblock annotations.
66
     *
67
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
68
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
69
     */
70 2095
    public function __construct(Reader $reader, $paths = null)
71
    {
72 2095
        $this->reader = $reader;
73 2095
        if ($paths) {
74 2015
            $this->addPaths((array) $paths);
75
        }
76 2095
    }
77
78
    /**
79
     * Appends lookup paths to metadata driver.
80
     *
81
     * @param string[] $paths
82
     */
83 2019
    public function addPaths(array $paths)
84
    {
85 2019
        $this->paths = array_unique(array_merge($this->paths, $paths));
86 2019
    }
87
88
    /**
89
     * Retrieves the defined metadata lookup paths.
90
     *
91
     * @return string[]
92
     */
93
    public function getPaths()
94
    {
95
        return $this->paths;
96
    }
97
98
    /**
99
     * Append exclude lookup paths to metadata driver.
100
     *
101
     * @param string[] $paths
102
     */
103
    public function addExcludePaths(array $paths)
104
    {
105
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
106
    }
107
108
    /**
109
     * Retrieve the defined metadata lookup exclude paths.
110
     *
111
     * @return string[]
112
     */
113
    public function getExcludePaths()
114
    {
115
        return $this->excludePaths;
116
    }
117
118
    /**
119
     * Retrieve the current annotation reader
120
     *
121
     * @return AnnotationReader
122
     */
123 1
    public function getReader()
124
    {
125 1
        return $this->reader;
126
    }
127
128
    /**
129
     * Gets the file extension used to look for mapping files under.
130
     *
131
     * @return string
132
     */
133
    public function getFileExtension()
134
    {
135
        return $this->fileExtension;
136
    }
137
138
    /**
139
     * Sets the file extension used to look for mapping files under.
140
     *
141
     * @param string $fileExtension The file extension to set.
142
     *
143
     */
144
    public function setFileExtension($fileExtension)
145
    {
146
        $this->fileExtension = $fileExtension;
147
    }
148
149
    /**
150
     * Returns whether the class with the specified name is transient. Only non-transient
151
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
152
     *
153
     * A class is non-transient if it is annotated with an annotation
154
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
155
     *
156
     * @param string $className
157
     *
158
     * @return bool
159
     */
160 187
    public function isTransient($className)
161
    {
162 187
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
163
164 187
        foreach ($classAnnotations as $annot) {
165 182
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
166 182
                return false;
167
            }
168
        }
169 12
        return true;
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175 60
    public function getAllClassNames()
176
    {
177 60
        if ($this->classNames !== null) {
178 45
            return $this->classNames;
179
        }
180
181 60
        if (! $this->paths) {
182
            throw Mapping\MappingException::pathRequired();
183
        }
184
185 60
        $classes       = [];
186 60
        $includedFiles = [];
187
188 60
        foreach ($this->paths as $path) {
189 60
            if (! is_dir($path)) {
190
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
191
            }
192
193 60
            $iterator = new \RegexIterator(
194 60
                new \RecursiveIteratorIterator(
195 60
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
196 60
                    \RecursiveIteratorIterator::LEAVES_ONLY
197
                ),
198 60
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
199 60
                \RecursiveRegexIterator::GET_MATCH
200
            );
201
202 60
            foreach ($iterator as $file) {
203 60
                $sourceFile = $file[0];
204
205 60
                if (! preg_match('(^phar:)i', $sourceFile)) {
206 60
                    $sourceFile = realpath($sourceFile);
207
                }
208
209 60
                foreach ($this->excludePaths as $excludePath) {
210
                    $exclude = str_replace('\\', '/', realpath($excludePath));
211
                    $current = str_replace('\\', '/', $sourceFile);
212
213
                    if (strpos($current, $exclude) !== false) {
214
                        continue 2;
215
                    }
216
                }
217
218 60
                require_once $sourceFile;
219
220 60
                $includedFiles[] = $sourceFile;
221
            }
222
        }
223
224 60
        $declared = get_declared_classes();
225
226 60
        foreach ($declared as $className) {
227 60
            $rc         = new \ReflectionClass($className);
228 60
            $sourceFile = $rc->getFileName();
229 60
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
230 60
                $classes[] = $className;
231
            }
232
        }
233
234 60
        $this->classNames = $classes;
235
236 60
        return $classes;
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242 353
    public function loadMetadataForClass(
243
        string $className,
244
        Mapping\ClassMetadata $metadata,
245
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
246
    ) : Mapping\ClassMetadata {
247 353
        $reflectionClass = $metadata->getReflectionClass();
248
249 353
        if (! $reflectionClass) {
250
            // this happens when running annotation driver in combination with
251
            // static reflection services. This is not the nicest fix
252
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
253
        }
254
255 353
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
256 353
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
257 353
            $classAnnotations,
258 353
            $reflectionClass,
259 353
            $metadata,
260 353
            $metadataBuildingContext
261
        );
262
263
        // Evaluate @Cache annotation
264 353
        if (isset($classAnnotations[Annotation\Cache::class])) {
265 16
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
266 16
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
267
268 16
            $classMetadata->setCache($cache);
269
        }
270
271
        // Evaluate annotations on properties/fields
272
        /* @var $reflProperty \ReflectionProperty */
273 353
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
274 353
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
275 73
                continue;
276
            }
277
278 353
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
279 352
            $property            = $this->convertPropertyAnnotationsToProperty(
280 352
                $propertyAnnotations,
281 352
                $reflectionProperty,
282 352
                $classMetadata
283
            );
284
285 352
            if (! $property) {
286 4
                continue;
287
            }
288
289 352
            $metadata->addProperty($property);
290
        }
291
292 352
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
293
294 352
        return $classMetadata;
295
    }
296
297
    /**
298
     * @param Annotation\Annotation[] $classAnnotations
299
     *
300
     * @throws Mapping\MappingException
301
     */
302 353
    private function convertClassAnnotationsToClassMetadata(
303
        array $classAnnotations,
304
        \ReflectionClass $reflectionClass,
305
        Mapping\ClassMetadata $metadata,
306
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
307
    ) : Mapping\ClassMetadata {
308
        switch (true) {
309 353
            case isset($classAnnotations[Annotation\Entity::class]):
310 353
                return $this->convertClassAnnotationsToEntityClassMetadata(
311 353
                    $classAnnotations,
312 353
                    $reflectionClass,
313 353
                    $metadata,
314 353
                    $metadataBuildingContext
315
                );
316
317
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
318
319 21
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
320 21
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
321 21
                    $classAnnotations,
322 21
                    $reflectionClass,
323 21
                    $metadata
324
                );
325
326
            case isset($classAnnotations[Annotation\Embeddable::class]):
327
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
328
                    $classAnnotations,
329
                    $reflectionClass,
330
                    $metadata
331
                );
332
333
            default:
334
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
335
        }
336
    }
337
338
    /**
339
     * @param Annotation\Annotation[] $classAnnotations
340
     *
341
     * @return Mapping\ClassMetadata
342
     *
343
     * @throws Mapping\MappingException
344
     * @throws \UnexpectedValueException
345
     */
346 353
    private function convertClassAnnotationsToEntityClassMetadata(
347
        array $classAnnotations,
348
        \ReflectionClass $reflectionClass,
349
        Mapping\ClassMetadata $metadata,
350
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
351
    ) {
352
        /** @var Annotation\Entity $entityAnnot */
353 353
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
354
355 353
        if ($entityAnnot->repositoryClass !== null) {
356 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
357
        }
358
359 353
        if ($entityAnnot->readOnly) {
360 1
            $metadata->asReadOnly();
361
        }
362
363 353
        $metadata->isMappedSuperclass = false;
364 353
        $metadata->isEmbeddedClass    = false;
365
366 353
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
367
368
        // Evaluate @ChangeTrackingPolicy annotation
369 353
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
370 5
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
371
372 5
            $metadata->setChangeTrackingPolicy(
373 5
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
374
            );
375
        }
376
377
        // Evaluate @InheritanceType annotation
378 353
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
379 76
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
380
381 76
            $metadata->setInheritanceType(
382 76
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
383
            );
384
385 76
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
386 76
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
387
            }
388
        }
389
390 353
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
391 353
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
392 353
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
393 353
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
394
395 353
        return $metadata;
396
    }
397
398
    /**
399
     * @param Annotation\Annotation[] $classAnnotations
400
     */
401 21
    private function convertClassAnnotationsToMappedSuperClassMetadata(
402
        array $classAnnotations,
403
        \ReflectionClass $reflectionClass,
404
        Mapping\ClassMetadata $metadata
405
    ) : Mapping\ClassMetadata {
406
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
407 21
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
408
409 21
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
410 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
411
        }
412
413 21
        $metadata->isMappedSuperclass = true;
414 21
        $metadata->isEmbeddedClass    = false;
415
416 21
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
417 21
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
418 21
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
419 21
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
420
421 21
        return $metadata;
422
    }
423
424
    /**
425
     * @param Annotation\Annotation[] $classAnnotations
426
     */
427
    private function convertClassAnnotationsToEmbeddableClassMetadata(
428
        array $classAnnotations,
429
        \ReflectionClass $reflectionClass,
430
        Mapping\ClassMetadata $metadata
431
    ) : Mapping\ClassMetadata {
432
        $metadata->isMappedSuperclass = false;
433
        $metadata->isEmbeddedClass    = true;
434
435
        return $metadata;
436
    }
437
438
    /**
439
     * @param Annotation\Annotation[] $propertyAnnotations
440
     *
441
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
442
     */
443 352
    private function convertPropertyAnnotationsToProperty(
444
        array $propertyAnnotations,
445
        \ReflectionProperty $reflectionProperty,
446
        Mapping\ClassMetadata $metadata
447
    ) : ?Mapping\Property {
448
        switch (true) {
449 352
            case isset($propertyAnnotations[Annotation\Column::class]):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
450 350
                return $this->convertReflectionPropertyToFieldMetadata(
451 350
                    $reflectionProperty,
452 350
                    $propertyAnnotations,
453 350
                    $metadata
454
                );
455
456 246
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
457 107
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
458 107
                    $reflectionProperty,
459 107
                    $propertyAnnotations,
460 107
                    $metadata
461
                );
462
463 196
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
464 131
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
465 131
                    $reflectionProperty,
466 131
                    $propertyAnnotations,
467 131
                    $metadata
468
                );
469
470 161
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
471 105
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
472 105
                    $reflectionProperty,
473 105
                    $propertyAnnotations,
474 105
                    $metadata
475
                );
476
477 105
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
478 84
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
479 84
                    $reflectionProperty,
480 84
                    $propertyAnnotations,
481 84
                    $metadata
482
                );
483
484 36
            case isset($propertyAnnotations[Annotation\Embedded::class]):
485 4
                return null;
486
487
            default:
488 32
                return new Mapping\TransientMetadata($reflectionProperty->getName());
489
        }
490
    }
491
492
    /**
493
     * @param Annotation\Annotation[] $propertyAnnotations
494
     *
495
     * @throws Mapping\MappingException
496
     */
497 350
    private function convertReflectionPropertyToFieldMetadata(
498
        \ReflectionProperty $reflProperty,
499
        array $propertyAnnotations,
500
        Mapping\ClassMetadata $metadata
501
    ) : Mapping\FieldMetadata {
502 350
        $className   = $metadata->getClassName();
503 350
        $fieldName   = $reflProperty->getName();
504 350
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
505 350
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
506
507 350
        if ($columnAnnot->type === null) {
508
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
509
        }
510
511 350
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
512
513
        // Check for Id
514 350
        if (isset($propertyAnnotations[Annotation\Id::class])) {
515 347
            $fieldMetadata->setPrimaryKey(true);
516
        }
517
518
        // Check for GeneratedValue strategy
519 350
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
520 297
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
521 297
            $strategy            = strtoupper($generatedValueAnnot->strategy);
522 297
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
523
524 297
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
525 275
                $idGeneratorDefinition = [];
526
527
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
528
                switch (true) {
529 275
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
530 9
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
531
532
                        $idGeneratorDefinition = [
533 9
                            'sequenceName' => $seqGeneratorAnnot->sequenceName,
534 9
                            'allocationSize' => $seqGeneratorAnnot->allocationSize,
535
                        ];
536
537 9
                        break;
538
539 266
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
540 3
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
541
542
                        $idGeneratorDefinition = [
543 3
                            'class' => $customGeneratorAnnot->class,
544 3
                            'arguments' => $customGeneratorAnnot->arguments,
545
                        ];
546
547 3
                        break;
548
549
                    /* @todo If it is not supported, why does this exist? */
550 263
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
551
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
552
                }
553
554 275
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
555
            }
556
        }
557
558 350
        return $fieldMetadata;
559
    }
560
561
    /**
562
     * @param Annotation\Annotation[] $propertyAnnotations
563
     *
564
     * @return Mapping\OneToOneAssociationMetadata
565
     */
566 107
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
567
        \ReflectionProperty $reflectionProperty,
568
        array $propertyAnnotations,
569
        Mapping\ClassMetadata $metadata
570
    ) {
571 107
        $className     = $metadata->getClassName();
572 107
        $fieldName     = $reflectionProperty->getName();
573 107
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
574 107
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
575 107
        $targetEntity  = $oneToOneAnnot->targetEntity;
576
577 107
        $assocMetadata->setTargetEntity($targetEntity);
578 107
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade));
579 107
        $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval);
580 107
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch));
581
582 107
        if (! empty($oneToOneAnnot->mappedBy)) {
583 37
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
584
        }
585
586 107
        if (! empty($oneToOneAnnot->inversedBy)) {
587 53
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
588
        }
589
590
        // Check for Id
591 107
        if (isset($propertyAnnotations[Annotation\Id::class])) {
592 9
            $assocMetadata->setPrimaryKey(true);
593
        }
594
595 107
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
596
597
        // Check for JoinColumn/JoinColumns annotations
598
        switch (true) {
599 107
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
600 79
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
601
602 79
                $assocMetadata->addJoinColumn(
603 79
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
604
                );
605
606 79
                break;
607
608 46
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
609 3
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
610
611 3
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
612 3
                    $assocMetadata->addJoinColumn(
613 3
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
614
                    );
615
                }
616
617 3
                break;
618
        }
619
620 107
        return $assocMetadata;
621
    }
622
623
    /**
624
     * @param Annotation\Annotation[] $propertyAnnotations
625
     *
626
     * @return Mapping\ManyToOneAssociationMetadata
627
     */
628 131
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
629
        \ReflectionProperty $reflectionProperty,
630
        array $propertyAnnotations,
631
        Mapping\ClassMetadata $metadata
632
    ) {
633 131
        $className      = $metadata->getClassName();
634 131
        $fieldName      = $reflectionProperty->getName();
635 131
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
636 131
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
637 131
        $targetEntity   = $manyToOneAnnot->targetEntity;
638
639 131
        $assocMetadata->setTargetEntity($targetEntity);
640 131
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade));
641 131
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch));
642
643 131
        if (! empty($manyToOneAnnot->inversedBy)) {
644 87
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
645
        }
646
647
        // Check for Id
648 131
        if (isset($propertyAnnotations[Annotation\Id::class])) {
649 31
            $assocMetadata->setPrimaryKey(true);
650
        }
651
652 131
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
653
654
        // Check for JoinColumn/JoinColumns annotations
655
        switch (true) {
656 131
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
657 73
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
658
659 73
                $assocMetadata->addJoinColumn(
660 73
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
661
                );
662
663 73
                break;
664
665 67
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
666 16
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
667
668 16
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
669 16
                    $assocMetadata->addJoinColumn(
670 16
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
671
                    );
672
                }
673
674 16
                break;
675
        }
676
677 131
        return $assocMetadata;
678
    }
679
680
    /**
681
     * @param Annotation\Annotation[] $propertyAnnotations
682
     *
683
     * @throws Mapping\MappingException
684
     */
685 105
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
686
        \ReflectionProperty $reflectionProperty,
687
        array $propertyAnnotations,
688
        Mapping\ClassMetadata $metadata
689
    ) : Mapping\OneToManyAssociationMetadata {
690 105
        $className      = $metadata->getClassName();
691 105
        $fieldName      = $reflectionProperty->getName();
692 105
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
693 105
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
694 105
        $targetEntity   = $oneToManyAnnot->targetEntity;
695
696 105
        $assocMetadata->setTargetEntity($targetEntity);
697 105
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
698 105
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
699 105
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));
700
701 105
        if (! empty($oneToManyAnnot->mappedBy)) {
702 105
            $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
703
        }
704
705 105
        if (! empty($oneToManyAnnot->indexBy)) {
706 7
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
707
        }
708
709
        // Check for OrderBy
710 105
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
711 15
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
712
713 15
            $assocMetadata->setOrderBy($orderByAnnot->value);
714
        }
715
716
        // Check for Id
717 105
        if (isset($propertyAnnotations[Annotation\Id::class])) {
718
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
719
        }
720
721 105
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
722
723 105
        return $assocMetadata;
724
    }
725
726
    /**
727
     * @param Annotation\Annotation[] $propertyAnnotations
728
     *
729
     * @throws Mapping\MappingException
730
     */
731 84
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
732
        \ReflectionProperty $reflectionProperty,
733
        array $propertyAnnotations,
734
        Mapping\ClassMetadata $metadata
735
    ) : Mapping\ManyToManyAssociationMetadata {
736 84
        $className       = $metadata->getClassName();
737 84
        $fieldName       = $reflectionProperty->getName();
738 84
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
739 84
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
740 84
        $targetEntity    = $manyToManyAnnot->targetEntity;
741
742 84
        $assocMetadata->setTargetEntity($targetEntity);
743 84
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade));
744 84
        $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval);
745 84
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch));
746
747 84
        if (! empty($manyToManyAnnot->mappedBy)) {
748 32
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
749
        }
750
751 84
        if (! empty($manyToManyAnnot->inversedBy)) {
752 42
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
753
        }
754
755 84
        if (! empty($manyToManyAnnot->indexBy)) {
756 3
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
757
        }
758
759
        // Check for JoinTable
760 84
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
761 67
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
762 67
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
763
764 67
            $assocMetadata->setJoinTable($joinTableMetadata);
765
        }
766
767
        // Check for OrderBy
768 84
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
769 3
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
770
771 3
            $assocMetadata->setOrderBy($orderByAnnot->value);
772
        }
773
774
        // Check for Id
775 84
        if (isset($propertyAnnotations[Annotation\Id::class])) {
776
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
777
        }
778
779 84
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
780
781 84
        return $assocMetadata;
782
    }
783
784
    /**
785
     * Parse the given Column as FieldMetadata
786
     */
787 350
    private function convertColumnAnnotationToFieldMetadata(
788
        Annotation\Column $columnAnnot,
789
        string $fieldName,
790
        bool $isVersioned
791
    ) : Mapping\FieldMetadata {
792 350
        $fieldMetadata = $isVersioned
793 16
            ? new Mapping\VersionFieldMetadata($fieldName)
794 350
            : new Mapping\FieldMetadata($fieldName)
795
        ;
796
797 350
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
798
799 350
        if (! empty($columnAnnot->name)) {
800 74
            $fieldMetadata->setColumnName($columnAnnot->name);
801
        }
802
803 350
        if (! empty($columnAnnot->columnDefinition)) {
804 5
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
805
        }
806
807 350
        if (! empty($columnAnnot->length)) {
808 350
            $fieldMetadata->setLength($columnAnnot->length);
809
        }
810
811 350
        if ($columnAnnot->options) {
812 7
            $fieldMetadata->setOptions($columnAnnot->options);
813
        }
814
815 350
        $fieldMetadata->setScale($columnAnnot->scale);
816 350
        $fieldMetadata->setPrecision($columnAnnot->precision);
817 350
        $fieldMetadata->setNullable($columnAnnot->nullable);
818 350
        $fieldMetadata->setUnique($columnAnnot->unique);
819
820 350
        return $fieldMetadata;
821
    }
822
823
    /**
824
     * Parse the given Table as TableMetadata
825
     */
826 185
    private function convertTableAnnotationToTableMetadata(
827
        Annotation\Table $tableAnnot,
828
        Mapping\TableMetadata $tableMetadata
829
    ) : void {
830 185
        if (! empty($tableAnnot->name)) {
831 180
            $tableMetadata->setName($tableAnnot->name);
832
        }
833
834 185
        if (! empty($tableAnnot->schema)) {
835 5
            $tableMetadata->setSchema($tableAnnot->schema);
836
        }
837
838 185
        foreach ($tableAnnot->options as $optionName => $optionValue) {
839 5
            $tableMetadata->addOption($optionName, $optionValue);
840
        }
841
842 185
        foreach ($tableAnnot->indexes as $indexAnnot) {
843 14
            $tableMetadata->addIndex([
844 14
                'name'    => $indexAnnot->name,
845 14
                'columns' => $indexAnnot->columns,
846 14
                'unique'  => $indexAnnot->unique,
847 14
                'options' => $indexAnnot->options,
848 14
                'flags'   => $indexAnnot->flags,
849
            ]);
850
        }
851
852 185
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
853 7
            $tableMetadata->addUniqueConstraint([
854 7
                'name'    => $uniqueConstraintAnnot->name,
855 7
                'columns' => $uniqueConstraintAnnot->columns,
856 7
                'options' => $uniqueConstraintAnnot->options,
857 7
                'flags'   => $uniqueConstraintAnnot->flags,
858
            ]);
859
        }
860 185
    }
861
862
    /**
863
     * Parse the given JoinTable as JoinTableMetadata
864
     */
865 67
    private function convertJoinTableAnnotationToJoinTableMetadata(
866
        Annotation\JoinTable $joinTableAnnot
867
    ) : Mapping\JoinTableMetadata {
868 67
        $joinTable = new Mapping\JoinTableMetadata();
869
870 67
        if (! empty($joinTableAnnot->name)) {
871 65
            $joinTable->setName($joinTableAnnot->name);
872
        }
873
874 67
        if (! empty($joinTableAnnot->schema)) {
875
            $joinTable->setSchema($joinTableAnnot->schema);
876
        }
877
878 67
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
879 66
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
880
881 66
            $joinTable->addJoinColumn($joinColumn);
882
        }
883
884 67
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
885 66
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
886
887 66
            $joinTable->addInverseJoinColumn($joinColumn);
888
        }
889
890 67
        return $joinTable;
891
    }
892
893
    /**
894
     * Parse the given JoinColumn as JoinColumnMetadata
895
     */
896 172
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
897
        Annotation\JoinColumn $joinColumnAnnot
898
    ) : Mapping\JoinColumnMetadata {
899 172
        $joinColumn = new Mapping\JoinColumnMetadata();
900
901
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
902 172
        if (! empty($joinColumnAnnot->name)) {
903 165
            $joinColumn->setColumnName($joinColumnAnnot->name);
904
        }
905
906 172
        if (! empty($joinColumnAnnot->referencedColumnName)) {
907 172
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
908
        }
909
910 172
        $joinColumn->setNullable($joinColumnAnnot->nullable);
911 172
        $joinColumn->setUnique($joinColumnAnnot->unique);
912
913 172
        if (! empty($joinColumnAnnot->fieldName)) {
914
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
915
        }
916
917 172
        if (! empty($joinColumnAnnot->columnDefinition)) {
918 4
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
919
        }
920
921 172
        if ($joinColumnAnnot->onDelete) {
922 15
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
923
        }
924
925 172
        return $joinColumn;
926
    }
927
928
    /**
929
     * Parse the given Cache as CacheMetadata
930
     *
931
     * @param string|null $fieldName
932
     */
933 16
    private function convertCacheAnnotationToCacheMetadata(
934
        Annotation\Cache $cacheAnnot,
935
        Mapping\ClassMetadata $metadata,
936
        $fieldName = null
937
    ) : Mapping\CacheMetadata {
938 16
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
939 16
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
940
941 16
        $usage  = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
942 16
        $region = $cacheAnnot->region ?: $defaultRegion;
943
944 16
        return new Mapping\CacheMetadata($usage, $region);
945
    }
946
947
    /**
948
     * @return mixed[]
949
     */
950 13
    private function convertSqlResultSetMapping(Annotation\SqlResultSetMapping $resultSetMapping)
951
    {
952 13
        $entities = [];
953
954 13
        foreach ($resultSetMapping->entities as $entityResultAnnot) {
955
            $entityResult = [
956 13
                'fields'                => [],
957 13
                'entityClass'           => $entityResultAnnot->entityClass,
958 13
                'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
959
            ];
960
961 13
            foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
962 13
                $entityResult['fields'][] = [
963 13
                    'name'      => $fieldResultAnnot->name,
964 13
                    'column'    => $fieldResultAnnot->column,
965
                ];
966
            }
967
968 13
            $entities[] = $entityResult;
969
        }
970
971 13
        $columns = [];
972
973 13
        foreach ($resultSetMapping->columns as $columnResultAnnot) {
974 8
            $columns[] = [
975 8
                'name' => $columnResultAnnot->name,
976
            ];
977
        }
978
979
        return [
980 13
            'name'     => $resultSetMapping->name,
981 13
            'entities' => $entities,
982 13
            'columns'  => $columns,
983
        ];
984
    }
985
986
    /**
987
     * @param Annotation\Annotation[] $classAnnotations
988
     */
989 353
    private function attachTable(
990
        array $classAnnotations,
991
        \ReflectionClass $reflectionClass,
992
        Mapping\ClassMetadata $metadata,
993
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
994
    ) : void {
995 353
        $parent = $metadata->getParent();
996
997 353
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
998
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
999
            do {
1000 27
                if (! $parent->isMappedSuperclass) {
1001 27
                    $metadata->setTable($parent->table);
1002
1003 27
                    break;
1004
                }
1005
1006 3
                $parent = $parent->getParent();
1007 3
            } while ($parent !== null);
1008
1009 27
            return;
1010
        }
1011
1012 353
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1013 353
        $tableMetadata  = new Mapping\TableMetadata();
1014
1015 353
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1016
1017
        // Evaluate @Table annotation
1018 353
        if (isset($classAnnotations[Annotation\Table::class])) {
1019 185
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1020
1021 185
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1022
        }
1023
1024 353
        $metadata->setTable($tableMetadata);
1025 353
    }
1026
1027
    /**
1028
     * @param Annotation\Annotation[] $classAnnotations
1029
     *
1030
     * @throws Mapping\MappingException
1031
     */
1032 76
    private function attachDiscriminatorColumn(
1033
        array $classAnnotations,
1034
        \ReflectionClass $reflectionClass,
1035
        Mapping\ClassMetadata $metadata
1036
    ) : void {
1037 76
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1038
1039 76
        $discriminatorColumn->setTableName($metadata->getTableName());
1040 76
        $discriminatorColumn->setColumnName('dtype');
1041 76
        $discriminatorColumn->setType(Type::getType('string'));
1042 76
        $discriminatorColumn->setLength(255);
1043
1044
        // Evaluate DiscriminatorColumn annotation
1045 76
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1046
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1047 59
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1048 59
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1049 55
                ? $discriminatorColumnAnnotation->type
1050 59
                : 'string';
1051
1052 59
            $discriminatorColumn->setType(Type::getType($typeName));
1053 59
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1054
1055 59
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1056 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1057
            }
1058
1059 59
            if (! empty($discriminatorColumnAnnotation->length)) {
1060 4
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1061
            }
1062
        }
1063
1064 76
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1065
1066
        // Evaluate DiscriminatorMap annotation
1067 76
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1068 75
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1069 75
            $discriminatorMap           = $discriminatorMapAnnotation->value;
1070
1071 75
            $metadata->setDiscriminatorMap($discriminatorMap);
1072
        }
1073 76
    }
1074
1075
    /**
1076
     * @param Annotation\Annotation[] $classAnnotations
1077
     *
1078
     * @throws \UnexpectedValueException
1079
     * @throws Mapping\MappingException
1080
     */
1081 353
    private function attachNamedQueries(
1082
        array $classAnnotations,
1083
        \ReflectionClass $reflectionClass,
1084
        Mapping\ClassMetadata $metadata
1085
    ) : void {
1086
        // Evaluate @NamedQueries annotation
1087 353
        if (isset($classAnnotations[Annotation\NamedQueries::class])) {
1088 10
            $namedQueriesAnnot = $classAnnotations[Annotation\NamedQueries::class];
1089
1090 10
            if (! is_array($namedQueriesAnnot->value)) {
1091
                throw new \UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.');
1092
            }
1093
1094 10
            foreach ($namedQueriesAnnot->value as $namedQuery) {
1095 10
                if (! ($namedQuery instanceof Annotation\NamedQuery)) {
1096
                    throw new \UnexpectedValueException('@NamedQueries should contain an array of @NamedQuery annotations.');
1097
                }
1098
1099 10
                $metadata->addNamedQuery($namedQuery->name, $namedQuery->query);
1100
            }
1101
        }
1102 353
    }
1103
1104
    /**
1105
     * @param Annotation\Annotation[] $classAnnotations
1106
     */
1107 353
    private function attachNamedNativeQueries(
1108
        array $classAnnotations,
1109
        \ReflectionClass $reflectionClass,
1110
        Mapping\ClassMetadata $metadata
1111
    ) : void {
1112
        // Evaluate @NamedNativeQueries annotation
1113 353
        if (isset($classAnnotations[Annotation\NamedNativeQueries::class])) {
1114 13
            $namedNativeQueriesAnnot = $classAnnotations[Annotation\NamedNativeQueries::class];
1115
1116 13
            foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
1117 13
                $metadata->addNamedNativeQuery(
1118 13
                    $namedNativeQuery->name,
1119 13
                    $namedNativeQuery->query,
1120
                    [
1121 13
                        'resultClass'      => $namedNativeQuery->resultClass,
1122 13
                        'resultSetMapping' => $namedNativeQuery->resultSetMapping,
1123
                    ]
1124
                );
1125
            }
1126
        }
1127
1128
        // Evaluate @SqlResultSetMappings annotation
1129 353
        if (isset($classAnnotations[Annotation\SqlResultSetMappings::class])) {
1130 13
            $sqlResultSetMappingsAnnot = $classAnnotations[Annotation\SqlResultSetMappings::class];
1131
1132 13
            foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
1133 13
                $sqlResultSetMapping = $this->convertSqlResultSetMapping($resultSetMapping);
1134
1135 13
                $metadata->addSqlResultSetMapping($sqlResultSetMapping);
1136
            }
1137
        }
1138 353
    }
1139
1140
    /**
1141
     * @param Annotation\Annotation[] $classAnnotations
1142
     */
1143 353
    private function attachLifecycleCallbacks(
1144
        array $classAnnotations,
1145
        \ReflectionClass $reflectionClass,
1146
        Mapping\ClassMetadata $metadata
1147
    ) : void {
1148
        // Evaluate @HasLifecycleCallbacks annotation
1149 353
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1150
            /* @var $method \ReflectionMethod */
1151 15
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1152 14
                foreach ($this->getMethodCallbacks($method) as $callback) {
1153 14
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1154
                }
1155
            }
1156
        }
1157 353
    }
1158
1159
    /**
1160
     * @param Annotation\Annotation[] $classAnnotations
1161
     *
1162
     * @throws \ReflectionException
1163
     * @throws Mapping\MappingException
1164
     */
1165 353
    private function attachEntityListeners(
1166
        array $classAnnotations,
1167
        \ReflectionClass $reflectionClass,
1168
        Mapping\ClassMetadata $metadata
1169
    ) : void {
1170
        // Evaluate @EntityListeners annotation
1171 353
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1172
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1173 9
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1174
1175 9
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1176 9
                if (! class_exists($listenerClassName)) {
1177
                    throw Mapping\MappingException::entityListenerClassNotFound(
1178
                        $listenerClassName,
1179
                        $metadata->getClassName()
1180
                    );
1181
                }
1182
1183 9
                $listenerClass = new \ReflectionClass($listenerClassName);
1184
1185
                /* @var $method \ReflectionMethod */
1186 9
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1187 9
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1188 9
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1189
                    }
1190
                }
1191
            }
1192
        }
1193 353
    }
1194
1195
    /**
1196
     * @param Annotation\Annotation[] $classAnnotations
1197
     *
1198
     * @throws Mapping\MappingException
1199
     */
1200 352
    private function attachPropertyOverrides(
1201
        array $classAnnotations,
1202
        \ReflectionClass $reflectionClass,
1203
        Mapping\ClassMetadata $metadata
1204
    ) : void {
1205
        // Evaluate AssociationOverrides annotation
1206 352
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1207 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1208
1209 5
            foreach ($associationOverridesAnnot->value as $associationOverride) {
1210 5
                $fieldName = $associationOverride->name;
1211 5
                $property  = $metadata->getProperty($fieldName);
1212
1213 5
                if (! $property) {
1214
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1215
                }
1216
1217 5
                $existingClass = get_class($property);
1218 5
                $override      = new $existingClass($fieldName);
1219
1220
                // Check for JoinColumn/JoinColumns annotations
1221 5
                if ($associationOverride->joinColumns) {
1222 3
                    $joinColumns = [];
1223
1224 3
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1225 3
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1226
                    }
1227
1228 3
                    $override->setJoinColumns($joinColumns);
1229
                }
1230
1231
                // Check for JoinTable annotations
1232 5
                if ($associationOverride->joinTable) {
1233 2
                    $joinTableAnnot    = $associationOverride->joinTable;
1234 2
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1235
1236 2
                    $override->setJoinTable($joinTableMetadata);
1237
                }
1238
1239
                // Check for inversedBy
1240 5
                if ($associationOverride->inversedBy) {
1241 1
                    $override->setInversedBy($associationOverride->inversedBy);
1242
                }
1243
1244
                // Check for fetch
1245 5
                if ($associationOverride->fetch) {
1246 1
                    $override->setFetchMode(
1247 1
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1248
                    );
1249
                }
1250
1251 5
                $metadata->setPropertyOverride($override);
1252
            }
1253
        }
1254
1255
        // Evaluate AttributeOverrides annotation
1256 352
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1257 3
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1258
1259 3
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1260 3
                $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1261 3
                    $attributeOverrideAnnot->column,
1262 3
                    $attributeOverrideAnnot->name,
1263 3
                    false
1264
                );
1265
1266 3
                $metadata->setPropertyOverride($fieldMetadata);
1267
            }
1268
        }
1269 352
    }
1270
1271
    /**
1272
     * @param Annotation\Annotation[] $propertyAnnotations
1273
     */
1274 240
    private function attachAssociationPropertyCache(
1275
        array $propertyAnnotations,
1276
        \ReflectionProperty $reflectionProperty,
1277
        Mapping\AssociationMetadata $assocMetadata,
1278
        Mapping\ClassMetadata $metadata
1279
    ) : void {
1280
        // Check for Cache
1281 240
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
1282 14
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
1283 14
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata(
1284 14
                $cacheAnnot,
1285 14
                $metadata,
1286 14
                $reflectionProperty->getName()
1287
            );
1288
1289 14
            $assocMetadata->setCache($cacheMetadata);
1290
        }
1291 240
    }
1292
1293
    /**
1294
     * Attempts to resolve the cascade modes.
1295
     *
1296
     * @param string   $className        The class name.
1297
     * @param string   $fieldName        The field name.
1298
     * @param string[] $originalCascades The original unprocessed field cascades.
1299
     *
1300
     * @return string[] The processed field cascades.
1301
     *
1302
     * @throws Mapping\MappingException If a cascade option is not valid.
1303
     */
1304 240
    private function getCascade(string $className, string $fieldName, array $originalCascades)
1305
    {
1306 240
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1307 240
        $cascades     = array_map('strtolower', $originalCascades);
1308
1309 240
        if (in_array('all', $cascades, true)) {
1310 20
            $cascades = $cascadeTypes;
1311
        }
1312
1313 240
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1314
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1315
1316
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1317
        }
1318
1319 240
        return $cascades;
1320
    }
1321
1322
    /**
1323
     * Attempts to resolve the fetch mode.
1324
     *
1325
     * @param string $className The class name.
1326
     * @param string $fetchMode The fetch mode.
1327
     *
1328
     * @return string The fetch mode as defined in ClassMetadata.
1329
     *
1330
     * @throws Mapping\MappingException If the fetch mode is not valid.
1331
     */
1332 240
    private function getFetchMode($className, $fetchMode) : string
1333
    {
1334 240
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1335
1336 240
        if (! defined($fetchModeConstant)) {
1337
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1338
        }
1339
1340 240
        return constant($fetchModeConstant);
1341
    }
1342
1343
    /**
1344
     * Parses the given method.
1345
     *
1346
     * @return string[]
1347
     */
1348 23
    private function getMethodCallbacks(\ReflectionMethod $method) : array
1349
    {
1350 23
        $annotations = $this->getMethodAnnotations($method);
1351
        $events      = [
1352 23
            Events::prePersist  => Annotation\PrePersist::class,
1353 23
            Events::postPersist => Annotation\PostPersist::class,
1354 23
            Events::preUpdate   => Annotation\PreUpdate::class,
1355 23
            Events::postUpdate  => Annotation\PostUpdate::class,
1356 23
            Events::preRemove   => Annotation\PreRemove::class,
1357 23
            Events::postRemove  => Annotation\PostRemove::class,
1358 23
            Events::postLoad    => Annotation\PostLoad::class,
1359 23
            Events::preFlush    => Annotation\PreFlush::class,
1360
        ];
1361
1362
        // Check for callbacks
1363 23
        $callbacks = [];
1364
1365 23
        foreach ($events as $eventName => $annotationClassName) {
1366 23
            if (isset($annotations[$annotationClassName]) || $method->getName() === $eventName) {
1367 23
                $callbacks[] = $eventName;
1368
            }
1369
        }
1370
1371 23
        return $callbacks;
1372
    }
1373
1374
    /**
1375
     * @return Annotation\Annotation[]
1376
     */
1377 353
    private function getClassAnnotations(\ReflectionClass $reflectionClass) : array
1378
    {
1379 353
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1380
1381 353
        foreach ($classAnnotations as $key => $annot) {
1382 353
            if (! is_numeric($key)) {
1383
                continue;
1384
            }
1385
1386 353
            $classAnnotations[get_class($annot)] = $annot;
1387
        }
1388
1389 353
        return $classAnnotations;
1390
    }
1391
1392
    /**
1393
     * @return Annotation\Annotation[]
1394
     */
1395 353
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty) : array
1396
    {
1397 353
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1398
1399 352
        foreach ($propertyAnnotations as $key => $annot) {
1400 352
            if (! is_numeric($key)) {
1401
                continue;
1402
            }
1403
1404 352
            $propertyAnnotations[get_class($annot)] = $annot;
1405
        }
1406
1407 352
        return $propertyAnnotations;
1408
    }
1409
1410
    /**
1411
     * @return Annotation\Annotation[]
1412
     */
1413 23
    private function getMethodAnnotations(\ReflectionMethod $reflectionMethod) : array
1414
    {
1415 23
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1416
1417 23
        foreach ($methodAnnotations as $key => $annot) {
1418 17
            if (! is_numeric($key)) {
1419
                continue;
1420
            }
1421
1422 17
            $methodAnnotations[get_class($annot)] = $annot;
1423
        }
1424
1425 23
        return $methodAnnotations;
1426
    }
1427
1428
    /**
1429
     * Factory method for the Annotation Driver.
1430
     *
1431
     * @param string|string[] $paths
1432
     *
1433
     * @return AnnotationDriver
1434
     */
1435
    public static function create($paths = [], ?AnnotationReader $reader = null)
1436
    {
1437
        if ($reader === null) {
1438
            $reader = new AnnotationReader();
1439
        }
1440
1441
        return new self($reader, $paths);
1442
    }
1443
}
1444