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

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

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