Failed Conditions
Push — master ( fa7802...d60694 )
by Guilherme
09:27
created

convertReflectionPropertyToFieldMetadata()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 81
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2.0263

Importance

Changes 0
Metric Value
cc 2
eloc 16
c 0
b 0
f 0
nc 2
nop 4
dl 0
loc 81
ccs 13
cts 16
cp 0.8125
crap 2.0263
rs 9.7333

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\DBAL\Types\Type;
0 ignored issues
show
introduced by
Type Doctrine\DBAL\Types\Type is not used in this file.
Loading history...
10
use Doctrine\ORM\Annotation;
11
use Doctrine\ORM\Cache\Exception\CacheException;
12
use Doctrine\ORM\Events;
13
use Doctrine\ORM\Mapping;
14
use Doctrine\ORM\Mapping\Builder;
15
use FilesystemIterator;
16
use RecursiveDirectoryIterator;
17
use RecursiveIteratorIterator;
18
use RecursiveRegexIterator;
19
use ReflectionClass;
20
use ReflectionException;
21
use ReflectionMethod;
22
use ReflectionProperty;
23
use RegexIterator;
24
use RuntimeException;
25
use UnexpectedValueException;
26
use function array_diff;
27
use function array_intersect;
28
use function array_map;
29
use function array_merge;
30
use function array_unique;
31
use function class_exists;
32
use function constant;
33
use function count;
34
use function defined;
35
use function get_class;
36
use function get_declared_classes;
37
use function in_array;
38
use function is_dir;
39
use function is_numeric;
40
use function preg_match;
41
use function preg_quote;
42
use function realpath;
43
use function sprintf;
44
use function str_replace;
45
use function strpos;
46
use function strtoupper;
0 ignored issues
show
introduced by
Type strtoupper is not used in this file.
Loading history...
47
use function var_export;
0 ignored issues
show
introduced by
Type var_export is not used in this file.
Loading history...
48
49
/**
50
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
51
 */
52
class AnnotationDriver implements MappingDriver
53
{
54
    /** @var int[] */
55
    protected $entityAnnotationClasses = [
56
        Annotation\Entity::class           => 1,
57
        Annotation\MappedSuperclass::class => 2,
58
    ];
59
60
    /**
61
     * The AnnotationReader.
62
     *
63
     * @var AnnotationReader
64
     */
65
    protected $reader;
66
67
    /**
68
     * The paths where to look for mapping files.
69
     *
70
     * @var string[]
71
     */
72
    protected $paths = [];
73
74
    /**
75
     * The paths excluded from path where to look for mapping files.
76
     *
77
     * @var string[]
78
     */
79
    protected $excludePaths = [];
80
81
    /**
82
     * The file extension of mapping documents.
83
     *
84
     * @var string
85
     */
86
    protected $fileExtension = '.php';
87
88
    /**
89
     * Cache for AnnotationDriver#getAllClassNames().
90
     *
91
     * @var string[]|null
92
     */
93
    protected $classNames;
94
95
    /**
96
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
97
     * docblock annotations.
98
     *
99
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
100
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
101
     */
102 2297
    public function __construct(Reader $reader, $paths = null)
103
    {
104 2297
        $this->reader = $reader;
0 ignored issues
show
Documentation Bug introduced by
$reader is of type Doctrine\Common\Annotations\Reader, but the property $reader was declared to be of type Doctrine\Common\Annotations\AnnotationReader. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
105
106 2297
        if ($paths) {
107 2211
            $this->addPaths((array) $paths);
108
        }
109 2297
    }
110
111
    /**
112
     * Appends lookup paths to metadata driver.
113
     *
114
     * @param string[] $paths
115
     */
116 2215
    public function addPaths(array $paths)
117
    {
118 2215
        $this->paths = array_unique(array_merge($this->paths, $paths));
119 2215
    }
120
121
    /**
122
     * Retrieves the defined metadata lookup paths.
123
     *
124
     * @return string[]
125
     */
126
    public function getPaths()
127
    {
128
        return $this->paths;
129
    }
130
131
    /**
132
     * Append exclude lookup paths to metadata driver.
133
     *
134
     * @param string[] $paths
135
     */
136
    public function addExcludePaths(array $paths)
137
    {
138
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
139
    }
140
141
    /**
142
     * Retrieve the defined metadata lookup exclude paths.
143
     *
144
     * @return string[]
145
     */
146
    public function getExcludePaths()
147
    {
148
        return $this->excludePaths;
149
    }
150
151
    /**
152
     * Retrieve the current annotation reader
153
     *
154
     * @return Reader
155
     */
156 1
    public function getReader()
157
    {
158 1
        return $this->reader;
159
    }
160
161
    /**
162
     * Gets the file extension used to look for mapping files under.
163
     *
164
     * @return string
165
     */
166
    public function getFileExtension()
167
    {
168
        return $this->fileExtension;
169
    }
170
171
    /**
172
     * Sets the file extension used to look for mapping files under.
173
     *
174
     * @param string $fileExtension The file extension to set.
175
     */
176
    public function setFileExtension($fileExtension)
177
    {
178
        $this->fileExtension = $fileExtension;
179
    }
180
181
    /**
182
     * Returns whether the class with the specified name is transient. Only non-transient
183
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
184
     *
185
     * A class is non-transient if it is annotated with an annotation
186
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
187
     *
188
     * @param string $className
189
     *
190
     * @throws ReflectionException
191
     */
192 193
    public function isTransient($className) : bool
193
    {
194 193
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
195
196 193
        foreach ($classAnnotations as $annotation) {
197 188
            if (isset($this->entityAnnotationClasses[get_class($annotation)])) {
198 188
                return false;
199
            }
200
        }
201
202 12
        return true;
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     *
208
     * @throws ReflectionException
209
     */
210 60
    public function getAllClassNames() : array
211
    {
212 60
        if ($this->classNames !== null) {
213 45
            return $this->classNames;
214
        }
215
216 60
        if (! $this->paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
217
            throw Mapping\MappingException::pathRequired();
218
        }
219
220 60
        $classes       = [];
221 60
        $includedFiles = [];
222
223 60
        foreach ($this->paths as $path) {
224 60
            if (! is_dir($path)) {
225
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
226
            }
227
228 60
            $iterator = new RegexIterator(
229 60
                new RecursiveIteratorIterator(
230 60
                    new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
231 60
                    RecursiveIteratorIterator::LEAVES_ONLY
232
                ),
233 60
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
234 60
                RecursiveRegexIterator::GET_MATCH
235
            );
236
237 60
            foreach ($iterator as $file) {
238 60
                $sourceFile = $file[0];
239
240 60
                if (! preg_match('(^phar:)i', $sourceFile)) {
241 60
                    $sourceFile = realpath($sourceFile);
242
                }
243
244 60
                foreach ($this->excludePaths as $excludePath) {
245
                    $exclude = str_replace('\\', '/', realpath($excludePath));
246
                    $current = str_replace('\\', '/', $sourceFile);
247
248
                    if (strpos($current, $exclude) !== false) {
249
                        continue 2;
250
                    }
251
                }
252
253 60
                require_once $sourceFile;
254
255 60
                $includedFiles[] = $sourceFile;
256
            }
257
        }
258
259 60
        $declared = get_declared_classes();
260
261 60
        foreach ($declared as $className) {
262 60
            $reflectionClass = new ReflectionClass($className);
263 60
            $sourceFile      = $reflectionClass->getFileName();
264
265 60
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
266 60
                $classes[] = $className;
267
            }
268
        }
269
270 60
        $this->classNames = $classes;
271
272 60
        return $classes;
273
    }
274
275
    /**
276
     * {@inheritDoc}
277
     *
278
     * @throws CacheException
279
     * @throws Mapping\MappingException
280
     * @throws ReflectionException
281
     * @throws RuntimeException
282
     * @throws UnexpectedValueException
283
     */
284 379
    public function loadMetadataForClass(
285
        string $className,
286
        ?Mapping\ComponentMetadata $parent,
287
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
288
    ) : Mapping\ComponentMetadata {
289 379
        $reflectionClass  = new ReflectionClass($className);
290 379
        $metadata         = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
291 379
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
292 379
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
293 379
            $classAnnotations,
294 379
            $reflectionClass,
295 379
            $metadata,
296 379
            $metadataBuildingContext
297
        );
298
299
        // Evaluate @Cache annotation
300 373
        if (isset($classAnnotations[Annotation\Cache::class])) {
301 18
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
302
303
            $cacheBuilder
304 18
                ->withComponentMetadata($metadata)
305 18
                ->withCacheAnnotation($classAnnotations[Annotation\Cache::class]);
306
307 18
            $metadata->setCache($cacheBuilder->build());
308
        }
309
310
        // Evaluate annotations on properties/fields
311
        /** @var ReflectionProperty $reflProperty */
312 373
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
313 373
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
314 74
                continue;
315
            }
316
317 373
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
318 372
            $property            = $this->convertPropertyAnnotationsToProperty(
319 372
                $propertyAnnotations,
320 372
                $reflectionProperty,
321 372
                $classMetadata,
322 372
                $metadataBuildingContext
323
            );
324
325 372
            if ($classMetadata->isMappedSuperclass &&
326 372
                $property instanceof Mapping\ToManyAssociationMetadata &&
327 372
                ! $property->isOwningSide()) {
328 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
329 1
                    $classMetadata->getClassName(),
330 1
                    $property->getName()
331
                );
332
            }
333
334 371
            $metadata->addProperty($property);
335
        }
336
337 370
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
338
339 370
        return $classMetadata;
340
    }
341
342
    /**
343
     * @param Annotation\Annotation[] $classAnnotations
344
     *
345
     * @throws Mapping\MappingException
346
     * @throws UnexpectedValueException
347
     * @throws ReflectionException
348
     */
349 379
    private function convertClassAnnotationsToClassMetadata(
350
        array $classAnnotations,
351
        ReflectionClass $reflectionClass,
352
        Mapping\ClassMetadata $metadata,
353
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
354
    ) : Mapping\ClassMetadata {
355
        switch (true) {
356 379
            case isset($classAnnotations[Annotation\Entity::class]):
357 372
                return $this->convertClassAnnotationsToEntityClassMetadata(
358 372
                    $classAnnotations,
359 372
                    $reflectionClass,
360 372
                    $metadata,
361 372
                    $metadataBuildingContext
362
                );
363
364
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

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

509
        /** @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...
510
        ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
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

510
        /** @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...
511
        Mapping\ClassMetadata $metadata
512
    ) : Mapping\ClassMetadata {
513
        $metadata->isMappedSuperclass = false;
514
        $metadata->isEmbeddedClass    = true;
515
516
        return $metadata;
517
    }
518
519
    /**
520
     * @param Annotation\Annotation[] $propertyAnnotations
521
     *
522
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
523
     */
524 372
    private function convertPropertyAnnotationsToProperty(
525
        array $propertyAnnotations,
526
        ReflectionProperty $reflectionProperty,
527
        Mapping\ClassMetadata $metadata,
528
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
529
    ) : ?Mapping\Property {
530
        switch (true) {
531 372
            case isset($propertyAnnotations[Annotation\Column::class]):
532 367
                return $this->convertReflectionPropertyToFieldMetadata(
533 367
                    $reflectionProperty,
534 367
                    $propertyAnnotations,
535 367
                    $metadata,
536 367
                    $metadataBuildingContext
537
                );
538 254
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
539 111
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
540 111
                    $reflectionProperty,
541 111
                    $propertyAnnotations,
542 111
                    $metadata,
543 111
                    $metadataBuildingContext
544
                );
545 202
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
546 141
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
547 141
                    $reflectionProperty,
548 141
                    $propertyAnnotations,
549 141
                    $metadata,
550 141
                    $metadataBuildingContext
551
                );
552 163
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
553 109
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
554 109
                    $reflectionProperty,
555 109
                    $propertyAnnotations,
556 109
                    $metadata,
557 109
                    $metadataBuildingContext
558
                );
559 104
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
560 89
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
561 89
                    $reflectionProperty,
562 89
                    $propertyAnnotations,
563 89
                    $metadata,
564 89
                    $metadataBuildingContext
565
                );
566 29
            case isset($propertyAnnotations[Annotation\Embedded::class]):
567
                return null;
568
            default:
569 29
                $transientBuilder = new Builder\TransientMetadataBuilder($metadataBuildingContext);
570
571
                $transientBuilder
572 29
                    ->withComponentMetadata($metadata)
573 29
                    ->withFieldName($reflectionProperty->getName());
574
575 29
                return $transientBuilder->build();
576
        }
577
    }
578
579
    /**
580
     * @param Annotation\Annotation[] $propertyAnnotations
581
     *
582
     * @throws Mapping\MappingException
583
     */
584 367
    private function convertReflectionPropertyToFieldMetadata(
585
        ReflectionProperty $reflectionProperty,
586
        array $propertyAnnotations,
587
        Mapping\ClassMetadata $metadata,
588
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
589
    ) : Mapping\FieldMetadata {
590 367
        $fieldBuilder  = new Builder\FieldMetadataBuilder($metadataBuildingContext);
591
        $fieldMetadata = $fieldBuilder
592 367
            ->withComponentMetadata($metadata)
593 367
            ->withFieldName($reflectionProperty->getName())
594 367
            ->withColumnAnnotation($propertyAnnotations[Annotation\Column::class])
595 367
            ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
596 367
            ->withVersionAnnotation($propertyAnnotations[Annotation\Version::class] ?? null)
597 367
            ->withGeneratedValueAnnotation($propertyAnnotations[Annotation\GeneratedValue::class] ?? null)
598 367
            ->withSequenceGeneratorAnnotation($propertyAnnotations[Annotation\SequenceGenerator::class] ?? null)
599 367
            ->withCustomIdGeneratorAnnotation($propertyAnnotations[Annotation\CustomIdGenerator::class] ?? null)
600 367
            ->build();
601
602
        // Prevent column duplication
603 367
        if ($metadata->checkPropertyDuplication($fieldMetadata->getColumnName())) {
0 ignored issues
show
Bug introduced by
It seems like $fieldMetadata->getColumnName() can also be of type null; however, parameter $columnName of Doctrine\ORM\Mapping\Cla...ckPropertyDuplication() does only seem to accept string, 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

603
        if ($metadata->checkPropertyDuplication(/** @scrutinizer ignore-type */ $fieldMetadata->getColumnName())) {
Loading history...
604
            throw Mapping\MappingException::duplicateColumnName(
605
                $metadata->getClassName(),
606
                $fieldMetadata->getColumnName()
607
            );
608
        }
609
610
//        // Check for GeneratedValue strategy
611
//        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
612
//            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
613
//            $strategy            = strtoupper($generatedValueAnnot->strategy);
614
//            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
615
//
616
//            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
617
//                $idGeneratorDefinition = [];
618
//
619
//                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
620
//                switch (true) {
621
//                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
622
//                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
623
//
624
//                        $idGeneratorDefinition = [
625
//                            'sequenceName' => $seqGeneratorAnnot->sequenceName,
626
//                            'allocationSize' => $seqGeneratorAnnot->allocationSize,
627
//                        ];
628
//
629
//                        break;
630
//
631
//                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
632
//                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
633
//
634
//                        $idGeneratorDefinition = [
635
//                            'class' => $customGeneratorAnnot->class,
636
//                            'arguments' => $customGeneratorAnnot->arguments,
637
//                        ];
638
//
639
//                        if (! isset($idGeneratorDefinition['class'])) {
640
//                            throw new Mapping\MappingException(
641
//                                sprintf('Cannot instantiate custom generator, no class has been defined')
642
//                            );
643
//                        }
644
//
645
//                        if (! class_exists($idGeneratorDefinition['class'])) {
646
//                            throw new Mapping\MappingException(
647
//                                sprintf('Cannot instantiate custom generator : %s', var_export($idGeneratorDefinition, true))
648
//                            );
649
//                        }
650
//
651
//                        break;
652
//
653
//                    /** @todo If it is not supported, why does this exist? */
654
//                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
655
//                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($metadata->getClassName());
656
//                }
657
//
658
//                $fieldMetadata->setValueGenerator(
659
//                    new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition)
660
//                );
661
//            }
662
//        }
663
664 367
        return $fieldMetadata;
665
    }
666
667
    /**
668
     * @param Annotation\Annotation[] $propertyAnnotations
669
     */
670 111
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
671
        ReflectionProperty $reflectionProperty,
672
        array $propertyAnnotations,
673
        Mapping\ClassMetadata $metadata,
674
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
675
    ) : Mapping\OneToOneAssociationMetadata {
676 111
        $className     = $metadata->getClassName();
677 111
        $fieldName     = $reflectionProperty->getName();
678 111
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
679 111
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
680 111
        $targetEntity  = $oneToOneAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
681
682 111
        $assocMetadata->setTargetEntity($targetEntity);
683 111
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
684 111
        $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval);
0 ignored issues
show
Bug introduced by
Accessing orphanRemoval on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
685 111
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
686
687 111
        if (! empty($oneToOneAnnot->mappedBy)) {
0 ignored issues
show
Bug introduced by
Accessing mappedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
688 40
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
689 40
            $assocMetadata->setOwningSide(false);
690
        }
691
692 111
        if (! empty($oneToOneAnnot->inversedBy)) {
0 ignored issues
show
Bug introduced by
Accessing inversedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
693 51
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
694 51
            $assocMetadata->setOwningSide(true);
695
        }
696
697
        // Check for Id
698 111
        if (isset($propertyAnnotations[Annotation\Id::class])) {
699 12
            $assocMetadata->setPrimaryKey(true);
700
        }
701
702
        // Check for Cache
703 111
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
704 4
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
705
706
            $cacheBuilder
707 4
                ->withComponentMetadata($metadata)
708 4
                ->withFieldName($fieldName)
709 4
                ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class]);
710
711 4
            $assocMetadata->setCache($cacheBuilder->build());
712
        }
713
714
        // Check for owning side to consider join column
715 111
        if (! $assocMetadata->isOwningSide()) {
716 40
            return $assocMetadata;
717
        }
718
719
        // Check for JoinColumn/JoinColumns annotations
720 105
        $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
721
722
        $joinColumnBuilder
723 105
            ->withComponentMetadata($metadata)
724 105
            ->withFieldName($fieldName);
725
726
        switch (true) {
727 105
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
728 79
                $joinColumnBuilder->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class]);
729
730 79
                $assocMetadata->addJoinColumn($joinColumnBuilder->build());
731 79
                break;
732
733 29
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
734 3
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
735
736 3
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
737 3
                    $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnot);
738
739 3
                    $assocMetadata->addJoinColumn($joinColumnBuilder->build());
740
                }
741
742 3
                break;
743
744
            default:
745 26
                $assocMetadata->addJoinColumn($joinColumnBuilder->build());
746 26
                break;
747
        }
748
749 105
        return $assocMetadata;
750
    }
751
752
    /**
753
     * @param Annotation\Annotation[] $propertyAnnotations
754
     */
755 141
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
756
        ReflectionProperty $reflectionProperty,
757
        array $propertyAnnotations,
758
        Mapping\ClassMetadata $metadata,
759
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
760
    ) : Mapping\ManyToOneAssociationMetadata {
761
        // ManyToOne must be owning side by design
762 141
        $className      = $metadata->getClassName();
763 141
        $fieldName      = $reflectionProperty->getName();
764 141
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
765 141
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
766 141
        $targetEntity   = $manyToOneAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
767
768 141
        $assocMetadata->setTargetEntity($targetEntity);
769 141
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
770 141
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
771
772 141
        if (! empty($manyToOneAnnot->inversedBy)) {
0 ignored issues
show
Bug introduced by
Accessing inversedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
773 94
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
774
        }
775
776
        // Check for Id
777 141
        if (isset($propertyAnnotations[Annotation\Id::class])) {
778 34
            $assocMetadata->setPrimaryKey(true);
779
        }
780
781
        // Check for Cache
782 141
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
783 12
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
784
785
            $cacheBuilder
786 12
                ->withComponentMetadata($metadata)
787 12
                ->withFieldName($fieldName)
788 12
                ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class]);
789
790 12
            $assocMetadata->setCache($cacheBuilder->build());
791
        }
792
793
        // Check for JoinColumn/JoinColumns annotations
794 141
        $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
795
796
        $joinColumnBuilder
797 141
            ->withComponentMetadata($metadata)
798 141
            ->withFieldName($fieldName);
799
800
        switch (true) {
801 141
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
802 81
                $joinColumnBuilder->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class]);
803
804 81
                $assocMetadata->addJoinColumn($joinColumnBuilder->build());
805 81
                break;
806
807 69
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
808 16
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
809
810 16
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
811 16
                    $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnot);
812
813 16
                    $assocMetadata->addJoinColumn($joinColumnBuilder->build());
814
                }
815
816 16
                break;
817
818
            default:
819 56
                $assocMetadata->addJoinColumn($joinColumnBuilder->build());
820 56
                break;
821
        }
822
823 141
        return $assocMetadata;
824
    }
825
826
    /**
827
     * @param Annotation\Annotation[] $propertyAnnotations
828
     *
829
     * @throws Mapping\MappingException
830
     */
831 109
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
832
        ReflectionProperty $reflectionProperty,
833
        array $propertyAnnotations,
834
        Mapping\ClassMetadata $metadata,
835
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
836
    ) : Mapping\OneToManyAssociationMetadata {
837 109
        $className      = $metadata->getClassName();
838 109
        $fieldName      = $reflectionProperty->getName();
839 109
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
840 109
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
841 109
        $targetEntity   = $oneToManyAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
842
843 109
        $assocMetadata->setTargetEntity($targetEntity);
844 109
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
845 109
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
0 ignored issues
show
Bug introduced by
Accessing orphanRemoval on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
846 109
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
847 109
        $assocMetadata->setOwningSide(false);
848 109
        $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
0 ignored issues
show
Bug introduced by
Accessing mappedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
849
850 109
        if (! empty($oneToManyAnnot->indexBy)) {
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
851 8
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
852
        }
853
854
        // Check for OrderBy
855 109
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
856 14
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
857
858 14
            $assocMetadata->setOrderBy($orderByAnnot->value);
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
859
        }
860
861
        // Check for Id
862 109
        if (isset($propertyAnnotations[Annotation\Id::class])) {
863
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
864
        }
865
866
        // Check for Cache
867 109
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
868 9
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
869
870
            $cacheBuilder
871 9
                ->withComponentMetadata($metadata)
872 9
                ->withFieldName($fieldName)
873 9
                ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class]);
874
875 9
            $assocMetadata->setCache($cacheBuilder->build());
876
        }
877
878 109
        return $assocMetadata;
879
    }
880
881
    /**
882
     * @param Annotation\Annotation[] $propertyAnnotations
883
     *
884
     * @throws Mapping\MappingException
885
     */
886 89
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
887
        ReflectionProperty $reflectionProperty,
888
        array $propertyAnnotations,
889
        Mapping\ClassMetadata $metadata,
890
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
891
    ) : Mapping\ManyToManyAssociationMetadata {
892 89
        $className       = $metadata->getClassName();
893 89
        $fieldName       = $reflectionProperty->getName();
894 89
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
895 89
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
896 89
        $targetEntity    = $manyToManyAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
897
898 89
        $assocMetadata->setTargetEntity($targetEntity);
899 89
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
900 89
        $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval);
0 ignored issues
show
Bug introduced by
Accessing orphanRemoval on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
901 89
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
902
903 89
        if (! empty($manyToManyAnnot->mappedBy)) {
0 ignored issues
show
Bug introduced by
Accessing mappedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
904 36
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
905 36
            $assocMetadata->setOwningSide(false);
906
        }
907
908 89
        if (! empty($manyToManyAnnot->inversedBy)) {
0 ignored issues
show
Bug introduced by
Accessing inversedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
909 45
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
910
        }
911
912 89
        if (! empty($manyToManyAnnot->indexBy)) {
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
913 3
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
914
        }
915
916
        // Check for OrderBy
917 89
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
918 3
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
919
920 3
            $assocMetadata->setOrderBy($orderByAnnot->value);
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
921
        }
922
923
        // Check for Id
924 89
        if (isset($propertyAnnotations[Annotation\Id::class])) {
925
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
926
        }
927
928
        // Check for Cache
929 89
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
930 2
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
931
932
            $cacheBuilder
933 2
                ->withComponentMetadata($metadata)
934 2
                ->withFieldName($fieldName)
935 2
                ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class]);
936
937 2
            $assocMetadata->setCache($cacheBuilder->build());
938
        }
939
940
        // Check for owning side to consider join column
941 89
        if (! $assocMetadata->isOwningSide()) {
942 36
            return $assocMetadata;
943
        }
944
945 79
        $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
946
947
        $joinTableBuilder
948 79
            ->withComponentMetadata($metadata)
949 79
            ->withTargetEntity($targetEntity)
950 79
            ->withFieldName($fieldName);
951
952
        // Check for JoinTable
953 79
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
954 71
            $joinTableBuilder->withJoinTableAnnotation($propertyAnnotations[Annotation\JoinTable::class]);
955
        }
956
957 79
        $assocMetadata->setJoinTable($joinTableBuilder->build());
958
959 79
        return $assocMetadata;
960
    }
961
962
    /**
963
     * @param Annotation\Annotation[] $classAnnotations
964
     */
965 373
    private function attachLifecycleCallbacks(
966
        array $classAnnotations,
967
        ReflectionClass $reflectionClass,
968
        Mapping\ClassMetadata $metadata
969
    ) : void {
970
        // Evaluate @HasLifecycleCallbacks annotation
971 373
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
972
            $eventMap = [
973 14
                Events::prePersist  => Annotation\PrePersist::class,
974 14
                Events::postPersist => Annotation\PostPersist::class,
975 14
                Events::preUpdate   => Annotation\PreUpdate::class,
976 14
                Events::postUpdate  => Annotation\PostUpdate::class,
977 14
                Events::preRemove   => Annotation\PreRemove::class,
978 14
                Events::postRemove  => Annotation\PostRemove::class,
979 14
                Events::postLoad    => Annotation\PostLoad::class,
980 14
                Events::preFlush    => Annotation\PreFlush::class,
981
            ];
982
983
            /** @var ReflectionMethod $reflectionMethod */
984 14
            foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
985 13
                $annotations = $this->getMethodAnnotations($reflectionMethod);
986
987 13
                foreach ($eventMap as $eventName => $annotationClassName) {
988 13
                    if (isset($annotations[$annotationClassName])) {
989 12
                        $metadata->addLifecycleCallback($eventName, $reflectionMethod->getName());
990
                    }
991
                }
992
            }
993
        }
994 373
    }
995
996
    /**
997
     * @param Annotation\Annotation[] $classAnnotations
998
     *
999
     * @throws ReflectionException
1000
     * @throws Mapping\MappingException
1001
     */
1002 373
    private function attachEntityListeners(
1003
        array $classAnnotations,
1004
        Mapping\ClassMetadata $metadata
1005
    ) : void {
1006
        // Evaluate @EntityListeners annotation
1007 373
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1008
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1009 8
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1010
            $eventMap             = [
1011 8
                Events::prePersist  => Annotation\PrePersist::class,
1012 8
                Events::postPersist => Annotation\PostPersist::class,
1013 8
                Events::preUpdate   => Annotation\PreUpdate::class,
1014 8
                Events::postUpdate  => Annotation\PostUpdate::class,
1015 8
                Events::preRemove   => Annotation\PreRemove::class,
1016 8
                Events::postRemove  => Annotation\PostRemove::class,
1017 8
                Events::postLoad    => Annotation\PostLoad::class,
1018 8
                Events::preFlush    => Annotation\PreFlush::class,
1019
            ];
1020
1021 8
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1022 8
                if (! class_exists($listenerClassName)) {
1023
                    throw Mapping\MappingException::entityListenerClassNotFound(
1024
                        $listenerClassName,
1025
                        $metadata->getClassName()
1026
                    );
1027
                }
1028
1029 8
                $listenerClass = new ReflectionClass($listenerClassName);
1030
1031
                /** @var ReflectionMethod $reflectionMethod */
1032 8
                foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
1033 8
                    $annotations = $this->getMethodAnnotations($reflectionMethod);
1034
1035 8
                    foreach ($eventMap as $eventName => $annotationClassName) {
1036 8
                        if (isset($annotations[$annotationClassName])) {
1037 6
                            $metadata->addEntityListener($eventName, $listenerClassName, $reflectionMethod->getName());
1038
                        }
1039
                    }
1040
                }
1041
            }
1042
        }
1043 373
    }
1044
1045
    /**
1046
     * @param Annotation\Annotation[] $classAnnotations
1047
     *
1048
     * @throws Mapping\MappingException
1049
     */
1050 370
    private function attachPropertyOverrides(
1051
        array $classAnnotations,
1052
        ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
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

1052
        /** @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...
1053
        Mapping\ClassMetadata $metadata,
1054
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
1055
    ) : void {
1056
        // Evaluate AssociationOverrides annotation
1057 370
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1058 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1059
1060 5
            foreach ($associationOverridesAnnot->value as $associationOverride) {
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1061 5
                $fieldName = $associationOverride->name;
1062 5
                $property  = $metadata->getProperty($fieldName);
1063
1064 5
                if (! $property) {
1065
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1066
                }
1067
1068 5
                $existingClass = get_class($property);
1069 5
                $override      = new $existingClass($fieldName);
1070
1071 5
                $override->setTargetEntity($property->getTargetEntity());
0 ignored issues
show
Bug introduced by
The method getTargetEntity() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\AssociationMetadata. ( Ignorable by Annotation )

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

1071
                $override->setTargetEntity($property->/** @scrutinizer ignore-call */ getTargetEntity());
Loading history...
1072
1073
                // Check for JoinColumn/JoinColumns annotations
1074 5
                if ($associationOverride->joinColumns) {
1075 3
                    $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
1076
1077
                    $joinColumnBuilder
1078 3
                        ->withComponentMetadata($metadata)
1079 3
                        ->withFieldName($fieldName);
1080
1081 3
                    $joinColumns = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $joinColumns is dead and can be removed.
Loading history...
1082
1083 3
                    foreach ($associationOverride->joinColumns as $joinColumnAnnotation) {
1084 3
                        $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
1085
1086 3
                        $override->addJoinColumn($joinColumnBuilder->build());
1087
                    }
1088
                }
1089
1090
                // Check for JoinTable annotations
1091 5
                if ($associationOverride->joinTable) {
1092 2
                    $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
1093
1094
                    $joinTableBuilder
1095 2
                        ->withComponentMetadata($metadata)
1096 2
                        ->withFieldName($fieldName)
1097 2
                        ->withTargetEntity($property->getTargetEntity())
1098 2
                        ->withJoinTableAnnotation($associationOverride->joinTable);
1099
1100 2
                    $override->setJoinTable($joinTableBuilder->build());
1101
                }
1102
1103
                // Check for inversedBy
1104 5
                if ($associationOverride->inversedBy) {
1105 1
                    $override->setInversedBy($associationOverride->inversedBy);
1106
                }
1107
1108
                // Check for fetch
1109 5
                if ($associationOverride->fetch) {
1110 1
                    $override->setFetchMode(
1111 1
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1112
                    );
1113
                }
1114
1115 5
                $metadata->setPropertyOverride($override);
1116
            }
1117
        }
1118
1119
        // Evaluate AttributeOverrides annotation
1120 370
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1121 3
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1122 3
            $fieldBuilder            = new Builder\FieldMetadataBuilder($metadataBuildingContext);
1123
1124
            $fieldBuilder
1125 3
                ->withComponentMetadata($metadata)
1126 3
                ->withIdAnnotation(null)
1127 3
                ->withVersionAnnotation(null);
1128
1129 3
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnotation) {
1130
                $fieldBuilder
1131 3
                    ->withFieldName($attributeOverrideAnnotation->name)
1132 3
                    ->withColumnAnnotation($attributeOverrideAnnotation->column);
1133
1134 3
                $metadata->setPropertyOverride($fieldBuilder->build());
1135
            }
1136
        }
1137 370
    }
1138
1139
    /**
1140
     * Attempts to resolve the cascade modes.
1141
     *
1142
     * @param string   $className        The class name.
1143
     * @param string   $fieldName        The field name.
1144
     * @param string[] $originalCascades The original unprocessed field cascades.
1145
     *
1146
     * @return string[] The processed field cascades.
1147
     *
1148
     * @throws Mapping\MappingException If a cascade option is not valid.
1149
     */
1150 251
    private function getCascade(string $className, string $fieldName, array $originalCascades) : array
1151
    {
1152 251
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1153 251
        $cascades     = array_map('strtolower', $originalCascades);
1154
1155 251
        if (in_array('all', $cascades, true)) {
1156 23
            $cascades = $cascadeTypes;
1157
        }
1158
1159 251
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1160
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1161
1162
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1163
        }
1164
1165 251
        return $cascades;
1166
    }
1167
1168
    /**
1169
     * Attempts to resolve the fetch mode.
1170
     *
1171
     * @param string $className The class name.
1172
     * @param string $fetchMode The fetch mode.
1173
     *
1174
     * @return string The fetch mode as defined in ClassMetadata.
1175
     *
1176
     * @throws Mapping\MappingException If the fetch mode is not valid.
1177
     */
1178 251
    private function getFetchMode($className, $fetchMode) : string
1179
    {
1180 251
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1181
1182 251
        if (! defined($fetchModeConstant)) {
1183
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1184
        }
1185
1186 251
        return constant($fetchModeConstant);
1187
    }
1188
1189
    /**
1190
     * @return Annotation\Annotation[]
1191
     */
1192 379
    private function getClassAnnotations(ReflectionClass $reflectionClass) : array
1193
    {
1194 379
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1195
1196 379
        foreach ($classAnnotations as $key => $annot) {
1197 373
            if (! is_numeric($key)) {
1198
                continue;
1199
            }
1200
1201 373
            $classAnnotations[get_class($annot)] = $annot;
1202
        }
1203
1204 379
        return $classAnnotations;
1205
    }
1206
1207
    /**
1208
     * @return Annotation\Annotation[]
1209
     */
1210 373
    private function getPropertyAnnotations(ReflectionProperty $reflectionProperty) : array
1211
    {
1212 373
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1213
1214 372
        foreach ($propertyAnnotations as $key => $annot) {
1215 372
            if (! is_numeric($key)) {
1216
                continue;
1217
            }
1218
1219 372
            $propertyAnnotations[get_class($annot)] = $annot;
1220
        }
1221
1222 372
        return $propertyAnnotations;
1223
    }
1224
1225
    /**
1226
     * @return Annotation\Annotation[]
1227
     */
1228 21
    private function getMethodAnnotations(ReflectionMethod $reflectionMethod) : array
1229
    {
1230 21
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1231
1232 21
        foreach ($methodAnnotations as $key => $annot) {
1233 18
            if (! is_numeric($key)) {
1234
                continue;
1235
            }
1236
1237 18
            $methodAnnotations[get_class($annot)] = $annot;
1238
        }
1239
1240 21
        return $methodAnnotations;
1241
    }
1242
}
1243