Failed Conditions
Push — master ( ee4e26...e98654 )
by Marco
13:06
created

AnnotationDriver::attachNamedNativeQueries()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 29
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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