Failed Conditions
Pull Request — master (#6959)
by Matthew
12:08
created

AnnotationDriver::getFileExtension()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\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 2269
    public function __construct(Reader $reader, $paths = null)
71
    {
72 2269
        $this->reader = $reader;
73 2269
        if ($paths) {
74 2186
            $this->addPaths((array) $paths);
75
        }
76 2269
    }
77
78
    /**
79
     * Appends lookup paths to metadata driver.
80
     *
81
     * @param string[] $paths
82
     */
83 2190
    public function addPaths(array $paths)
84
    {
85 2190
        $this->paths = array_unique(array_merge($this->paths, $paths));
86 2190
    }
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 192
    public function isTransient($className)
161
    {
162 192
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
163
164 192
        foreach ($classAnnotations as $annot) {
165 187
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
166 187
                return false;
167
            }
168
        }
169 12
        return true;
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175 61
    public function getAllClassNames()
176
    {
177 61
        if ($this->classNames !== null) {
178 46
            return $this->classNames;
179
        }
180
181 61
        if (! $this->paths) {
182
            throw Mapping\MappingException::pathRequired();
183
        }
184
185 61
        $classes       = [];
186 61
        $includedFiles = [];
187
188 61
        foreach ($this->paths as $path) {
189 61
            if (! is_dir($path)) {
190
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
191
            }
192
193 61
            $iterator = new \RegexIterator(
194 61
                new \RecursiveIteratorIterator(
195 61
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
196 61
                    \RecursiveIteratorIterator::LEAVES_ONLY
197
                ),
198 61
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
199 61
                \RecursiveRegexIterator::GET_MATCH
200
            );
201
202 61
            foreach ($iterator as $file) {
203 61
                $sourceFile = $file[0];
204
205 61
                if (! preg_match('(^phar:)i', $sourceFile)) {
206 61
                    $sourceFile = realpath($sourceFile);
207
                }
208
209 61
                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 61
                require_once $sourceFile;
219
220 61
                $includedFiles[] = $sourceFile;
221
            }
222
        }
223
224 61
        $declared = get_declared_classes();
225
226 61
        foreach ($declared as $className) {
227 61
            $rc         = new \ReflectionClass($className);
228 61
            $sourceFile = $rc->getFileName();
229 61
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
230 61
                $classes[] = $className;
231
            }
232
        }
233
234 61
        $this->classNames = $classes;
235
236 61
        return $classes;
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242 362
    public function loadMetadataForClass(
243
        string $className,
244
        Mapping\ClassMetadata $metadata,
245
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
246
    ) : Mapping\ClassMetadata {
247 362
        $reflectionClass = $metadata->getReflectionClass();
248
249 362
        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 362
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
256 362
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
257 362
            $classAnnotations,
258 362
            $reflectionClass,
259 362
            $metadata,
260 362
            $metadataBuildingContext
261
        );
262
263
        // Evaluate @Cache annotation
264 359
        if (isset($classAnnotations[Annotation\Cache::class])) {
265 17
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
266 17
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
267
268 17
            $classMetadata->setCache($cache);
269
        }
270
271
        // Evaluate annotations on properties/fields
272
        /* @var $reflProperty \ReflectionProperty */
273 359
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
274 359
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
275 74
                continue;
276
            }
277
278 359
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
279 358
            $property            = $this->convertPropertyAnnotationsToProperty(
280 358
                $propertyAnnotations,
281 358
                $reflectionProperty,
282 358
                $classMetadata
283
            );
284
285 358
            if (! $property) {
286 1
                continue;
287
            }
288
289 358
            $metadata->addProperty($property);
290
        }
291
292 357
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
293
294 357
        return $classMetadata;
295
    }
296
297
    /**
298
     * @param Annotation\Annotation[] $classAnnotations
299
     *
300
     * @throws Mapping\MappingException
301
     */
302 362
    private function convertClassAnnotationsToClassMetadata(
303
        array $classAnnotations,
304
        \ReflectionClass $reflectionClass,
305
        Mapping\ClassMetadata $metadata,
306
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
307
    ) : Mapping\ClassMetadata {
308
        switch (true) {
309 362
            case isset($classAnnotations[Annotation\Entity::class]):
310 358
                return $this->convertClassAnnotationsToEntityClassMetadata(
311 358
                    $classAnnotations,
312 358
                    $reflectionClass,
313 358
                    $metadata,
314 358
                    $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 27
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
320 24
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
321 24
                    $classAnnotations,
322 24
                    $reflectionClass,
323 24
                    $metadata
324
                );
325
326 3
            case isset($classAnnotations[Annotation\Embeddable::class]):
327
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
328
                    $classAnnotations,
329
                    $reflectionClass,
330
                    $metadata
331
                );
332
333
            default:
334 3
                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 358
    private function convertClassAnnotationsToEntityClassMetadata(
347
        array $classAnnotations,
348
        \ReflectionClass $reflectionClass,
349
        Mapping\ClassMetadata $metadata,
350
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
351
    ) {
352
        /** @var Annotation\Entity $entityAnnot */
353 358
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
354
355 358
        if ($entityAnnot->repositoryClass !== null) {
356 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
357
        }
358
359 358
        if ($entityAnnot->readOnly) {
360 1
            $metadata->asReadOnly();
361
        }
362
363 358
        $metadata->isMappedSuperclass = false;
364 358
        $metadata->isEmbeddedClass    = false;
365
366 358
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
367
368
        // Evaluate @ChangeTrackingPolicy annotation
369 358
        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 358
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
379 77
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
380
381 77
            $metadata->setInheritanceType(
382 77
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
383
            );
384
385 77
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
386 77
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
387
            }
388
        }
389
390 358
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
391 358
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
392 358
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
393
394 358
        return $metadata;
395
    }
396
397
    /**
398
     * @param Annotation\Annotation[] $classAnnotations
399
     */
400 24
    private function convertClassAnnotationsToMappedSuperClassMetadata(
401
        array $classAnnotations,
402
        \ReflectionClass $reflectionClass,
403
        Mapping\ClassMetadata $metadata
404
    ) : Mapping\ClassMetadata {
405
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
406 24
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
407
408 24
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
409 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
410
        }
411
412 24
        $metadata->isMappedSuperclass = true;
413 24
        $metadata->isEmbeddedClass    = false;
414
415 24
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
416 24
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
417 24
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
418
419 24
        return $metadata;
420
    }
421
422
    /**
423
     * @param Annotation\Annotation[] $classAnnotations
424
     */
425
    private function convertClassAnnotationsToEmbeddableClassMetadata(
426
        array $classAnnotations,
427
        \ReflectionClass $reflectionClass,
428
        Mapping\ClassMetadata $metadata
429
    ) : Mapping\ClassMetadata {
430
        $metadata->isMappedSuperclass = false;
431
        $metadata->isEmbeddedClass    = true;
432
433
        return $metadata;
434
    }
435
436
    /**
437
     * @param Annotation\Annotation[] $propertyAnnotations
438
     *
439
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
440
     */
441 358
    private function convertPropertyAnnotationsToProperty(
442
        array $propertyAnnotations,
443
        \ReflectionProperty $reflectionProperty,
444
        Mapping\ClassMetadata $metadata
445
    ) : ?Mapping\Property {
446
        switch (true) {
447 358
            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...
448 354
                return $this->convertReflectionPropertyToFieldMetadata(
449 354
                    $reflectionProperty,
450 354
                    $propertyAnnotations,
451 354
                    $metadata
452
                );
453
454 248
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
455 107
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
456 107
                    $reflectionProperty,
457 107
                    $propertyAnnotations,
458 107
                    $metadata
459
                );
460
461 198
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
462 134
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
463 134
                    $reflectionProperty,
464 134
                    $propertyAnnotations,
465 134
                    $metadata
466
                );
467
468 160
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
469 105
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
470 105
                    $reflectionProperty,
471 105
                    $propertyAnnotations,
472 105
                    $metadata
473
                );
474
475 104
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
476 86
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
477 86
                    $reflectionProperty,
478 86
                    $propertyAnnotations,
479 86
                    $metadata
480
                );
481
482 33
            case isset($propertyAnnotations[Annotation\Embedded::class]):
483 1
                return null;
484
485
            default:
486 32
                return new Mapping\TransientMetadata($reflectionProperty->getName());
487
        }
488
    }
489
490
    /**
491
     * @param Annotation\Annotation[] $propertyAnnotations
492
     *
493
     * @throws Mapping\MappingException
494
     */
495 354
    private function convertReflectionPropertyToFieldMetadata(
496
        \ReflectionProperty $reflProperty,
497
        array $propertyAnnotations,
498
        Mapping\ClassMetadata $metadata
499
    ) : Mapping\FieldMetadata {
500 354
        $className   = $metadata->getClassName();
501 354
        $fieldName   = $reflProperty->getName();
502 354
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
503 354
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
504
505 354
        if ($columnAnnot->type === null) {
506
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
507
        }
508
509 354
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
510
511
        // Check for Id
512 354
        if (isset($propertyAnnotations[Annotation\Id::class])) {
513 350
            $fieldMetadata->setPrimaryKey(true);
514
        }
515
516
        // Check for GeneratedValue strategy
517 354
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
518 300
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
519 300
            $strategy            = strtoupper($generatedValueAnnot->strategy);
520 300
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
521
522 300
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
523 280
                $idGeneratorDefinition = [];
524
525
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
526
                switch (true) {
527 280
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
528 9
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
529
530
                        $idGeneratorDefinition = [
531 9
                            'sequenceName' => $seqGeneratorAnnot->sequenceName,
532 9
                            'allocationSize' => $seqGeneratorAnnot->allocationSize,
533
                        ];
534
535 9
                        break;
536
537 271
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
538 3
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
539
540
                        $idGeneratorDefinition = [
541 3
                            'class' => $customGeneratorAnnot->class,
542 3
                            'arguments' => $customGeneratorAnnot->arguments,
543
                        ];
544
545 3
                        break;
546
547
                    /* @todo If it is not supported, why does this exist? */
548 268
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
549
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
550
                }
551
552 280
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
553
            }
554
        }
555
556 354
        return $fieldMetadata;
557
    }
558
559
    /**
560
     * @param Annotation\Annotation[] $propertyAnnotations
561
     *
562
     * @return Mapping\OneToOneAssociationMetadata
563
     */
564 107
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
565
        \ReflectionProperty $reflectionProperty,
566
        array $propertyAnnotations,
567
        Mapping\ClassMetadata $metadata
568
    ) {
569 107
        $className     = $metadata->getClassName();
570 107
        $fieldName     = $reflectionProperty->getName();
571 107
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
572 107
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
573 107
        $targetEntity  = $oneToOneAnnot->targetEntity;
574
575 107
        $assocMetadata->setTargetEntity($targetEntity);
576 107
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade));
577 107
        $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval);
578 107
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch));
579
580 107
        if (! empty($oneToOneAnnot->mappedBy)) {
581 37
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
582
        }
583
584 107
        if (! empty($oneToOneAnnot->inversedBy)) {
585 52
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
586
        }
587
588
        // Check for Id
589 107
        if (isset($propertyAnnotations[Annotation\Id::class])) {
590 9
            $assocMetadata->setPrimaryKey(true);
591
        }
592
593 107
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
594
595
        // Check for JoinColumn/JoinColumns annotations
596
        switch (true) {
597 107
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
598 79
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
599
600 79
                $assocMetadata->addJoinColumn(
601 79
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
602
                );
603
604 79
                break;
605
606 46
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
607 3
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
608
609 3
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
610 3
                    $assocMetadata->addJoinColumn(
611 3
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
612
                    );
613
                }
614
615 3
                break;
616
        }
617
618 107
        return $assocMetadata;
619
    }
620
621
    /**
622
     * @param Annotation\Annotation[] $propertyAnnotations
623
     *
624
     * @return Mapping\ManyToOneAssociationMetadata
625
     */
626 134
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
627
        \ReflectionProperty $reflectionProperty,
628
        array $propertyAnnotations,
629
        Mapping\ClassMetadata $metadata
630
    ) {
631 134
        $className      = $metadata->getClassName();
632 134
        $fieldName      = $reflectionProperty->getName();
633 134
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
634 134
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
635 134
        $targetEntity   = $manyToOneAnnot->targetEntity;
636
637 134
        $assocMetadata->setTargetEntity($targetEntity);
638 134
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade));
639 134
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch));
640
641 134
        if (! empty($manyToOneAnnot->inversedBy)) {
642 90
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
643
        }
644
645
        // Check for Id
646 134
        if (isset($propertyAnnotations[Annotation\Id::class])) {
647 32
            $assocMetadata->setPrimaryKey(true);
648
        }
649
650 134
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
651
652
        // Check for JoinColumn/JoinColumns annotations
653
        switch (true) {
654 134
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
655 75
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
656
657 75
                $assocMetadata->addJoinColumn(
658 75
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
659
                );
660
661 75
                break;
662
663 68
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
664 16
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
665
666 16
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
667 16
                    $assocMetadata->addJoinColumn(
668 16
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
669
                    );
670
                }
671
672 16
                break;
673
        }
674
675 134
        return $assocMetadata;
676
    }
677
678
    /**
679
     * @param Annotation\Annotation[] $propertyAnnotations
680
     *
681
     * @throws Mapping\MappingException
682
     */
683 105
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
684
        \ReflectionProperty $reflectionProperty,
685
        array $propertyAnnotations,
686
        Mapping\ClassMetadata $metadata
687
    ) : Mapping\OneToManyAssociationMetadata {
688 105
        $className      = $metadata->getClassName();
689 105
        $fieldName      = $reflectionProperty->getName();
690 105
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
691 105
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
692 105
        $targetEntity   = $oneToManyAnnot->targetEntity;
693
694 105
        $assocMetadata->setTargetEntity($targetEntity);
695 105
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
696 105
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
697 105
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));
698
699 105
        if (! empty($oneToManyAnnot->mappedBy)) {
700 105
            $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
701
        }
702
703 105
        if (! empty($oneToManyAnnot->indexBy)) {
704 7
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
705
        }
706
707
        // Check for OrderBy
708 105
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
709 14
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
710
711 14
            $assocMetadata->setOrderBy($orderByAnnot->value);
712
        }
713
714
        // Check for Id
715 105
        if (isset($propertyAnnotations[Annotation\Id::class])) {
716
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
717
        }
718
719 105
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
720
721 105
        return $assocMetadata;
722
    }
723
724
    /**
725
     * @param Annotation\Annotation[] $propertyAnnotations
726
     *
727
     * @throws Mapping\MappingException
728
     */
729 86
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
730
        \ReflectionProperty $reflectionProperty,
731
        array $propertyAnnotations,
732
        Mapping\ClassMetadata $metadata
733
    ) : Mapping\ManyToManyAssociationMetadata {
734 86
        $className       = $metadata->getClassName();
735 86
        $fieldName       = $reflectionProperty->getName();
736 86
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
737 86
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
738 86
        $targetEntity    = $manyToManyAnnot->targetEntity;
739
740 86
        $assocMetadata->setTargetEntity($targetEntity);
741 86
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade));
742 86
        $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval);
743 86
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch));
744
745 86
        if (! empty($manyToManyAnnot->mappedBy)) {
746 34
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
747
        }
748
749 86
        if (! empty($manyToManyAnnot->inversedBy)) {
750 43
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
751
        }
752
753 86
        if (! empty($manyToManyAnnot->indexBy)) {
754 3
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
755
        }
756
757
        // Check for JoinTable
758 86
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
759 68
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
760 68
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
761
762 68
            $assocMetadata->setJoinTable($joinTableMetadata);
763
        }
764
765
        // Check for OrderBy
766 86
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
767 3
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
768
769 3
            $assocMetadata->setOrderBy($orderByAnnot->value);
770
        }
771
772
        // Check for Id
773 86
        if (isset($propertyAnnotations[Annotation\Id::class])) {
774
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
775
        }
776
777 86
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
778
779 86
        return $assocMetadata;
780
    }
781
782
    /**
783
     * Parse the given Column as FieldMetadata
784
     */
785 354
    private function convertColumnAnnotationToFieldMetadata(
786
        Annotation\Column $columnAnnot,
787
        string $fieldName,
788
        bool $isVersioned
789
    ) : Mapping\FieldMetadata {
790 354
        $fieldMetadata = $isVersioned
791 15
            ? new Mapping\VersionFieldMetadata($fieldName)
792 354
            : new Mapping\FieldMetadata($fieldName)
793
        ;
794
795 354
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
796
797 354
        if (! empty($columnAnnot->name)) {
798 73
            $fieldMetadata->setColumnName($columnAnnot->name);
799
        }
800
801 354
        if (! empty($columnAnnot->columnDefinition)) {
802 4
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
803
        }
804
805 354
        if (! empty($columnAnnot->length)) {
806 354
            $fieldMetadata->setLength($columnAnnot->length);
807
        }
808
809 354
        if ($columnAnnot->options) {
810 6
            $fieldMetadata->setOptions($columnAnnot->options);
811
        }
812
813 354
        $fieldMetadata->setScale($columnAnnot->scale);
814 354
        $fieldMetadata->setPrecision($columnAnnot->precision);
815 354
        $fieldMetadata->setNullable($columnAnnot->nullable);
816 354
        $fieldMetadata->setUnique($columnAnnot->unique);
817
818 354
        return $fieldMetadata;
819
    }
820
821
    /**
822
     * Parse the given Table as TableMetadata
823
     */
824 188
    private function convertTableAnnotationToTableMetadata(
825
        Annotation\Table $tableAnnot,
826
        Mapping\TableMetadata $tableMetadata
827
    ) : void {
828 188
        if (! empty($tableAnnot->name)) {
829 183
            $tableMetadata->setName($tableAnnot->name);
830
        }
831
832 188
        if (! empty($tableAnnot->schema)) {
833 5
            $tableMetadata->setSchema($tableAnnot->schema);
834
        }
835
836 188
        foreach ($tableAnnot->options as $optionName => $optionValue) {
837 4
            $tableMetadata->addOption($optionName, $optionValue);
838
        }
839
840 188
        foreach ($tableAnnot->indexes as $indexAnnot) {
841 13
            $tableMetadata->addIndex([
842 13
                'name'    => $indexAnnot->name,
843 13
                'columns' => $indexAnnot->columns,
844 13
                'unique'  => $indexAnnot->unique,
845 13
                'options' => $indexAnnot->options,
846 13
                'flags'   => $indexAnnot->flags,
847
            ]);
848
        }
849
850 188
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
851 6
            $tableMetadata->addUniqueConstraint([
852 6
                'name'    => $uniqueConstraintAnnot->name,
853 6
                'columns' => $uniqueConstraintAnnot->columns,
854 6
                'options' => $uniqueConstraintAnnot->options,
855 6
                'flags'   => $uniqueConstraintAnnot->flags,
856
            ]);
857
        }
858 188
    }
859
860
    /**
861
     * Parse the given JoinTable as JoinTableMetadata
862
     */
863 68
    private function convertJoinTableAnnotationToJoinTableMetadata(
864
        Annotation\JoinTable $joinTableAnnot
865
    ) : Mapping\JoinTableMetadata {
866 68
        $joinTable = new Mapping\JoinTableMetadata();
867
868 68
        if (! empty($joinTableAnnot->name)) {
869 66
            $joinTable->setName($joinTableAnnot->name);
870
        }
871
872 68
        if (! empty($joinTableAnnot->schema)) {
873
            $joinTable->setSchema($joinTableAnnot->schema);
874
        }
875
876 68
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
877 67
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
878
879 67
            $joinTable->addJoinColumn($joinColumn);
880
        }
881
882 68
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
883 67
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
884
885 67
            $joinTable->addInverseJoinColumn($joinColumn);
886
        }
887
888 68
        return $joinTable;
889
    }
890
891
    /**
892
     * Parse the given JoinColumn as JoinColumnMetadata
893
     */
894 175
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
895
        Annotation\JoinColumn $joinColumnAnnot
896
    ) : Mapping\JoinColumnMetadata {
897 175
        $joinColumn = new Mapping\JoinColumnMetadata();
898
899
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
900 175
        if (! empty($joinColumnAnnot->name)) {
901 168
            $joinColumn->setColumnName($joinColumnAnnot->name);
902
        }
903
904 175
        if (! empty($joinColumnAnnot->referencedColumnName)) {
905 175
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
906
        }
907
908 175
        $joinColumn->setNullable($joinColumnAnnot->nullable);
909 175
        $joinColumn->setUnique($joinColumnAnnot->unique);
910
911 175
        if (! empty($joinColumnAnnot->fieldName)) {
912
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
913
        }
914
915 175
        if (! empty($joinColumnAnnot->columnDefinition)) {
916 3
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
917
        }
918
919 175
        if ($joinColumnAnnot->onDelete) {
920 15
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
921
        }
922
923 175
        return $joinColumn;
924
    }
925
926
    /**
927
     * Parse the given Cache as CacheMetadata
928
     *
929
     * @param string|null $fieldName
930
     */
931 17
    private function convertCacheAnnotationToCacheMetadata(
932
        Annotation\Cache $cacheAnnot,
933
        Mapping\ClassMetadata $metadata,
934
        $fieldName = null
935
    ) : Mapping\CacheMetadata {
936 17
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
937 17
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
938
939 17
        $usage  = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
940 17
        $region = $cacheAnnot->region ?: $defaultRegion;
941
942 17
        return new Mapping\CacheMetadata($usage, $region);
943
    }
944
945
    /**
946
     * @return mixed[]
947
     */
948 14
    private function convertSqlResultSetMapping(Annotation\SqlResultSetMapping $resultSetMapping)
949
    {
950 14
        $entities = [];
951
952 14
        foreach ($resultSetMapping->entities as $entityResultAnnot) {
953
            $entityResult = [
954 14
                'fields'                => [],
955 14
                'entityClass'           => $entityResultAnnot->entityClass,
956 14
                'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
957
            ];
958
959 14
            foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
960 14
                $entityResult['fields'][] = [
961 14
                    'name'      => $fieldResultAnnot->name,
962 14
                    'column'    => $fieldResultAnnot->column,
963
                ];
964
            }
965
966 14
            $entities[] = $entityResult;
967
        }
968
969 14
        $columns = [];
970
971 14
        foreach ($resultSetMapping->columns as $columnResultAnnot) {
972 8
            $columns[] = [
973 8
                'name' => $columnResultAnnot->name,
974
            ];
975
        }
976
977
        return [
978 14
            'name'     => $resultSetMapping->name,
979 14
            'entities' => $entities,
980 14
            'columns'  => $columns,
981
        ];
982
    }
983
984
    /**
985
     * @param Annotation\Annotation[] $classAnnotations
986
     */
987 358
    private function attachTable(
988
        array $classAnnotations,
989
        \ReflectionClass $reflectionClass,
990
        Mapping\ClassMetadata $metadata,
991
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
992
    ) : void {
993 358
        $parent = $metadata->getParent();
994
995 358
        if ($parent instanceof Mapping\ClassMetadata
996 358
            && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE
997
        ) {
998
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
999
            do {
1000 29
                assert($parent instanceof Mapping\ClassMetadata);
1001
1002 29
                if (! $parent->isMappedSuperclass) {
1003 29
                    $metadata->setTable($parent->table);
1004
1005 29
                    break;
1006
                }
1007
1008 4
                $parent = $parent->getParent();
1009 4
            } while ($parent !== null);
1010
1011 29
            return;
1012
        }
1013
1014 358
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1015 358
        $tableMetadata  = new Mapping\TableMetadata();
1016
1017 358
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1018
1019
        // Evaluate @Table annotation
1020 358
        if (isset($classAnnotations[Annotation\Table::class])) {
1021 188
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1022
1023 188
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1024
        }
1025
1026 358
        $metadata->setTable($tableMetadata);
1027 358
    }
1028
1029
    /**
1030
     * @param Annotation\Annotation[] $classAnnotations
1031
     *
1032
     * @throws Mapping\MappingException
1033
     */
1034 77
    private function attachDiscriminatorColumn(
1035
        array $classAnnotations,
1036
        \ReflectionClass $reflectionClass,
1037
        Mapping\ClassMetadata $metadata
1038
    ) : void {
1039 77
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1040
1041 77
        $discriminatorColumn->setTableName($metadata->getTableName());
1042 77
        $discriminatorColumn->setColumnName('dtype');
1043 77
        $discriminatorColumn->setType(Type::getType('string'));
1044 77
        $discriminatorColumn->setLength(255);
1045
1046
        // Evaluate DiscriminatorColumn annotation
1047 77
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1048
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1049 61
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1050 61
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1051 57
                ? $discriminatorColumnAnnotation->type
1052 61
                : 'string';
1053
1054 61
            $discriminatorColumn->setType(Type::getType($typeName));
1055 61
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1056
1057 61
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1058 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1059
            }
1060
1061 61
            if (! empty($discriminatorColumnAnnotation->length)) {
1062 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1063
            }
1064
        }
1065
1066 77
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1067
1068
        // Evaluate DiscriminatorMap annotation
1069 77
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1070 76
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1071 76
            $discriminatorMap           = $discriminatorMapAnnotation->value;
1072
1073 76
            $metadata->setDiscriminatorMap($discriminatorMap);
1074
        }
1075 77
    }
1076
1077
    /**
1078
     * @param Annotation\Annotation[] $classAnnotations
1079
     */
1080 359
    private function attachNamedNativeQueries(
1081
        array $classAnnotations,
1082
        \ReflectionClass $reflectionClass,
1083
        Mapping\ClassMetadata $metadata
1084
    ) : void {
1085
        // Evaluate @NamedNativeQueries annotation
1086 359
        if (isset($classAnnotations[Annotation\NamedNativeQueries::class])) {
1087 14
            $namedNativeQueriesAnnot = $classAnnotations[Annotation\NamedNativeQueries::class];
1088
1089 14
            foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
1090 14
                $metadata->addNamedNativeQuery(
1091 14
                    $namedNativeQuery->name,
1092 14
                    $namedNativeQuery->query,
1093
                    [
1094 14
                        'resultClass'      => $namedNativeQuery->resultClass,
1095 14
                        'resultSetMapping' => $namedNativeQuery->resultSetMapping,
1096
                    ]
1097
                );
1098
            }
1099
        }
1100
1101
        // Evaluate @SqlResultSetMappings annotation
1102 359
        if (isset($classAnnotations[Annotation\SqlResultSetMappings::class])) {
1103 14
            $sqlResultSetMappingsAnnot = $classAnnotations[Annotation\SqlResultSetMappings::class];
1104
1105 14
            foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
1106 14
                $sqlResultSetMapping = $this->convertSqlResultSetMapping($resultSetMapping);
1107
1108 14
                $metadata->addSqlResultSetMapping($sqlResultSetMapping);
1109
            }
1110
        }
1111 359
    }
1112
1113
    /**
1114
     * @param Annotation\Annotation[] $classAnnotations
1115
     */
1116 359
    private function attachLifecycleCallbacks(
1117
        array $classAnnotations,
1118
        \ReflectionClass $reflectionClass,
1119
        Mapping\ClassMetadata $metadata
1120
    ) : void {
1121
        // Evaluate @HasLifecycleCallbacks annotation
1122 359
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1123
            /* @var $method \ReflectionMethod */
1124 15
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1125 14
                foreach ($this->getMethodCallbacks($method) as $callback) {
1126 14
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1127
                }
1128
            }
1129
        }
1130 359
    }
1131
1132
    /**
1133
     * @param Annotation\Annotation[] $classAnnotations
1134
     *
1135
     * @throws \ReflectionException
1136
     * @throws Mapping\MappingException
1137
     */
1138 359
    private function attachEntityListeners(
1139
        array $classAnnotations,
1140
        \ReflectionClass $reflectionClass,
1141
        Mapping\ClassMetadata $metadata
1142
    ) : void {
1143
        // Evaluate @EntityListeners annotation
1144 359
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1145
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1146 10
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1147
1148 10
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1149 10
                if (! class_exists($listenerClassName)) {
1150
                    throw Mapping\MappingException::entityListenerClassNotFound(
1151
                        $listenerClassName,
1152
                        $metadata->getClassName()
1153
                    );
1154
                }
1155
1156 10
                $listenerClass = new \ReflectionClass($listenerClassName);
1157
1158
                /* @var $method \ReflectionMethod */
1159 10
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1160 10
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1161 10
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1162
                    }
1163
                }
1164
            }
1165
        }
1166 359
    }
1167
1168
    /**
1169
     * @param Annotation\Annotation[] $classAnnotations
1170
     *
1171
     * @throws Mapping\MappingException
1172
     */
1173 357
    private function attachPropertyOverrides(
1174
        array $classAnnotations,
1175
        \ReflectionClass $reflectionClass,
1176
        Mapping\ClassMetadata $metadata
1177
    ) : void {
1178
        // Evaluate AssociationOverrides annotation
1179 357
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1180 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1181
1182 5
            foreach ($associationOverridesAnnot->value as $associationOverride) {
1183 5
                $fieldName = $associationOverride->name;
1184 5
                $property  = $metadata->getProperty($fieldName);
1185
1186 5
                if (! $property) {
1187
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1188
                }
1189
1190 5
                $existingClass = get_class($property);
1191 5
                $override      = new $existingClass($fieldName);
1192
1193
                // Check for JoinColumn/JoinColumns annotations
1194 5
                if ($associationOverride->joinColumns) {
1195 3
                    $joinColumns = [];
1196
1197 3
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1198 3
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1199
                    }
1200
1201 3
                    $override->setJoinColumns($joinColumns);
1202
                }
1203
1204
                // Check for JoinTable annotations
1205 5
                if ($associationOverride->joinTable) {
1206 2
                    $joinTableAnnot    = $associationOverride->joinTable;
1207 2
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1208
1209 2
                    $override->setJoinTable($joinTableMetadata);
1210
                }
1211
1212
                // Check for inversedBy
1213 5
                if ($associationOverride->inversedBy) {
1214 1
                    $override->setInversedBy($associationOverride->inversedBy);
1215
                }
1216
1217
                // Check for fetch
1218 5
                if ($associationOverride->fetch) {
1219 1
                    $override->setFetchMode(
1220 1
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1221
                    );
1222
                }
1223
1224 5
                $metadata->setPropertyOverride($override);
1225
            }
1226
        }
1227
1228
        // Evaluate AttributeOverrides annotation
1229 357
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1230 3
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1231
1232 3
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1233 3
                $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1234 3
                    $attributeOverrideAnnot->column,
1235 3
                    $attributeOverrideAnnot->name,
1236 3
                    false
1237
                );
1238
1239 3
                $metadata->setPropertyOverride($fieldMetadata);
1240
            }
1241
        }
1242 357
    }
1243
1244
    /**
1245
     * @param Annotation\Annotation[] $propertyAnnotations
1246
     */
1247 244
    private function attachAssociationPropertyCache(
1248
        array $propertyAnnotations,
1249
        \ReflectionProperty $reflectionProperty,
1250
        Mapping\AssociationMetadata $assocMetadata,
1251
        Mapping\ClassMetadata $metadata
1252
    ) : void {
1253
        // Check for Cache
1254 244
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
1255 14
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
1256 14
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata(
1257 14
                $cacheAnnot,
1258 14
                $metadata,
1259 14
                $reflectionProperty->getName()
1260
            );
1261
1262 14
            $assocMetadata->setCache($cacheMetadata);
1263
        }
1264 244
    }
1265
1266
    /**
1267
     * Attempts to resolve the cascade modes.
1268
     *
1269
     * @param string   $className        The class name.
1270
     * @param string   $fieldName        The field name.
1271
     * @param string[] $originalCascades The original unprocessed field cascades.
1272
     *
1273
     * @return string[] The processed field cascades.
1274
     *
1275
     * @throws Mapping\MappingException If a cascade option is not valid.
1276
     */
1277 244
    private function getCascade(string $className, string $fieldName, array $originalCascades)
1278
    {
1279 244
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1280 244
        $cascades     = array_map('strtolower', $originalCascades);
1281
1282 244
        if (in_array('all', $cascades, true)) {
1283 19
            $cascades = $cascadeTypes;
1284
        }
1285
1286 244
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1287
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1288
1289
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1290
        }
1291
1292 244
        return $cascades;
1293
    }
1294
1295
    /**
1296
     * Attempts to resolve the fetch mode.
1297
     *
1298
     * @param string $className The class name.
1299
     * @param string $fetchMode The fetch mode.
1300
     *
1301
     * @return string The fetch mode as defined in ClassMetadata.
1302
     *
1303
     * @throws Mapping\MappingException If the fetch mode is not valid.
1304
     */
1305 244
    private function getFetchMode($className, $fetchMode) : string
1306
    {
1307 244
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1308
1309 244
        if (! defined($fetchModeConstant)) {
1310
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1311
        }
1312
1313 244
        return constant($fetchModeConstant);
1314
    }
1315
1316
    /**
1317
     * Parses the given method.
1318
     *
1319
     * @return string[]
1320
     */
1321 24
    private function getMethodCallbacks(\ReflectionMethod $method) : array
1322
    {
1323 24
        $annotations = $this->getMethodAnnotations($method);
1324
        $events      = [
1325 24
            Events::prePersist  => Annotation\PrePersist::class,
1326 24
            Events::postPersist => Annotation\PostPersist::class,
1327 24
            Events::preUpdate   => Annotation\PreUpdate::class,
1328 24
            Events::postUpdate  => Annotation\PostUpdate::class,
1329 24
            Events::preRemove   => Annotation\PreRemove::class,
1330 24
            Events::postRemove  => Annotation\PostRemove::class,
1331 24
            Events::postLoad    => Annotation\PostLoad::class,
1332 24
            Events::preFlush    => Annotation\PreFlush::class,
1333
        ];
1334
1335
        // Check for callbacks
1336 24
        $callbacks = [];
1337
1338 24
        foreach ($events as $eventName => $annotationClassName) {
1339 24
            if (isset($annotations[$annotationClassName]) || $method->getName() === $eventName) {
1340 24
                $callbacks[] = $eventName;
1341
            }
1342
        }
1343
1344 24
        return $callbacks;
1345
    }
1346
1347
    /**
1348
     * @return Annotation\Annotation[]
1349
     */
1350 362
    private function getClassAnnotations(\ReflectionClass $reflectionClass) : array
1351
    {
1352 362
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1353
1354 362
        foreach ($classAnnotations as $key => $annot) {
1355 359
            if (! is_numeric($key)) {
1356
                continue;
1357
            }
1358
1359 359
            $classAnnotations[get_class($annot)] = $annot;
1360
        }
1361
1362 362
        return $classAnnotations;
1363
    }
1364
1365
    /**
1366
     * @return Annotation\Annotation[]
1367
     */
1368 359
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty) : array
1369
    {
1370 359
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1371
1372 358
        foreach ($propertyAnnotations as $key => $annot) {
1373 358
            if (! is_numeric($key)) {
1374
                continue;
1375
            }
1376
1377 358
            $propertyAnnotations[get_class($annot)] = $annot;
1378
        }
1379
1380 358
        return $propertyAnnotations;
1381
    }
1382
1383
    /**
1384
     * @return Annotation\Annotation[]
1385
     */
1386 24
    private function getMethodAnnotations(\ReflectionMethod $reflectionMethod) : array
1387
    {
1388 24
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1389
1390 24
        foreach ($methodAnnotations as $key => $annot) {
1391 18
            if (! is_numeric($key)) {
1392
                continue;
1393
            }
1394
1395 18
            $methodAnnotations[get_class($annot)] = $annot;
1396
        }
1397
1398 24
        return $methodAnnotations;
1399
    }
1400
1401
    /**
1402
     * Factory method for the Annotation Driver.
1403
     *
1404
     * @param string|string[] $paths
1405
     *
1406
     * @return AnnotationDriver
1407
     */
1408
    public static function create($paths = [], ?AnnotationReader $reader = null)
1409
    {
1410
        if ($reader === null) {
1411
            $reader = new AnnotationReader();
1412
        }
1413
1414
        return new self($reader, $paths);
1415
    }
1416
}
1417