Failed Conditions
Pull Request — master (#6743)
by Grégoire
18:17 queued 12:33
created

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