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

Doctrine/ORM/Mapping/Driver/AnnotationDriver.php (7 issues)

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

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

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

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

Loading history...
357
358 29
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
359 23
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
360 23
                    $classAnnotations,
361 23
                    $reflectionClass,
362 23
                    $metadata
363
                );
364 6
            case isset($classAnnotations[Annotation\Embeddable::class]):
365
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
366
                    $classAnnotations,
367
                    $reflectionClass,
368
                    $metadata
369
                );
370
            default:
371 6
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
372
        }
373
    }
374
375
    /**
376
     * @param Annotation\Annotation[] $classAnnotations
377
     *
378
     * @return Mapping\ClassMetadata
379
     *
380
     * @throws Mapping\MappingException
381
     * @throws ReflectionException
382
     * @throws UnexpectedValueException
383
     */
384 372
    private function convertClassAnnotationsToEntityClassMetadata(
385
        array $classAnnotations,
386
        ReflectionClass $reflectionClass,
387
        Mapping\ClassMetadata $metadata,
388
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
389
    ) {
390
        /** @var Annotation\Entity $entityAnnot */
391 372
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
392
393 372
        if ($entityAnnot->repositoryClass !== null) {
394 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
395
        }
396
397 372
        if ($entityAnnot->readOnly) {
398 1
            $metadata->asReadOnly();
399
        }
400
401 372
        $metadata->isMappedSuperclass = false;
402 372
        $metadata->isEmbeddedClass    = false;
403
404
        // Process table information
405 372
        $parent = $metadata->getParent();
406
407 372
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
408
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
409
            do {
410 29
                if (! $parent->isMappedSuperclass) {
411 29
                    $metadata->setTable($parent->table);
412
413 29
                    break;
414
                }
415
416 4
                $parent = $parent->getParent();
417 29
            } while ($parent !== null);
418
        } else {
419 372
            $tableBuilder = new Builder\TableMetadataBuilder($metadataBuildingContext);
420
421
            $tableBuilder
422 372
                ->withEntityClassMetadata($metadata)
423 372
                ->withTableAnnotation($classAnnotations[Annotation\Table::class] ?? null);
424
425 372
            $metadata->setTable($tableBuilder->build());
426
        }
427
428
        // Evaluate @ChangeTrackingPolicy annotation
429 372
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
430 6
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
431
432 6
            $metadata->setChangeTrackingPolicy(
433 6
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
434
            );
435
        }
436
437
        // Evaluate @InheritanceType annotation
438 372
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
439 80
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
440
441 80
            $metadata->setInheritanceType(
442 80
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
443
            );
444
445 80
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
446 80
                $discriminatorColumnBuilder = new Builder\DiscriminatorColumnMetadataBuilder($metadataBuildingContext);
447
448
                $discriminatorColumnBuilder
449 80
                    ->withComponentMetadata($metadata)
450 80
                    ->withDiscriminatorColumnAnnotation($classAnnotations[Annotation\DiscriminatorColumn::class] ?? null);
451
452 80
                $metadata->setDiscriminatorColumn($discriminatorColumnBuilder->build());
453
454
                // Evaluate DiscriminatorMap annotation
455 80
                if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
456 77
                    $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
457 77
                    $discriminatorMap           = $discriminatorMapAnnotation->value;
458
459 77
                    $metadata->setDiscriminatorMap($discriminatorMap);
460
                }
461
            }
462
        }
463
464 372
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
465 372
        $this->attachEntityListeners($classAnnotations, $metadata);
466
467 372
        return $metadata;
468
    }
469
470
    /**
471
     * @param Annotation\Annotation[] $classAnnotations
472
     *
473
     * @throws Mapping\MappingException
474
     * @throws ReflectionException
475
     */
476 23
    private function convertClassAnnotationsToMappedSuperClassMetadata(
477
        array $classAnnotations,
478
        ReflectionClass $reflectionClass,
479
        Mapping\ClassMetadata $metadata
480
    ) : Mapping\ClassMetadata {
481
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
482 23
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
483
484 23
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
485 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
486
        }
487
488 23
        $metadata->isMappedSuperclass = true;
489 23
        $metadata->isEmbeddedClass    = false;
490
491 23
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
492 23
        $this->attachEntityListeners($classAnnotations, $metadata);
493
494 23
        return $metadata;
495
    }
496
497
    /**
498
     * @param Annotation\Annotation[] $classAnnotations
499
     */
500
    private function convertClassAnnotationsToEmbeddableClassMetadata(
501
        array $classAnnotations,
0 ignored issues
show
The parameter $classAnnotations is not used and could be removed. ( Ignorable by Annotation )

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

501
        /** @scrutinizer ignore-unused */ array $classAnnotations,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
502
        ReflectionClass $reflectionClass,
0 ignored issues
show
The parameter $reflectionClass is not used and could be removed. ( Ignorable by Annotation )

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

502
        /** @scrutinizer ignore-unused */ ReflectionClass $reflectionClass,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
503
        Mapping\ClassMetadata $metadata
504
    ) : Mapping\ClassMetadata {
505
        $metadata->isMappedSuperclass = false;
506
        $metadata->isEmbeddedClass    = true;
507
508
        return $metadata;
509
    }
510
511
    /**
512
     * @param Annotation\Annotation[] $propertyAnnotations
513
     *
514
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
515
     */
516 372
    private function convertPropertyAnnotationsToProperty(
517
        array $propertyAnnotations,
518
        ReflectionProperty $reflectionProperty,
519
        Mapping\ClassMetadata $metadata,
520
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
521
    ) : ?Mapping\Property {
522
        switch (true) {
523 372
            case isset($propertyAnnotations[Annotation\Column::class]):
524 367
                $fieldBuilder  = new Builder\FieldMetadataBuilder($metadataBuildingContext);
525
                $fieldMetadata = $fieldBuilder
526 367
                    ->withComponentMetadata($metadata)
527 367
                    ->withFieldName($reflectionProperty->getName())
528 367
                    ->withColumnAnnotation($propertyAnnotations[Annotation\Column::class])
529 367
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
530 367
                    ->withVersionAnnotation($propertyAnnotations[Annotation\Version::class] ?? null)
531 367
                    ->withGeneratedValueAnnotation($propertyAnnotations[Annotation\GeneratedValue::class] ?? null)
532 367
                    ->withSequenceGeneratorAnnotation($propertyAnnotations[Annotation\SequenceGenerator::class] ?? null)
533 367
                    ->withCustomIdGeneratorAnnotation($propertyAnnotations[Annotation\CustomIdGenerator::class] ?? null)
534 367
                    ->build();
535
536
                // Prevent column duplication
537 367
                $columnName = $fieldMetadata->getColumnName();
538
539 367
                if ($metadata->checkPropertyDuplication($columnName)) {
540
                    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
541
                }
542
543 367
                $metadata->fieldNames[$fieldMetadata->getColumnName()] = $fieldMetadata->getName();
544
545 367
                return $fieldMetadata;
546 254
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
547 111
                $oneToOneAssociationBuilder = new Builder\OneToOneAssociationMetadataBuilder($metadataBuildingContext);
548
                $associationMetadata        = $oneToOneAssociationBuilder
549 111
                    ->withComponentMetadata($metadata)
550 111
                    ->withFieldName($reflectionProperty->getName())
551 111
                    ->withOneToOneAnnotation($propertyAnnotations[Annotation\OneToOne::class] ?? null)
0 ignored issues
show
It seems like $propertyAnnotations[Doc...neToOne::class] ?? null can also be of type null; however, parameter $oneToOneAnnotation of Doctrine\ORM\Mapping\Bui...ithOneToOneAnnotation() does only seem to accept Doctrine\ORM\Annotation\OneToOne, maybe add an additional type check? ( Ignorable by Annotation )

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

551
                    ->withOneToOneAnnotation(/** @scrutinizer ignore-type */ $propertyAnnotations[Annotation\OneToOne::class] ?? null)
Loading history...
552 111
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
553 111
                    ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null)
554 111
                    ->withJoinColumnsAnnotation($propertyAnnotations[Annotation\JoinColumns::class] ?? null)
555 111
                    ->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class] ?? null)
556 111
                    ->build();
557
558
                // Prevent column duplication
559 111
                foreach ($associationMetadata->getJoinColumns() as $joinColumnMetadata) {
560 105
                    $columnName = $joinColumnMetadata->getColumnName();
561
562
                    // @todo guilhermeblanco Open an issue to discuss making this scenario impossible.
563
                    //if ($metadata->checkPropertyDuplication($columnName)) {
564
                    //    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
565
                    //}
566
567 105
                    if ($associationMetadata->isOwningSide()) {
568 105
                        $metadata->fieldNames[$columnName] = $associationMetadata->getName();
569
                    }
570
                }
571
572 111
                return $associationMetadata;
573 202
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
574 141
                $manyToOneAssociationBuilder = new Builder\ManyToOneAssociationMetadataBuilder($metadataBuildingContext);
575
                $associationMetadata         = $manyToOneAssociationBuilder
576 141
                    ->withComponentMetadata($metadata)
577 141
                    ->withFieldName($reflectionProperty->getName())
578 141
                    ->withManyToOneAnnotation($propertyAnnotations[Annotation\ManyToOne::class] ?? null)
0 ignored issues
show
It seems like $propertyAnnotations[Doc...nyToOne::class] ?? null can also be of type null; however, parameter $manyToOneAnnotation of Doctrine\ORM\Mapping\Bui...thManyToOneAnnotation() does only seem to accept Doctrine\ORM\Annotation\ManyToOne, maybe add an additional type check? ( Ignorable by Annotation )

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

578
                    ->withManyToOneAnnotation(/** @scrutinizer ignore-type */ $propertyAnnotations[Annotation\ManyToOne::class] ?? null)
Loading history...
579 141
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
580 141
                    ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null)
581 141
                    ->withJoinColumnsAnnotation($propertyAnnotations[Annotation\JoinColumns::class] ?? null)
582 141
                    ->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class] ?? null)
583 141
                    ->build();
584
585
                // Prevent column duplication
586 140
                foreach ($associationMetadata->getJoinColumns() as $joinColumnMetadata) {
587 140
                    $columnName = $joinColumnMetadata->getColumnName();
588
589
                    // @todo guilhermeblanco Open an issue to discuss making this scenario impossible.
590
                    //if ($metadata->checkPropertyDuplication($columnName)) {
591
                    //    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
592
                    //}
593
594 140
                    if ($associationMetadata->isOwningSide()) {
595 140
                        $metadata->fieldNames[$columnName] = $associationMetadata->getName();
596
                    }
597
                }
598
599 140
                return $associationMetadata;
600 163
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
601 109
                $oneToManyAssociationBuilder = new Builder\OneToManyAssociationMetadataBuilder($metadataBuildingContext);
602
603
                return $oneToManyAssociationBuilder
604 109
                    ->withComponentMetadata($metadata)
605 109
                    ->withFieldName($reflectionProperty->getName())
606 109
                    ->withOneToManyAnnotation($propertyAnnotations[Annotation\OneToMany::class] ?? null)
0 ignored issues
show
It seems like $propertyAnnotations[Doc...eToMany::class] ?? null can also be of type null; however, parameter $oneToManyAnnotation of Doctrine\ORM\Mapping\Bui...thOneToManyAnnotation() does only seem to accept Doctrine\ORM\Annotation\OneToMany, maybe add an additional type check? ( Ignorable by Annotation )

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

606
                    ->withOneToManyAnnotation(/** @scrutinizer ignore-type */ $propertyAnnotations[Annotation\OneToMany::class] ?? null)
Loading history...
607 109
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
608 109
                    ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null)
609 109
                    ->withOrderByAnnotation($propertyAnnotations[Annotation\OrderBy::class] ?? null)
610 109
                    ->build();
611 104
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
612 89
                $manyToManyAssociationBuilder = new Builder\ManyToManyAssociationMetadataBuilder($metadataBuildingContext);
613
614
                return $manyToManyAssociationBuilder
615 89
                    ->withComponentMetadata($metadata)
616 89
                    ->withFieldName($reflectionProperty->getName())
617 89
                    ->withManyToManyAnnotation($propertyAnnotations[Annotation\ManyToMany::class] ?? null)
0 ignored issues
show
It seems like $propertyAnnotations[Doc...yToMany::class] ?? null can also be of type null; however, parameter $manyToManyAnnotation of Doctrine\ORM\Mapping\Bui...hManyToManyAnnotation() does only seem to accept Doctrine\ORM\Annotation\ManyToMany, maybe add an additional type check? ( Ignorable by Annotation )

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

617
                    ->withManyToManyAnnotation(/** @scrutinizer ignore-type */ $propertyAnnotations[Annotation\ManyToMany::class] ?? null)
Loading history...
618 89
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
619 89
                    ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null)
620 89
                    ->withJoinTableAnnotation($propertyAnnotations[Annotation\JoinTable::class] ?? null)
621 89
                    ->withOrderByAnnotation($propertyAnnotations[Annotation\OrderBy::class] ?? null)
622 89
                    ->build();
623 29
            case isset($propertyAnnotations[Annotation\Embedded::class]):
624
                return null;
625
            default:
626 29
                $transientBuilder = new Builder\TransientMetadataBuilder($metadataBuildingContext);
627
628
                return $transientBuilder
629 29
                    ->withComponentMetadata($metadata)
630 29
                    ->withFieldName($reflectionProperty->getName())
631 29
                    ->build();
632
        }
633
    }
634
635
    /**
636
     * @param Annotation\Annotation[] $classAnnotations
637
     */
638 373
    private function attachLifecycleCallbacks(
639
        array $classAnnotations,
640
        ReflectionClass $reflectionClass,
641
        Mapping\ClassMetadata $metadata
642
    ) : void {
643
        // Evaluate @HasLifecycleCallbacks annotation
644 373
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
645
            $eventMap = [
646 14
                Events::prePersist  => Annotation\PrePersist::class,
647 14
                Events::postPersist => Annotation\PostPersist::class,
648 14
                Events::preUpdate   => Annotation\PreUpdate::class,
649 14
                Events::postUpdate  => Annotation\PostUpdate::class,
650 14
                Events::preRemove   => Annotation\PreRemove::class,
651 14
                Events::postRemove  => Annotation\PostRemove::class,
652 14
                Events::postLoad    => Annotation\PostLoad::class,
653 14
                Events::preFlush    => Annotation\PreFlush::class,
654
            ];
655
656
            /** @var ReflectionMethod $reflectionMethod */
657 14
            foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
658 13
                $annotations = $this->getMethodAnnotations($reflectionMethod);
659
660 13
                foreach ($eventMap as $eventName => $annotationClassName) {
661 13
                    if (isset($annotations[$annotationClassName])) {
662 12
                        $metadata->addLifecycleCallback($eventName, $reflectionMethod->getName());
663
                    }
664
                }
665
            }
666
        }
667 373
    }
668
669
    /**
670
     * @param Annotation\Annotation[] $classAnnotations
671
     *
672
     * @throws ReflectionException
673
     * @throws Mapping\MappingException
674
     */
675 373
    private function attachEntityListeners(
676
        array $classAnnotations,
677
        Mapping\ClassMetadata $metadata
678
    ) : void {
679
        // Evaluate @EntityListeners annotation
680 373
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
681
            /** @var Annotation\EntityListeners $entityListenersAnnot */
682 8
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
683
            $eventMap             = [
684 8
                Events::prePersist  => Annotation\PrePersist::class,
685 8
                Events::postPersist => Annotation\PostPersist::class,
686 8
                Events::preUpdate   => Annotation\PreUpdate::class,
687 8
                Events::postUpdate  => Annotation\PostUpdate::class,
688 8
                Events::preRemove   => Annotation\PreRemove::class,
689 8
                Events::postRemove  => Annotation\PostRemove::class,
690 8
                Events::postLoad    => Annotation\PostLoad::class,
691 8
                Events::preFlush    => Annotation\PreFlush::class,
692
            ];
693
694 8
            foreach ($entityListenersAnnot->value as $listenerClassName) {
695 8
                if (! class_exists($listenerClassName)) {
696
                    throw Mapping\MappingException::entityListenerClassNotFound(
697
                        $listenerClassName,
698
                        $metadata->getClassName()
699
                    );
700
                }
701
702 8
                $listenerClass = new ReflectionClass($listenerClassName);
703
704
                /** @var ReflectionMethod $reflectionMethod */
705 8
                foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
706 8
                    $annotations = $this->getMethodAnnotations($reflectionMethod);
707
708 8
                    foreach ($eventMap as $eventName => $annotationClassName) {
709 8
                        if (isset($annotations[$annotationClassName])) {
710 6
                            $metadata->addEntityListener($eventName, $listenerClassName, $reflectionMethod->getName());
711
                        }
712
                    }
713
                }
714
            }
715
        }
716 373
    }
717
718
    /**
719
     * @param Annotation\Annotation[] $classAnnotations
720
     *
721
     * @throws Mapping\MappingException
722
     */
723 370
    private function attachPropertyOverrides(
724
        array $classAnnotations,
725
        ReflectionClass $reflectionClass,
726
        Mapping\ClassMetadata $metadata,
727
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
728
    ) : void {
729
        // Evaluate AssociationOverrides annotation
730 370
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
731 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
732
733 5
            foreach ($associationOverridesAnnot->value as $associationOverrideAnnotation) {
734 5
                $fieldName = $associationOverrideAnnotation->name;
735 5
                $property  = $metadata->getProperty($fieldName);
736
737 5
                if (! $property) {
738
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
739
                }
740
741 5
                $override = clone $property;
742
743
                // Check for JoinColumn/JoinColumns annotations
744 5
                if ($associationOverrideAnnotation->joinColumns) {
745 3
                    $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
746
747
                    $joinColumnBuilder
748 3
                        ->withComponentMetadata($metadata)
749 3
                        ->withFieldName($fieldName);
750
751 3
                    $joinColumns = [];
752
753 3
                    foreach ($associationOverrideAnnotation->joinColumns as $joinColumnAnnotation) {
754 3
                        $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
755
756 3
                        $joinColumnMetadata = $joinColumnBuilder->build();
757 3
                        $columnName         = $joinColumnMetadata->getColumnName();
758
759
                        // @todo guilhermeblanco Open an issue to discuss making this scenario impossible.
760
                        //if ($metadata->checkPropertyDuplication($columnName)) {
761
                        //    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
762
                        //}
763
764 3
                        if ($override->isOwningSide()) {
765 3
                            $metadata->fieldNames[$columnName] = $fieldName;
766
                        }
767
768 3
                        $joinColumns[] = $joinColumnMetadata;
769
                    }
770
771 3
                    $override->setJoinColumns($joinColumns);
772
                }
773
774
                // Check for JoinTable annotations
775 5
                if ($associationOverrideAnnotation->joinTable) {
776 2
                    $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
777
778
                    $joinTableBuilder
779 2
                        ->withComponentMetadata($metadata)
780 2
                        ->withFieldName($fieldName)
781 2
                        ->withTargetEntity($property->getTargetEntity())
782 2
                        ->withJoinTableAnnotation($associationOverrideAnnotation->joinTable);
783
784 2
                    $override->setJoinTable($joinTableBuilder->build());
785
                }
786
787
                // Check for inversedBy
788 5
                if ($associationOverrideAnnotation->inversedBy) {
789 1
                    $override->setInversedBy($associationOverrideAnnotation->inversedBy);
790
                }
791
792
                // Check for fetch
793 5
                if ($associationOverrideAnnotation->fetch) {
794 1
                    $override->setFetchMode(constant(Mapping\FetchMode::class . '::' . $associationOverrideAnnotation->fetch));
795
                }
796
797 5
                $metadata->setPropertyOverride($override);
798
            }
799
        }
800
801
        // Evaluate AttributeOverrides annotation
802 370
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
803 3
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
804 3
            $fieldBuilder            = new Builder\FieldMetadataBuilder($metadataBuildingContext);
805
806
            $fieldBuilder
807 3
                ->withComponentMetadata($metadata)
808 3
                ->withIdAnnotation(null)
809 3
                ->withVersionAnnotation(null);
810
811 3
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnotation) {
812 3
                $fieldName = $attributeOverrideAnnotation->name;
813 3
                $property  = $metadata->getProperty($fieldName);
814
815 3
                if (! $property) {
816
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
817
                }
818
819
                $fieldBuilder
820 3
                    ->withFieldName($fieldName)
821 3
                    ->withColumnAnnotation($attributeOverrideAnnotation->column);
822
823 3
                $fieldMetadata = $fieldBuilder->build();
824 3
                $columnName    = $fieldMetadata->getColumnName();
825
826
                // Prevent column duplication
827 3
                if ($metadata->checkPropertyDuplication($columnName)) {
828
                    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
829
                }
830
831 3
                $metadata->fieldNames[$fieldMetadata->getColumnName()] = $fieldName;
832
833 3
                $metadata->setPropertyOverride($fieldMetadata);
834
            }
835
        }
836 370
    }
837
838
    /**
839
     * @return Annotation\Annotation[]
840
     */
841 379
    private function getClassAnnotations(ReflectionClass $reflectionClass) : array
842
    {
843 379
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
844
845 379
        foreach ($classAnnotations as $key => $annot) {
846 373
            if (! is_numeric($key)) {
847
                continue;
848
            }
849
850 373
            $classAnnotations[get_class($annot)] = $annot;
851
        }
852
853 379
        return $classAnnotations;
854
    }
855
856
    /**
857
     * @return Annotation\Annotation[]
858
     */
859 373
    private function getPropertyAnnotations(ReflectionProperty $reflectionProperty) : array
860
    {
861 373
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
862
863 372
        foreach ($propertyAnnotations as $key => $annot) {
864 372
            if (! is_numeric($key)) {
865
                continue;
866
            }
867
868 372
            $propertyAnnotations[get_class($annot)] = $annot;
869
        }
870
871 372
        return $propertyAnnotations;
872
    }
873
874
    /**
875
     * @return Annotation\Annotation[]
876
     */
877 21
    private function getMethodAnnotations(ReflectionMethod $reflectionMethod) : array
878
    {
879 21
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
880
881 21
        foreach ($methodAnnotations as $key => $annot) {
882 18
            if (! is_numeric($key)) {
883
                continue;
884
            }
885
886 18
            $methodAnnotations[get_class($annot)] = $annot;
887
        }
888
889 21
        return $methodAnnotations;
890
    }
891
}
892