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

AnnotationDriver::attachNamedQueries()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5.2742

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 5
nop 3
dl 0
loc 19
ccs 7
cts 9
cp 0.7778
crap 5.2742
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\Reader;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Annotation;
11
use Doctrine\ORM\Events;
12
use Doctrine\ORM\Mapping;
13
14
/**
15
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
16
 *
17
 */
18
class AnnotationDriver implements MappingDriver
19
{
20
    /**
21
     * @var int[]
22
     */
23
    protected $entityAnnotationClasses = [
24
        Annotation\Entity::class           => 1,
25
        Annotation\MappedSuperclass::class => 2,
26
    ];
27
28
    /**
29
     * The AnnotationReader.
30
     *
31
     * @var AnnotationReader
32
     */
33
    protected $reader;
34
35
    /**
36
     * The paths where to look for mapping files.
37
     *
38
     * @var string[]
39
     */
40
    protected $paths = [];
41
42
    /**
43
     * The paths excluded from path where to look for mapping files.
44
     *
45
     * @var string[]
46
     */
47
    protected $excludePaths = [];
48
49
    /**
50
     * The file extension of mapping documents.
51
     *
52
     * @var string
53
     */
54
    protected $fileExtension = '.php';
55
56
    /**
57
     * Cache for AnnotationDriver#getAllClassNames().
58
     *
59
     * @var string[]|null
60
     */
61
    protected $classNames;
62
63
    /**
64
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
65
     * docblock annotations.
66
     *
67
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
68
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
69
     */
70 2268
    public function __construct(Reader $reader, $paths = null)
71
    {
72 2268
        $this->reader = $reader;
73 2268
        if ($paths) {
74 2185
            $this->addPaths((array) $paths);
75
        }
76 2268
    }
77
78
    /**
79
     * Appends lookup paths to metadata driver.
80
     *
81
     * @param string[] $paths
82
     */
83 2189
    public function addPaths(array $paths)
84
    {
85 2189
        $this->paths = array_unique(array_merge($this->paths, $paths));
86 2189
    }
87
88
    /**
89
     * Retrieves the defined metadata lookup paths.
90
     *
91
     * @return string[]
92
     */
93
    public function getPaths()
94
    {
95
        return $this->paths;
96
    }
97
98
    /**
99
     * Append exclude lookup paths to metadata driver.
100
     *
101
     * @param string[] $paths
102
     */
103
    public function addExcludePaths(array $paths)
104
    {
105
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
106
    }
107
108
    /**
109
     * Retrieve the defined metadata lookup exclude paths.
110
     *
111
     * @return string[]
112
     */
113
    public function getExcludePaths()
114
    {
115
        return $this->excludePaths;
116
    }
117
118
    /**
119
     * Retrieve the current annotation reader
120
     *
121
     * @return AnnotationReader
122
     */
123 1
    public function getReader()
124
    {
125 1
        return $this->reader;
126
    }
127
128
    /**
129
     * Gets the file extension used to look for mapping files under.
130
     *
131
     * @return string
132
     */
133
    public function getFileExtension()
134
    {
135
        return $this->fileExtension;
136
    }
137
138
    /**
139
     * Sets the file extension used to look for mapping files under.
140
     *
141
     * @param string $fileExtension The file extension to set.
142
     *
143
     */
144
    public function setFileExtension($fileExtension)
145
    {
146
        $this->fileExtension = $fileExtension;
147
    }
148
149
    /**
150
     * Returns whether the class with the specified name is transient. Only non-transient
151
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
152
     *
153
     * A class is non-transient if it is annotated with an annotation
154
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
155
     *
156
     * @param string $className
157
     *
158
     * @return bool
159
     */
160 191
    public function isTransient($className)
161
    {
162 191
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
163
164 191
        foreach ($classAnnotations as $annot) {
165 186
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
166 186
                return false;
167
            }
168
        }
169 12
        return true;
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175 61
    public function getAllClassNames()
176
    {
177 61
        if ($this->classNames !== null) {
178 46
            return $this->classNames;
179
        }
180
181 61
        if (! $this->paths) {
182
            throw Mapping\MappingException::pathRequired();
183
        }
184
185 61
        $classes       = [];
186 61
        $includedFiles = [];
187
188 61
        foreach ($this->paths as $path) {
189 61
            if (! is_dir($path)) {
190
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
191
            }
192
193 61
            $iterator = new \RegexIterator(
194 61
                new \RecursiveIteratorIterator(
195 61
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
196 61
                    \RecursiveIteratorIterator::LEAVES_ONLY
197
                ),
198 61
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
199 61
                \RecursiveRegexIterator::GET_MATCH
200
            );
201
202 61
            foreach ($iterator as $file) {
203 61
                $sourceFile = $file[0];
204
205 61
                if (! preg_match('(^phar:)i', $sourceFile)) {
206 61
                    $sourceFile = realpath($sourceFile);
207
                }
208
209 61
                foreach ($this->excludePaths as $excludePath) {
210
                    $exclude = str_replace('\\', '/', realpath($excludePath));
211
                    $current = str_replace('\\', '/', $sourceFile);
212
213
                    if (strpos($current, $exclude) !== false) {
214
                        continue 2;
215
                    }
216
                }
217
218 61
                require_once $sourceFile;
219
220 61
                $includedFiles[] = $sourceFile;
221
            }
222
        }
223
224 61
        $declared = get_declared_classes();
225
226 61
        foreach ($declared as $className) {
227 61
            $rc         = new \ReflectionClass($className);
228 61
            $sourceFile = $rc->getFileName();
229 61
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
230 61
                $classes[] = $className;
231
            }
232
        }
233
234 61
        $this->classNames = $classes;
235
236 61
        return $classes;
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242 361
    public function loadMetadataForClass(
243
        string $className,
244
        Mapping\ClassMetadata $metadata,
245
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
246
    ) : Mapping\ClassMetadata {
247 361
        $reflectionClass = $metadata->getReflectionClass();
248
249 361
        if (! $reflectionClass) {
250
            // this happens when running annotation driver in combination with
251
            // static reflection services. This is not the nicest fix
252
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
253
        }
254
255 361
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
256 361
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
257 361
            $classAnnotations,
258 361
            $reflectionClass,
259 361
            $metadata,
260 361
            $metadataBuildingContext
261
        );
262
263
        // Evaluate @Cache annotation
264 358
        if (isset($classAnnotations[Annotation\Cache::class])) {
265 17
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
266 17
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
267
268 17
            $classMetadata->setCache($cache);
269
        }
270
271
        // Evaluate annotations on properties/fields
272
        /* @var $reflProperty \ReflectionProperty */
273 358
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
274 358
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
275 74
                continue;
276
            }
277
278 358
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
279 357
            $property            = $this->convertPropertyAnnotationsToProperty(
280 357
                $propertyAnnotations,
281 357
                $reflectionProperty,
282 357
                $classMetadata
283
            );
284
285 357
            if (! $property) {
286 1
                continue;
287
            }
288
289 357
            $metadata->addProperty($property);
290
        }
291
292 356
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
293
294 356
        return $classMetadata;
295
    }
296
297
    /**
298
     * @param Annotation\Annotation[] $classAnnotations
299
     *
300
     * @throws Mapping\MappingException
301
     */
302 361
    private function convertClassAnnotationsToClassMetadata(
303
        array $classAnnotations,
304
        \ReflectionClass $reflectionClass,
305
        Mapping\ClassMetadata $metadata,
306
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
307
    ) : Mapping\ClassMetadata {
308
        switch (true) {
309 361
            case isset($classAnnotations[Annotation\Entity::class]):
310 357
                return $this->convertClassAnnotationsToEntityClassMetadata(
311 357
                    $classAnnotations,
312 357
                    $reflectionClass,
313 357
                    $metadata,
314 357
                    $metadataBuildingContext
315
                );
316
317
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
318
319 27
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
320 24
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
321 24
                    $classAnnotations,
322 24
                    $reflectionClass,
323 24
                    $metadata
324
                );
325
326 3
            case isset($classAnnotations[Annotation\Embeddable::class]):
327
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
328
                    $classAnnotations,
329
                    $reflectionClass,
330
                    $metadata
331
                );
332
333
            default:
334 3
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
335
        }
336
    }
337
338
    /**
339
     * @param Annotation\Annotation[] $classAnnotations
340
     *
341
     * @return Mapping\ClassMetadata
342
     *
343
     * @throws Mapping\MappingException
344
     * @throws \UnexpectedValueException
345
     */
346 357
    private function convertClassAnnotationsToEntityClassMetadata(
347
        array $classAnnotations,
348
        \ReflectionClass $reflectionClass,
349
        Mapping\ClassMetadata $metadata,
350
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
351
    ) {
352
        /** @var Annotation\Entity $entityAnnot */
353 357
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
354
355 357
        if ($entityAnnot->repositoryClass !== null) {
356 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
357
        }
358
359 357
        if ($entityAnnot->readOnly) {
360 1
            $metadata->asReadOnly();
361
        }
362
363 357
        $metadata->isMappedSuperclass = false;
364 357
        $metadata->isEmbeddedClass    = false;
365
366 357
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
367
368
        // Evaluate @ChangeTrackingPolicy annotation
369 357
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
370 5
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
371
372 5
            $metadata->setChangeTrackingPolicy(
373 5
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
374
            );
375
        }
376
377
        // Evaluate @InheritanceType annotation
378 357
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
379 77
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
380
381 77
            $metadata->setInheritanceType(
382 77
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
383
            );
384
385 77
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
386 77
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
387
            }
388
        }
389
390 357
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
391 357
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
392 357
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
393
394 357
        return $metadata;
395
    }
396
397
    /**
398
     * @param Annotation\Annotation[] $classAnnotations
399
     */
400 24
    private function convertClassAnnotationsToMappedSuperClassMetadata(
401
        array $classAnnotations,
402
        \ReflectionClass $reflectionClass,
403
        Mapping\ClassMetadata $metadata
404
    ) : Mapping\ClassMetadata {
405
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
406 24
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
407
408 24
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
409 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
410
        }
411
412 24
        $metadata->isMappedSuperclass = true;
413 24
        $metadata->isEmbeddedClass    = false;
414
415 24
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
416 24
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
417 24
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
418
419 24
        return $metadata;
420
    }
421
422
    /**
423
     * @param Annotation\Annotation[] $classAnnotations
424
     */
425
    private function convertClassAnnotationsToEmbeddableClassMetadata(
426
        array $classAnnotations,
427
        \ReflectionClass $reflectionClass,
428
        Mapping\ClassMetadata $metadata
429
    ) : Mapping\ClassMetadata {
430
        $metadata->isMappedSuperclass = false;
431
        $metadata->isEmbeddedClass    = true;
432
433
        return $metadata;
434
    }
435
436
    /**
437
     * @param Annotation\Annotation[] $propertyAnnotations
438
     *
439
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
440
     */
441 357
    private function convertPropertyAnnotationsToProperty(
442
        array $propertyAnnotations,
443
        \ReflectionProperty $reflectionProperty,
444
        Mapping\ClassMetadata $metadata
445
    ) : ?Mapping\Property {
446
        switch (true) {
447 357
            case isset($propertyAnnotations[Annotation\Column::class]):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

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