Failed Conditions
Push — master ( 148895...b07393 )
by Guilherme
09:59
created

ORM/Mapping/Driver/NewAnnotationDriver.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\Persistence\Mapping\Driver\FileLocator;
9
use Doctrine\DBAL\DBALException;
10
use Doctrine\DBAL\Types\Type;
11
use Doctrine\ORM\Annotation;
12
use Doctrine\ORM\Events;
13
use Doctrine\ORM\Mapping;
14
use Doctrine\ORM\Mapping\Factory;
15
use ReflectionClass;
16
use ReflectionException;
17
use ReflectionMethod;
18
use ReflectionProperty;
19
use function array_diff;
20
use function array_filter;
21
use function array_intersect;
22
use function array_map;
23
use function array_merge;
24
use function class_exists;
25
use function constant;
26
use function count;
27
use function defined;
28
use function get_class;
29
use function in_array;
30
use function is_numeric;
31
use function sprintf;
32
use function str_replace;
33
use function strtolower;
34
use function strtoupper;
35
36
class NewAnnotationDriver implements MappingDriver
37
{
38
    /** @var int[] */
39
    protected static $entityAnnotationClasses = [
40
        Annotation\Entity::class           => 1,
41
        Annotation\MappedSuperclass::class => 2,
42
    ];
43
44
    /**
45
     * The Annotation reader.
46
     *
47
     * @var AnnotationReader
48
     */
49
    protected $reader;
50
51
    /**
52
     * The file locator.
53
     *
54
     * @var FileLocator
55
     */
56
    protected $locator;
57
58
    /** @var Factory\NamingStrategy */
59
    protected $namingStrategy;
60
61
    /**
62
     * Cache for AnnotationDriver#getAllClassNames().
63
     *
64
     * @var string[]|null
65
     */
66
    private $classNames;
67
68
    /**
69
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading docblock annotations.
70
     *
71
     * @param AnnotationReader       $reader         The AnnotationReader to use, duck-typed.
72
     * @param FileLocator            $locator        A FileLocator or one/multiple paths where mapping documents can be found.
73
     * @param Factory\NamingStrategy $namingStrategy The NamingStrategy to use.
74
     */
75
    public function __construct(AnnotationReader $reader, FileLocator $locator, Factory\NamingStrategy $namingStrategy)
76
    {
77
        $this->reader         = $reader;
78
        $this->locator        = $locator;
79
        $this->namingStrategy = $namingStrategy;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     *
85
     * @return Mapping\ComponentMetadata
86
     *
87
     * @throws Mapping\MappingException
88
     * @throws ReflectionException
89
     */
90
    public function loadMetadataForClass(
91
        string $className,
92
        ?Mapping\ComponentMetadata $parent,
93
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
94
    ) : Mapping\ComponentMetadata {
95
        // IMPORTANT: We're handling $metadata as "parent" metadata here, while building the $className ClassMetadata.
96
        $reflectionClass = new ReflectionClass($className);
97
98
        // Evaluate annotations on class metadata
99
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
100
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
101
            $classAnnotations,
102
            $reflectionClass,
103
            $parent,
104
            $metadataBuildingContext
105
        );
106
107
        // Evaluate @Cache annotation
108
        if (isset($classAnnotations[Annotation\Cache::class])) {
109
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
110
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $classMetadata);
111
112
            $classMetadata->setCache($cache);
113
        }
114
115
        // Evaluate annotations on properties/fields
116
        /** @var ReflectionProperty $reflectionProperty */
117
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
118
            if ($reflectionProperty->getDeclaringClass()->getClassName() !== $reflectionClass->getName()) {
119
                continue;
120
            }
121
122
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
123
            $property            = $this->convertReflectionPropertyAnnotationsToProperty(
124
                $reflectionProperty,
125
                $propertyAnnotations,
126
                $classMetadata
127
            );
128
129
            $classMetadata->addProperty($property);
130
        }
131
132
        return $classMetadata;
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function getAllClassNames() : array
139
    {
140
        if ($this->classNames !== null) {
141
            return $this->classNames;
142
        }
143
144
        $classNames = array_filter(
145
            $this->locator->getAllClassNames(null),
146
            function ($className) {
147
                return ! $this->isTransient($className);
148
            }
149
        );
150
151
        $this->classNames = $classNames;
152
153
        return $classNames;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function isTransient($className) : bool
160
    {
161
        $reflectionClass  = new ReflectionClass($className);
162
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
163
164
        foreach ($classAnnotations as $annotation) {
165
            if (isset(self::$entityAnnotationClasses[get_class($annotation)])) {
166
                return false;
167
            }
168
        }
169
170
        return true;
171
    }
172
173
    /**
174
     * @param Annotation\Annotation[] $classAnnotations
175
     *
176
     * @return Mapping\ClassMetadata|Mapping\ComponentMetadata
177
     *
178
     * @throws DBALException
179
     * @throws ReflectionException
180
     */
181
    private function convertClassAnnotationsToClassMetadata(
182
        array $classAnnotations,
183
        ReflectionClass $reflectionClass,
184
        ?Mapping\ComponentMetadata $parent,
185
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
186
    ) : Mapping\ComponentMetadata {
187
        switch (true) {
188
            case isset($classAnnotations[Annotation\Entity::class]):
189
                return $this->convertClassAnnotationsToEntityClassMetadata(
190
                    $classAnnotations,
191
                    $reflectionClass,
192
                    $parent,
193
                    $metadataBuildingContext
194
                );
195
196
                break;
197
198
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
199
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
200
                    $classAnnotations,
201
                    $reflectionClass,
202
                    $parent,
203
                    $metadataBuildingContext
204
                );
205
            case isset($classAnnotations[Annotation\Embeddable::class]):
206
                return $this->convertClassAnnotationsToEntityClassMetadata(
207
                    $classAnnotations,
208
                    $reflectionClass,
209
                    $parent,
210
                    $metadataBuildingContext
211
                );
212
            default:
213
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
214
        }
215
    }
216
217
    /**
218
     * @param Annotation\Annotation[] $classAnnotations
219
     *
220
     * @return Mapping\ClassMetadata
221
     *
222
     * @throws DBALException
223
     * @throws ReflectionException
224
     */
225
    private function convertClassAnnotationsToEntityClassMetadata(
226
        array $classAnnotations,
227
        ReflectionClass $reflectionClass,
228
        ?Mapping\ComponentMetadata $parent,
229
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
230
    ) {
231
        /** @var Annotation\Entity $entityAnnot */
232
        $entityAnnot   = $classAnnotations[Annotation\Entity::class];
233
        $classMetadata = new Mapping\ClassMetadata($reflectionClass->getName(), $parent, $metadataBuildingContext);
234
235
        if ($entityAnnot->repositoryClass !== null) {
236
            $classMetadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
237
        }
238
239
        if ($entityAnnot->readOnly) {
240
            $classMetadata->asReadOnly();
241
        }
242
243
        // Evaluate @Table annotation
244
        if (isset($classAnnotations[Annotation\Table::class])) {
245
            /** @var Annotation\Table $tableAnnot */
246
            $tableAnnot = $classAnnotations[Annotation\Table::class];
247
            $table      = $this->convertTableAnnotationToTableMetadata($tableAnnot);
248
249
            $classMetadata->setTable($table);
250
        }
251
252
        // Evaluate @ChangeTrackingPolicy annotation
253
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
254
            /** @var Annotation\ChangeTrackingPolicy $changeTrackingAnnot */
255
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
256
257
            $classMetadata->setChangeTrackingPolicy(
258
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
259
            );
260
        }
261
262
        // Evaluate @EntityListeners annotation
263
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
264
            /** @var Annotation\EntityListeners $entityListenersAnnot */
265
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
266
            $eventMap             = [
267
                Events::prePersist  => Annotation\PrePersist::class,
268
                Events::postPersist => Annotation\PostPersist::class,
269
                Events::preUpdate   => Annotation\PreUpdate::class,
270
                Events::postUpdate  => Annotation\PostUpdate::class,
271
                Events::preRemove   => Annotation\PreRemove::class,
272
                Events::postRemove  => Annotation\PostRemove::class,
273
                Events::postLoad    => Annotation\PostLoad::class,
274
                Events::preFlush    => Annotation\PreFlush::class,
275
            ];
276
277
            foreach ($entityListenersAnnot->value as $listenerClassName) {
278
                if (! class_exists($listenerClassName)) {
279
                    throw Mapping\MappingException::entityListenerClassNotFound(
280
                        $listenerClassName,
281
                        $classMetadata->getClassName()
282
                    );
283
                }
284
285
                $listenerClass = new ReflectionClass($listenerClassName);
286
287
                /** @var ReflectionMethod $reflectionMethod */
288
                foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
289
                    $annotations = $this->getMethodAnnotations($reflectionMethod);
290
291
                    foreach ($eventMap as $eventName => $annotationClassName) {
292
                        if (isset($annotations[$annotationClassName])) {
293
                            $classMetadata->addEntityListener($eventName, $listenerClassName, $reflectionMethod->getName());
294
                        }
295
                    }
296
                }
297
            }
298
        }
299
300
        // Evaluate @HasLifecycleCallbacks annotation
301
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
302
            $eventMap = [
303
                Events::prePersist  => Annotation\PrePersist::class,
304
                Events::postPersist => Annotation\PostPersist::class,
305
                Events::preUpdate   => Annotation\PreUpdate::class,
306
                Events::postUpdate  => Annotation\PostUpdate::class,
307
                Events::preRemove   => Annotation\PreRemove::class,
308
                Events::postRemove  => Annotation\PostRemove::class,
309
                Events::postLoad    => Annotation\PostLoad::class,
310
                Events::preFlush    => Annotation\PreFlush::class,
311
            ];
312
313
            /** @var ReflectionMethod $reflectionMethod */
314
            foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
315
                $annotations = $this->getMethodAnnotations($reflectionMethod);
316
317
                foreach ($eventMap as $eventName => $annotationClassName) {
318
                    if (isset($annotations[$annotationClassName])) {
319
                        $classMetadata->addLifecycleCallback($eventName, $reflectionMethod->getName());
320
                    }
321
                }
322
            }
323
        }
324
325
        // Evaluate @InheritanceType annotation
326
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
327
            /** @var Annotation\InheritanceType $inheritanceTypeAnnot */
328
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
329
330
            $classMetadata->setInheritanceType(
331
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
332
            );
333
334
            if ($classMetadata->inheritanceType !== Mapping\InheritanceType::NONE) {
335
                $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
336
337
                // Evaluate @DiscriminatorColumn annotation
338
                if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
339
                    /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnot */
340
                    $discriminatorColumnAnnot = $classAnnotations[Annotation\DiscriminatorColumn::class];
341
342
                    $discriminatorColumn->setColumnName($discriminatorColumnAnnot->name);
343
344
                    if (! empty($discriminatorColumnAnnot->columnDefinition)) {
345
                        $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnot->columnDefinition);
346
                    }
347
348
                    if (! empty($discriminatorColumnAnnot->type)) {
349
                        $discriminatorColumn->setType(Type::getType($discriminatorColumnAnnot->type));
350
                    }
351
352
                    if (! empty($discriminatorColumnAnnot->length)) {
353
                        $discriminatorColumn->setLength($discriminatorColumnAnnot->length);
354
                    }
355
                }
356
357
                if (empty($discriminatorColumn->getColumnName())) {
358
                    throw Mapping\MappingException::nameIsMandatoryForDiscriminatorColumns($reflectionClass->getName());
359
                }
360
361
                $classMetadata->setDiscriminatorColumn($discriminatorColumn);
362
363
                // Evaluate @DiscriminatorMap annotation
364
                if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
365
                    /** @var Annotation\DiscriminatorMap $discriminatorMapAnnotation */
366
                    $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
367
                    $discriminatorMap           = $discriminatorMapAnnotation->value;
368
369
                    $classMetadata->setDiscriminatorMap($discriminatorMap);
370
                }
371
            }
372
        }
373
374
        return $classMetadata;
375
    }
376
377
    /**
378
     * @param Annotation\Annotation[] $classAnnotations
379
     *
380
     * @return Mapping\MappedSuperClassMetadata
381
     */
382
    private function convertClassAnnotationsToMappedSuperClassMetadata(
383
        array $classAnnotations,
384
        ReflectionClass $reflectionClass,
385
        ?Mapping\ComponentMetadata $parent,
386
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
387
    ) {
388
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
389
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
390
        $classMetadata         = new Mapping\MappedSuperClassMetadata($reflectionClass->getName(), $parent);
0 ignored issues
show
$parent of type Doctrine\ORM\Mapping\ComponentMetadata|null is incompatible with the type Doctrine\ORM\Mapping\ClassMetadataBuildingContext expected by parameter $metadataBuildingContext of Doctrine\ORM\Mapping\Map...Metadata::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

390
        $classMetadata         = new Mapping\MappedSuperClassMetadata($reflectionClass->getName(), /** @scrutinizer ignore-type */ $parent);
Loading history...
391
392
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
393
            $classMetadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
394
        }
395
396
        return $classMetadata;
397
    }
398
399
    /**
400
     * Parse the given Table as TableMetadata
401
     *
402
     * @return Mapping\TableMetadata
403
     */
404
    private function convertTableAnnotationToTableMetadata(Annotation\Table $tableAnnot)
405
    {
406
        $table = new Mapping\TableMetadata();
407
408
        if (! empty($tableAnnot->name)) {
409
            $table->setName($tableAnnot->name);
410
        }
411
412
        if (! empty($tableAnnot->schema)) {
413
            $table->setSchema($tableAnnot->schema);
414
        }
415
416
        foreach ($tableAnnot->options as $optionName => $optionValue) {
417
            $table->addOption($optionName, $optionValue);
418
        }
419
420
        foreach ($tableAnnot->indexes as $indexAnnot) {
421
            $table->addIndex([
422
                'name'    => $indexAnnot->name,
423
                'columns' => $indexAnnot->columns,
424
                'unique'  => $indexAnnot->unique,
425
                'options' => $indexAnnot->options,
426
                'flags'   => $indexAnnot->flags,
427
            ]);
428
        }
429
430
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
431
            $table->addUniqueConstraint([
432
                'name'    => $uniqueConstraintAnnot->name,
433
                'columns' => $uniqueConstraintAnnot->columns,
434
                'options' => $uniqueConstraintAnnot->options,
435
                'flags'   => $uniqueConstraintAnnot->flags,
436
            ]);
437
        }
438
439
        return $table;
440
    }
441
442
    /**
443
     * Parse the given Cache as CacheMetadata
444
     *
445
     * @param string|null $fieldName
446
     *
447
     * @return Mapping\CacheMetadata
448
     */
449
    private function convertCacheAnnotationToCacheMetadata(
450
        Annotation\Cache $cacheAnnot,
451
        Mapping\ClassMetadata $metadata,
452
        $fieldName = null
453
    ) {
454
        $usage         = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
455
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
456
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
457
458
        return new Mapping\CacheMetadata($usage, $cacheAnnot->region ?: $defaultRegion);
459
    }
460
461
    /**
462
     * @param Annotation\Annotation[] $propertyAnnotations
463
     *
464
     * @return Mapping\Property
465
     *
466
     * @throws Mapping\MappingException
467
     */
468
    private function convertReflectionPropertyAnnotationsToProperty(
469
        ReflectionProperty $reflectionProperty,
470
        array $propertyAnnotations,
471
        Mapping\ClassMetadata $classMetadata
472
    ) {
473
        // Field can only be annotated with one of:
474
        // @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, @Embedded
475
        switch (true) {
476
            case isset($propertyAnnotations[Annotation\Column::class]):
477
                return $this->convertReflectionPropertyToFieldMetadata(
478
                    $reflectionProperty,
479
                    $propertyAnnotations,
480
                    $classMetadata
481
                );
482
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
483
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
484
                    $reflectionProperty,
485
                    $propertyAnnotations,
486
                    $classMetadata
487
                );
488
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
489
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
490
                    $reflectionProperty,
491
                    $propertyAnnotations,
492
                    $classMetadata
493
                );
494
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
495
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
496
                    $reflectionProperty,
497
                    $propertyAnnotations,
498
                    $classMetadata
499
                );
500
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
501
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
502
                    $reflectionProperty,
503
                    $propertyAnnotations,
504
                    $classMetadata
505
                );
506
            case isset($propertyAnnotations[Annotation\Embedded::class]):
507
                // @todo guilhermeblanco Implement later... =)
508
                break;
509
510
            default:
511
                return new Mapping\TransientMetadata($reflectionProperty->getName());
512
        }
513
    }
514
515
    /**
516
     * @param Annotation\Annotation[] $propertyAnnotations
517
     *
518
     * @return Mapping\FieldMetadata
519
     *
520
     * @throws Mapping\MappingException
521
     * @throws DBALException
522
     */
523
    private function convertReflectionPropertyToFieldMetadata(
524
        ReflectionProperty $reflectionProperty,
525
        array $propertyAnnotations,
526
        Mapping\ClassMetadata $classMetadata
527
    ) {
528
        $className    = $classMetadata->getClassName();
529
        $fieldName    = $reflectionProperty->getName();
530
        $columnAnnot  = $propertyAnnotations[Annotation\Column::class];
531
        $isVersioned  = isset($propertyAnnotations[Annotation\Version::class]);
532
        $isPrimaryKey = isset($propertyAnnotations[Annotation\Id::class]);
533
534
        if ($columnAnnot->type === null) {
535
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
536
        }
537
538
        if ($isVersioned && $isPrimaryKey) {
539
            throw Mapping\MappingException::cannotVersionIdField($className, $fieldName);
540
        }
541
542
        $fieldType  = Type::getType($columnAnnot->type);
543
        $columnName = empty($columnAnnot->name)
544
            ? $this->namingStrategy->propertyToColumnName($fieldName, $className)
545
            : $columnAnnot->name;
546
547
        $fieldMetadata = new Mapping\FieldMetadata($fieldName);
548
549
        $fieldMetadata->setType($fieldType);
550
        $fieldMetadata->setVersioned($isVersioned);
551
        $fieldMetadata->setColumnName($columnName);
552
        $fieldMetadata->setScale($columnAnnot->scale);
553
        $fieldMetadata->setPrecision($columnAnnot->precision);
554
        $fieldMetadata->setNullable($columnAnnot->nullable);
555
        $fieldMetadata->setUnique($columnAnnot->unique);
556
557
        // Check for Id
558
        if ($isPrimaryKey) {
559
            if ($fieldType->canRequireSQLConversion()) {
560
                throw Mapping\MappingException::sqlConversionNotAllowedForPrimaryKeyProperties(
561
                    $className,
562
                    $fieldName,
563
                    $fieldType->getName()
564
                );
565
            }
566
567
            $fieldMetadata->setPrimaryKey(true);
568
        }
569
570
        if (! empty($columnAnnot->columnDefinition)) {
571
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
572
        }
573
574
        if (! empty($columnAnnot->length)) {
575
            $fieldMetadata->setLength($columnAnnot->length);
576
        }
577
578
        // Assign default options
579
        $customOptions  = $columnAnnot->options ?? [];
580
        $defaultOptions = [];
581
582
        if ($isVersioned) {
583
            switch ($fieldMetadata->getTypeName()) {
584
                case 'integer':
585
                case 'bigint':
586
                case 'smallint':
587
                    $defaultOptions['default'] = 1;
588
                    break;
589
590
                case 'datetime':
591
                    $defaultOptions['default'] = 'CURRENT_TIMESTAMP';
592
                    break;
593
594
                default:
595
                    if (! isset($customOptions['default'])) {
596
                        throw Mapping\MappingException::unsupportedOptimisticLockingType($fieldMetadata->getType());
597
                    }
598
            }
599
        }
600
601
        $fieldMetadata->setOptions(array_merge($defaultOptions, $customOptions));
602
603
        return $fieldMetadata;
604
    }
605
606
    /**
607
     * @param Annotation\Annotation[] $propertyAnnotations
608
     *
609
     * @return Mapping\OneToOneAssociationMetadata
610
     */
611
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
612
        ReflectionProperty $reflectionProperty,
613
        array $propertyAnnotations,
614
        Mapping\ClassMetadata $classMetadata
615
    ) {
616
        $className     = $classMetadata->getClassName();
617
        $fieldName     = $reflectionProperty->getName();
618
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
619
620
        if ($oneToOneAnnot->targetEntity === null) {
621
            throw Mapping\MappingException::missingTargetEntity($fieldName);
622
        }
623
624
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
625
        $targetEntity  = $oneToOneAnnot->targetEntity;
626
627
        $assocMetadata->setSourceEntity($className);
628
        $assocMetadata->setTargetEntity($targetEntity);
629
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade));
630
        $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval);
631
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch));
632
633
        if (! empty($oneToOneAnnot->mappedBy)) {
634
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
635
        }
636
637
        if (! empty($oneToOneAnnot->inversedBy)) {
638
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
639
        }
640
641
        // Check for Id
642
        if (isset($propertyAnnotations[Annotation\Id::class])) {
643
            $assocMetadata->setPrimaryKey(true);
644
        }
645
646
        // Check for Cache
647
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
648
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
649
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $classMetadata, $fieldName);
650
651
            $assocMetadata->setCache($cacheMetadata);
652
        }
653
654
        // Check for JoinColumn/JoinColumns annotations
655
        switch (true) {
656
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
657
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
658
                $joinColumn      = $this->convertJoinColumnAnnotationToJoinColumnMetadata(
659
                    $reflectionProperty,
660
                    $joinColumnAnnot,
661
                    $classMetadata
662
                );
663
664
                $assocMetadata->addJoinColumn($joinColumn);
665
666
                break;
667
668
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
669
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
670
671
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
672
                    $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata(
673
                        $reflectionProperty,
674
                        $joinColumnAnnot,
675
                        $classMetadata
676
                    );
677
678
                    $assocMetadata->addJoinColumn($joinColumn);
679
                }
680
681
                break;
682
        }
683
684
        return $assocMetadata;
685
    }
686
687
    /**
688
     * @param Annotation\Annotation[] $propertyAnnotations
689
     *
690
     * @return Mapping\ManyToOneAssociationMetadata
691
     */
692
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
693
        ReflectionProperty $reflectionProperty,
694
        array $propertyAnnotations,
695
        Mapping\ClassMetadata $classMetadata
696
    ) {
697
        $className      = $classMetadata->getClassName();
698
        $fieldName      = $reflectionProperty->getName();
699
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
700
701
        if ($manyToOneAnnot->targetEntity === null) {
702
            throw Mapping\MappingException::missingTargetEntity($fieldName);
703
        }
704
705
        $assocMetadata = new Mapping\ManyToOneAssociationMetadata($fieldName);
706
        $targetEntity  = $manyToOneAnnot->targetEntity;
707
708
        $assocMetadata->setSourceEntity($className);
709
        $assocMetadata->setTargetEntity($targetEntity);
710
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade));
711
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch));
712
713
        if (! empty($manyToOneAnnot->inversedBy)) {
714
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
715
        }
716
717
        // Check for Id
718
        if (isset($propertyAnnotations[Annotation\Id::class])) {
719
            $assocMetadata->setPrimaryKey(true);
720
        }
721
722
        // Check for Cache
723
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
724
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
725
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $classMetadata, $fieldName);
726
727
            $assocMetadata->setCache($cacheMetadata);
728
        }
729
730
        // Check for JoinColumn/JoinColumns annotations
731
        switch (true) {
732
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
733
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
734
                $joinColumn      = $this->convertJoinColumnAnnotationToJoinColumnMetadata(
735
                    $reflectionProperty,
736
                    $joinColumnAnnot,
737
                    $classMetadata
738
                );
739
740
                $assocMetadata->addJoinColumn($joinColumn);
741
742
                break;
743
744
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
745
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
746
747
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
748
                    $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata(
749
                        $reflectionProperty,
750
                        $joinColumnAnnot,
751
                        $classMetadata
752
                    );
753
754
                    $assocMetadata->addJoinColumn($joinColumn);
755
                }
756
757
                break;
758
        }
759
760
        return $assocMetadata;
761
    }
762
763
    /**
764
     * @param Annotation\Annotation[] $propertyAnnotations
765
     *
766
     * @return Mapping\OneToManyAssociationMetadata
767
     */
768
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
769
        ReflectionProperty $reflectionProperty,
770
        array $propertyAnnotations,
771
        Mapping\ClassMetadata $classMetadata
772
    ) {
773
        $className      = $classMetadata->getClassName();
774
        $fieldName      = $reflectionProperty->getName();
775
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
776
777
        if ($oneToManyAnnot->targetEntity === null) {
778
            throw Mapping\MappingException::missingTargetEntity($fieldName);
779
        }
780
781
        $assocMetadata = new Mapping\OneToManyAssociationMetadata($fieldName);
782
        $targetEntity  = $oneToManyAnnot->targetEntity;
783
784
        $assocMetadata->setSourceEntity($className);
785
        $assocMetadata->setTargetEntity($targetEntity);
786
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
787
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
788
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));
789
790
        if (! empty($oneToManyAnnot->mappedBy)) {
791
            $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
792
        }
793
794
        if (! empty($oneToManyAnnot->indexBy)) {
795
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
796
        }
797
798
        // Check for OrderBy
799
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
800
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
801
802
            $assocMetadata->setOrderBy($orderByAnnot->value);
803
        }
804
805
        // Check for Id
806
        if (isset($propertyAnnotations[Annotation\Id::class])) {
807
            $assocMetadata->setPrimaryKey(true);
808
        }
809
810
        // Check for Cache
811
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
812
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
813
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $classMetadata, $fieldName);
814
815
            $assocMetadata->setCache($cacheMetadata);
816
        }
817
818
        return $assocMetadata;
819
    }
820
821
    /**
822
     * @param Annotation\Annotation[] $propertyAnnotations
823
     *
824
     * @return Mapping\ManyToManyAssociationMetadata
825
     */
826
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
827
        ReflectionProperty $reflectionProperty,
828
        array $propertyAnnotations,
829
        Mapping\ClassMetadata $classMetadata
830
    ) {
831
        $className       = $classMetadata->getClassName();
832
        $fieldName       = $reflectionProperty->getName();
833
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
834
835
        if ($manyToManyAnnot->targetEntity === null) {
836
            throw Mapping\MappingException::missingTargetEntity($fieldName);
837
        }
838
839
        $assocMetadata = new Mapping\ManyToManyAssociationMetadata($fieldName);
840
        $targetEntity  = $manyToManyAnnot->targetEntity;
841
842
        $assocMetadata->setSourceEntity($className);
843
        $assocMetadata->setTargetEntity($targetEntity);
844
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade));
845
        $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval);
846
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch));
847
848
        if (! empty($manyToManyAnnot->mappedBy)) {
849
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
850
        }
851
852
        if (! empty($manyToManyAnnot->inversedBy)) {
853
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
854
        }
855
856
        if (! empty($manyToManyAnnot->indexBy)) {
857
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
858
        }
859
860
        // Check for JoinTable
861
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
862
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
863
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata(
864
                $reflectionProperty,
865
                $joinTableAnnot,
866
                $classMetadata
867
            );
868
869
            $assocMetadata->setJoinTable($joinTableMetadata);
870
        }
871
872
        // Check for OrderBy
873
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
874
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
875
876
            $assocMetadata->setOrderBy($orderByAnnot->value);
877
        }
878
879
        // Check for Id
880
        if (isset($propertyAnnotations[Annotation\Id::class])) {
881
            $assocMetadata->setPrimaryKey(true);
882
        }
883
884
        // Check for Cache
885
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
886
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
887
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $classMetadata, $fieldName);
888
889
            $assocMetadata->setCache($cacheMetadata);
890
        }
891
892
        return $assocMetadata;
893
    }
894
895
    /**
896
     * Parse the given JoinTable as JoinTableMetadata
897
     *
898
     * @return Mapping\JoinTableMetadata
899
     */
900
    private function convertJoinTableAnnotationToJoinTableMetadata(
901
        ReflectionProperty $reflectionProperty,
902
        Annotation\JoinTable $joinTableAnnot,
903
        Mapping\ClassMetadata $classMetadata
904
    ) {
905
        $joinTable = new Mapping\JoinTableMetadata();
906
907
        if (! empty($joinTableAnnot->name)) {
908
            $joinTable->setName($joinTableAnnot->name);
909
        }
910
911
        if (! empty($joinTableAnnot->schema)) {
912
            $joinTable->setSchema($joinTableAnnot->schema);
913
        }
914
915
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
916
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata(
917
                $reflectionProperty,
918
                $joinColumnAnnot,
919
                $classMetadata
920
            );
921
922
            $joinTable->addJoinColumn($joinColumn);
923
        }
924
925
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
926
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata(
927
                $reflectionProperty,
928
                $joinColumnAnnot,
929
                $classMetadata
930
            );
931
932
            $joinTable->addInverseJoinColumn($joinColumn);
933
        }
934
935
        return $joinTable;
936
    }
937
938
    /**
939
     * Parse the given JoinColumn as JoinColumnMetadata
940
     *
941
     * @return Mapping\JoinColumnMetadata
942
     */
943
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
944
        ReflectionProperty $reflectionProperty,
945
        Annotation\JoinColumn $joinColumnAnnot,
946
        Mapping\ClassMetadata $classMetadata
947
    ) {
948
        $fieldName            = $reflectionProperty->getName();
949
        $joinColumn           = new Mapping\JoinColumnMetadata();
950
        $columnName           = empty($joinColumnAnnot->name)
951
            ? $this->namingStrategy->propertyToColumnName($fieldName, $classMetadata->getClassName())
952
            : $joinColumnAnnot->name;
953
        $referencedColumnName = empty($joinColumnAnnot->referencedColumnName)
954
            ? $this->namingStrategy->referenceColumnName()
955
            : $joinColumnAnnot->referencedColumnName;
956
957
        $joinColumn->setColumnName($columnName);
958
        $joinColumn->setReferencedColumnName($referencedColumnName);
959
        $joinColumn->setNullable($joinColumnAnnot->nullable);
960
        $joinColumn->setUnique($joinColumnAnnot->unique);
961
962
        if (! empty($joinColumnAnnot->fieldName)) {
963
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
964
        }
965
966
        if (! empty($joinColumnAnnot->columnDefinition)) {
967
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
968
        }
969
970
        if ($joinColumnAnnot->onDelete) {
971
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
972
        }
973
974
        return $joinColumn;
975
    }
976
977
    /**
978
     * Parses the given method.
979
     *
980
     * @return string[]
981
     */
982
    private function getMethodCallbacks(ReflectionMethod $method)
983
    {
984
        $annotations = $this->getMethodAnnotations($method);
985
        $events      = [
986
            Events::prePersist  => Annotation\PrePersist::class,
987
            Events::postPersist => Annotation\PostPersist::class,
988
            Events::preUpdate   => Annotation\PreUpdate::class,
989
            Events::postUpdate  => Annotation\PostUpdate::class,
990
            Events::preRemove   => Annotation\PreRemove::class,
991
            Events::postRemove  => Annotation\PostRemove::class,
992
            Events::postLoad    => Annotation\PostLoad::class,
993
            Events::preFlush    => Annotation\PreFlush::class,
994
        ];
995
996
        // Check for callbacks
997
        $callbacks = [];
998
999
        foreach ($events as $eventName => $annotationClassName) {
1000
            if (isset($annotations[$annotationClassName]) || $method->getName() === $eventName) {
1001
                $callbacks[] = $eventName;
1002
            }
1003
        }
1004
1005
        return $callbacks;
1006
    }
1007
1008
    /**
1009
     * Attempts to resolve the fetch mode.
1010
     *
1011
     * @param string $className The class name.
1012
     * @param string $fetchMode The fetch mode.
1013
     *
1014
     * @return int The fetch mode as defined in ClassMetadata.
1015
     *
1016
     * @throws Mapping\MappingException If the fetch mode is not valid.
1017
     */
1018
    private function getFetchMode($className, $fetchMode)
1019
    {
1020
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1021
1022
        if (! defined($fetchModeConstant)) {
1023
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1024
        }
1025
1026
        return constant($fetchModeConstant);
1027
    }
1028
1029
    /**
1030
     * @param string   $className        The class name.
1031
     * @param string   $fieldName        The field name.
1032
     * @param string[] $originalCascades The original unprocessed field cascades.
1033
     *
1034
     * @return string[] The processed field cascades.
1035
     *
1036
     * @throws Mapping\MappingException If a cascade option is not valid.
1037
     */
1038
    private function getCascade(string $className, string $fieldName, array $originalCascades)
1039
    {
1040
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1041
        $cascades     = array_map('strtolower', $originalCascades);
1042
1043
        if (in_array('all', $cascades, true)) {
1044
            $cascades = $cascadeTypes;
1045
        }
1046
1047
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1048
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1049
1050
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1051
        }
1052
1053
        return $cascades;
1054
    }
1055
1056
    /**
1057
     * @return Annotation\Annotation[]
1058
     */
1059
    private function getClassAnnotations(ReflectionClass $reflectionClass)
1060
    {
1061
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1062
1063
        foreach ($classAnnotations as $key => $annot) {
1064
            if (! is_numeric($key)) {
1065
                continue;
1066
            }
1067
1068
            $classAnnotations[get_class($annot)] = $annot;
1069
        }
1070
1071
        return $classAnnotations;
1072
    }
1073
1074
    /**
1075
     * @return Annotation\Annotation[]
1076
     */
1077
    private function getPropertyAnnotations(ReflectionProperty $reflectionProperty)
1078
    {
1079
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1080
1081
        foreach ($propertyAnnotations as $key => $annot) {
1082
            if (! is_numeric($key)) {
1083
                continue;
1084
            }
1085
1086
            $propertyAnnotations[get_class($annot)] = $annot;
1087
        }
1088
1089
        return $propertyAnnotations;
1090
    }
1091
1092
    /**
1093
     * @return Annotation\Annotation[]
1094
     */
1095
    private function getMethodAnnotations(ReflectionMethod $reflectionMethod)
1096
    {
1097
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1098
1099
        foreach ($methodAnnotations as $key => $annot) {
1100
            if (! is_numeric($key)) {
1101
                continue;
1102
            }
1103
1104
            $methodAnnotations[get_class($annot)] = $annot;
1105
        }
1106
1107
        return $methodAnnotations;
1108
    }
1109
}
1110