Failed Conditions
Push — master ( b07393...21bc80 )
by Guilherme
09:49
created

ORM/Mapping/Driver/NewAnnotationDriver.php (1 issue)

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);
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());
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)
0 ignored issues
show
The method getMethodCallbacks() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
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