Passed
Push — master ( 89e39b...ec508a )
by Marco
11:02
created

ORM/Mapping/Driver/NewAnnotationDriver.php (2 issues)

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

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
405
    {
406
        $entities = [];
407
408
        foreach ($resultSetMapping->entities as $entityResultAnnot) {
409
            $entityResult = [
410
                'fields'                => [],
411
                'entityClass'           => $entityResultAnnot->entityClass,
412
                'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
413
            ];
414
415
            foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
416
                $entityResult['fields'][] = [
417
                    'name'      => $fieldResultAnnot->name,
418
                    'column'    => $fieldResultAnnot->column,
419
                ];
420
            }
421
422
            $entities[] = $entityResult;
423
        }
424
425
        $columns = [];
426
427
        foreach ($resultSetMapping->columns as $columnResultAnnot) {
428
            $columns[] = [
429
                'name' => $columnResultAnnot->name,
430
            ];
431
        }
432
433
        return [
434
            'name'     => $resultSetMapping->name,
435
            'entities' => $entities,
436
            'columns'  => $columns,
437
        ];
438
    }
439
440
    /**
441
     * Parse the given Cache as CacheMetadata
442
     *
443
     * @param string|null $fieldName
444
     *
445
     * @return Mapping\CacheMetadata
446
     */
447
    private function convertCacheAnnotationToCacheMetadata(
448
        Annotation\Cache $cacheAnnot,
449
        Mapping\ClassMetadata $metadata,
450
        $fieldName = null
451
    ) {
452
        $usage         = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
453
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
454
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
455
456
        return new Mapping\CacheMetadata($usage, $cacheAnnot->region ?: $defaultRegion);
457
    }
458
459
    /**
460
     * @param Annotation\Annotation[] $propertyAnnotations
461
     *
462
     * @return Mapping\Property
463
     *
464
     * @throws Mapping\MappingException
465
     */
466
    private function convertReflectionPropertyAnnotationsToProperty(
467
        \ReflectionProperty $reflectionProperty,
468
        array $propertyAnnotations,
469
        Mapping\ClassMetadata $classMetadata
470
    ) {
471
        // Field can only be annotated with one of:
472
        // @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany, @Embedded
473
        switch (true) {
474
            case isset($propertyAnnotations[Annotation\Column::class]):
475
                return $this->convertReflectionPropertyToFieldMetadata(
476
                    $reflectionProperty,
477
                    $propertyAnnotations,
478
                    $classMetadata
479
                );
480
481
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
482
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
483
                    $reflectionProperty,
484
                    $propertyAnnotations,
485
                    $classMetadata
486
                );
487
488
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
489
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
490
                    $reflectionProperty,
491
                    $propertyAnnotations,
492
                    $classMetadata
493
                );
494
495
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
496
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
497
                    $reflectionProperty,
498
                    $propertyAnnotations,
499
                    $classMetadata
500
                );
501
502
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
503
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
504
                    $reflectionProperty,
505
                    $propertyAnnotations,
506
                    $classMetadata
507
                );
508
509
            case isset($propertyAnnotations[Annotation\Embedded::class]):
510
                // @todo guilhermeblanco Implement later... =)
511
                break;
512
513
            default:
514
                return new Mapping\TransientMetadata($reflectionProperty->getName());
515
        }
516
    }
517
518
    /**
519
     * @param Annotation\Annotation[] $propertyAnnotations
520
     *
521
     * @return Mapping\FieldMetadata
522
     *
523
     * @throws Mapping\MappingException
524
     */
525
    private function convertReflectionPropertyToFieldMetadata(
526
        \ReflectionProperty $reflectionProperty,
527
        array $propertyAnnotations,
528
        Mapping\ClassMetadata $classMetadata
529
    ) {
530
        $className    = $classMetadata->getClassName();
531
        $fieldName    = $reflectionProperty->getName();
532
        $columnAnnot  = $propertyAnnotations[Annotation\Column::class];
533
        $isVersioned  = isset($propertyAnnotations[Annotation\Version::class]);
534
        $isPrimaryKey = isset($propertyAnnotations[Annotation\Id::class]);
535
536
        if ($columnAnnot->type === null) {
537
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
538
        }
539
540
        if ($isVersioned && $isPrimaryKey) {
541
            throw Mapping\MappingException::cannotVersionIdField($className, $fieldName);
542
        }
543
544
        $columnName = empty($columnAnnot->name)
545
            ? $this->namingStrategy->propertyToColumnName($fieldName, $className)
546
            : $columnAnnot->name
547
        ;
548
549
        $fieldMetadata = $isVersioned
550
            ? new Mapping\VersionFieldMetadata($fieldName)
551
            : new Mapping\FieldMetadata($fieldName)
552
        ;
553
554
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
555
        $fieldMetadata->setColumnName($columnName);
556
        $fieldMetadata->setScale($columnAnnot->scale);
557
        $fieldMetadata->setPrecision($columnAnnot->precision);
558
        $fieldMetadata->setNullable($columnAnnot->nullable);
559
        $fieldMetadata->setUnique($columnAnnot->unique);
560
561
        // Check for Id
562
        if ($isPrimaryKey) {
563
            if ($fieldMetadata->getType()->canRequireSQLConversion()) {
564
                throw Mapping\MappingException::sqlConversionNotAllowedForPrimaryKeyProperties($className, $fieldMetadata);
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
        ;
954
        $referencedColumnName = empty($joinColumnAnnot->referencedColumnName)
955
            ? $this->namingStrategy->referenceColumnName()
956
            : $joinColumnAnnot->referencedColumnName
957
        ;
958
959
        $joinColumn->setColumnName($columnName);
960
        $joinColumn->setReferencedColumnName($referencedColumnName);
961
        $joinColumn->setNullable($joinColumnAnnot->nullable);
962
        $joinColumn->setUnique($joinColumnAnnot->unique);
963
964
        if (! empty($joinColumnAnnot->fieldName)) {
965
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
966
        }
967
968
        if (! empty($joinColumnAnnot->columnDefinition)) {
969
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
970
        }
971
972
        if ($joinColumnAnnot->onDelete) {
973
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
974
        }
975
976
        return $joinColumn;
977
    }
978
979
    /**
980
     * Parses the given method.
981
     *
982
     * @return string[]
983
     */
984
    private function getMethodCallbacks(\ReflectionMethod $method)
985
    {
986
        $annotations = $this->getMethodAnnotations($method);
987
        $events      = [
988
            Events::prePersist  => Annotation\PrePersist::class,
989
            Events::postPersist => Annotation\PostPersist::class,
990
            Events::preUpdate   => Annotation\PreUpdate::class,
991
            Events::postUpdate  => Annotation\PostUpdate::class,
992
            Events::preRemove   => Annotation\PreRemove::class,
993
            Events::postRemove  => Annotation\PostRemove::class,
994
            Events::postLoad    => Annotation\PostLoad::class,
995
            Events::preFlush    => Annotation\PreFlush::class,
996
        ];
997
998
        // Check for callbacks
999
        $callbacks = [];
1000
1001
        foreach ($events as $eventName => $annotationClassName) {
1002
            if (isset($annotations[$annotationClassName]) || $method->getName() === $eventName) {
1003
                $callbacks[] = $eventName;
1004
            }
1005
        }
1006
1007
        return $callbacks;
1008
    }
1009
1010
    /**
1011
     * Attempts to resolve the fetch mode.
1012
     *
1013
     * @param string $className The class name.
1014
     * @param string $fetchMode The fetch mode.
1015
     *
1016
     * @return int The fetch mode as defined in ClassMetadata.
1017
     *
1018
     * @throws Mapping\MappingException If the fetch mode is not valid.
1019
     */
1020
    private function getFetchMode($className, $fetchMode)
1021
    {
1022
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1023
1024
        if (! defined($fetchModeConstant)) {
1025
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1026
        }
1027
1028
        return constant($fetchModeConstant);
1029
    }
1030
1031
    /**
1032
     * @param string   $className        The class name.
1033
     * @param string   $fieldName        The field name.
1034
     * @param string[] $originalCascades The original unprocessed field cascades.
1035
     *
1036
     * @return string[] The processed field cascades.
1037
     *
1038
     * @throws Mapping\MappingException If a cascade option is not valid.
1039
     */
1040
    private function getCascade(string $className, string $fieldName, array $originalCascades)
1041
    {
1042
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1043
        $cascades     = array_map('strtolower', $originalCascades);
1044
1045
        if (in_array('all', $cascades, true)) {
1046
            $cascades = $cascadeTypes;
1047
        }
1048
1049
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1050
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1051
1052
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1053
        }
1054
1055
        return $cascades;
1056
    }
1057
1058
    /**
1059
     * @return Annotation\Annotation[]
1060
     */
1061
    private function getClassAnnotations(\ReflectionClass $reflectionClass)
1062
    {
1063
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1064
1065
        foreach ($classAnnotations as $key => $annot) {
1066
            if (! is_numeric($key)) {
1067
                continue;
1068
            }
1069
1070
            $classAnnotations[get_class($annot)] = $annot;
1071
        }
1072
1073
        return $classAnnotations;
1074
    }
1075
1076
    /**
1077
     * @return Annotation\Annotation[]
1078
     */
1079
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty)
1080
    {
1081
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1082
1083
        foreach ($propertyAnnotations as $key => $annot) {
1084
            if (! is_numeric($key)) {
1085
                continue;
1086
            }
1087
1088
            $propertyAnnotations[get_class($annot)] = $annot;
1089
        }
1090
1091
        return $propertyAnnotations;
1092
    }
1093
1094
    /**
1095
     * @return Annotation\Annotation[]
1096
     */
1097
    private function getMethodAnnotations(\ReflectionMethod $reflectionMethod)
1098
    {
1099
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1100
1101
        foreach ($methodAnnotations as $key => $annot) {
1102
            if (! is_numeric($key)) {
1103
                continue;
1104
            }
1105
1106
            $methodAnnotations[get_class($annot)] = $annot;
1107
        }
1108
1109
        return $methodAnnotations;
1110
    }
1111
}
1112