Failed Conditions
Pull Request — master (#7085)
by Guilherme
14:34
created

Doctrine/ORM/Mapping/Driver/AnnotationDriver.php (1 issue)

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