Failed Conditions
Push — master ( 4bfa22...148895 )
by Guilherme
17:21 queued 09:56
created

convertPropertyAnnotationsToProperty()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 116
Code Lines 79

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 70
CRAP Score 12.003

Importance

Changes 0
Metric Value
cc 12
eloc 79
nc 12
nop 4
dl 0
loc 116
ccs 70
cts 72
cp 0.9722
crap 12.003
rs 6.0315
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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\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_diff;
26
use function array_intersect;
27
use function array_map;
28
use function array_merge;
29
use function array_unique;
30
use function class_exists;
31
use function constant;
32
use function count;
33
use function defined;
34
use function get_class;
35
use function get_declared_classes;
36
use function in_array;
37
use function is_dir;
38
use function is_numeric;
39
use function preg_match;
40
use function preg_quote;
41
use function realpath;
42
use function sprintf;
43
use function str_replace;
44
use function strpos;
45
46
/**
47
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
48
 */
49
class AnnotationDriver implements MappingDriver
50
{
51
    /** @var int[] */
52
    protected $entityAnnotationClasses = [
53
        Annotation\Entity::class           => 1,
54
        Annotation\MappedSuperclass::class => 2,
55
    ];
56
57
    /**
58
     * The AnnotationReader.
59
     *
60
     * @var AnnotationReader
61
     */
62
    protected $reader;
63
64
    /**
65
     * The paths where to look for mapping files.
66
     *
67
     * @var string[]
68
     */
69
    protected $paths = [];
70
71
    /**
72
     * The paths excluded from path where to look for mapping files.
73
     *
74
     * @var string[]
75
     */
76
    protected $excludePaths = [];
77
78
    /**
79
     * The file extension of mapping documents.
80
     *
81
     * @var string
82
     */
83
    protected $fileExtension = '.php';
84
85
    /**
86
     * Cache for AnnotationDriver#getAllClassNames().
87
     *
88
     * @var string[]|null
89
     */
90
    protected $classNames;
91
92
    /**
93
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
94
     * docblock annotations.
95
     *
96
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
97
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
98
     */
99 2297
    public function __construct(Reader $reader, $paths = null)
100
    {
101 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...
102
103 2297
        if ($paths) {
104 2211
            $this->addPaths((array) $paths);
105
        }
106 2297
    }
107
108
    /**
109
     * Appends lookup paths to metadata driver.
110
     *
111
     * @param string[] $paths
112
     */
113 2215
    public function addPaths(array $paths)
114
    {
115 2215
        $this->paths = array_unique(array_merge($this->paths, $paths));
116 2215
    }
117
118
    /**
119
     * Retrieves the defined metadata lookup paths.
120
     *
121
     * @return string[]
122
     */
123
    public function getPaths()
124
    {
125
        return $this->paths;
126
    }
127
128
    /**
129
     * Append exclude lookup paths to metadata driver.
130
     *
131
     * @param string[] $paths
132
     */
133
    public function addExcludePaths(array $paths)
134
    {
135
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
136
    }
137
138
    /**
139
     * Retrieve the defined metadata lookup exclude paths.
140
     *
141
     * @return string[]
142
     */
143
    public function getExcludePaths()
144
    {
145
        return $this->excludePaths;
146
    }
147
148
    /**
149
     * Retrieve the current annotation reader
150
     *
151
     * @return Reader
152
     */
153 1
    public function getReader()
154
    {
155 1
        return $this->reader;
156
    }
157
158
    /**
159
     * Gets the file extension used to look for mapping files under.
160
     *
161
     * @return string
162
     */
163
    public function getFileExtension()
164
    {
165
        return $this->fileExtension;
166
    }
167
168
    /**
169
     * Sets the file extension used to look for mapping files under.
170
     *
171
     * @param string $fileExtension The file extension to set.
172
     */
173
    public function setFileExtension($fileExtension)
174
    {
175
        $this->fileExtension = $fileExtension;
176
    }
177
178
    /**
179
     * Returns whether the class with the specified name is transient. Only non-transient
180
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
181
     *
182
     * A class is non-transient if it is annotated with an annotation
183
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
184
     *
185
     * @param string $className
186
     *
187
     * @throws ReflectionException
188
     */
189 193
    public function isTransient($className) : bool
190
    {
191 193
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
192
193 193
        foreach ($classAnnotations as $annotation) {
194 188
            if (isset($this->entityAnnotationClasses[get_class($annotation)])) {
195 188
                return false;
196
            }
197
        }
198
199 12
        return true;
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     *
205
     * @throws ReflectionException
206
     */
207 60
    public function getAllClassNames() : array
208
    {
209 60
        if ($this->classNames !== null) {
210 45
            return $this->classNames;
211
        }
212
213 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...
214
            throw Mapping\MappingException::pathRequired();
215
        }
216
217 60
        $classes       = [];
218 60
        $includedFiles = [];
219
220 60
        foreach ($this->paths as $path) {
221 60
            if (! is_dir($path)) {
222
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
223
            }
224
225 60
            $iterator = new RegexIterator(
226 60
                new RecursiveIteratorIterator(
227 60
                    new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
228 60
                    RecursiveIteratorIterator::LEAVES_ONLY
229
                ),
230 60
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
231 60
                RecursiveRegexIterator::GET_MATCH
232
            );
233
234 60
            foreach ($iterator as $file) {
235 60
                $sourceFile = $file[0];
236
237 60
                if (! preg_match('(^phar:)i', $sourceFile)) {
238 60
                    $sourceFile = realpath($sourceFile);
239
                }
240
241 60
                foreach ($this->excludePaths as $excludePath) {
242
                    $exclude = str_replace('\\', '/', realpath($excludePath));
243
                    $current = str_replace('\\', '/', $sourceFile);
244
245
                    if (strpos($current, $exclude) !== false) {
246
                        continue 2;
247
                    }
248
                }
249
250 60
                require_once $sourceFile;
251
252 60
                $includedFiles[] = $sourceFile;
253
            }
254
        }
255
256 60
        $declared = get_declared_classes();
257
258 60
        foreach ($declared as $className) {
259 60
            $reflectionClass = new ReflectionClass($className);
260 60
            $sourceFile      = $reflectionClass->getFileName();
261
262 60
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
263 60
                $classes[] = $className;
264
            }
265
        }
266
267 60
        $this->classNames = $classes;
268
269 60
        return $classes;
270
    }
271
272
    /**
273
     * {@inheritDoc}
274
     *
275
     * @throws CacheException
276
     * @throws Mapping\MappingException
277
     * @throws ReflectionException
278
     * @throws RuntimeException
279
     * @throws UnexpectedValueException
280
     */
281 379
    public function loadMetadataForClass(
282
        string $className,
283
        ?Mapping\ComponentMetadata $parent,
284
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
285
    ) : Mapping\ComponentMetadata {
286 379
        $reflectionClass  = new ReflectionClass($className);
287 379
        $metadata         = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
288 379
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
289 379
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
290 379
            $classAnnotations,
291 379
            $reflectionClass,
292 379
            $metadata,
293 379
            $metadataBuildingContext
294
        );
295
296
        // Evaluate @Cache annotation
297 373
        if (isset($classAnnotations[Annotation\Cache::class])) {
298 18
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
299
300
            $cacheBuilder
301 18
                ->withComponentMetadata($metadata)
302 18
                ->withCacheAnnotation($classAnnotations[Annotation\Cache::class]);
303
304 18
            $metadata->setCache($cacheBuilder->build());
305
        }
306
307
        // Evaluate annotations on properties/fields
308
        /** @var ReflectionProperty $reflProperty */
309 373
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
310 373
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
311 74
                continue;
312
            }
313
314 373
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
315 372
            $property            = $this->convertPropertyAnnotationsToProperty(
316 372
                $propertyAnnotations,
317 372
                $reflectionProperty,
318 372
                $classMetadata,
319 372
                $metadataBuildingContext
320
            );
321
322 371
            if ($classMetadata->isMappedSuperclass &&
323 371
                $property instanceof Mapping\ToManyAssociationMetadata &&
324 371
                ! $property->isOwningSide()) {
325 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
326 1
                    $classMetadata->getClassName(),
327 1
                    $property->getName()
328
                );
329
            }
330
331 370
            $metadata->addProperty($property);
332
        }
333
334 370
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
335
336 370
        return $classMetadata;
337
    }
338
339
    /**
340
     * @param Annotation\Annotation[] $classAnnotations
341
     *
342
     * @throws Mapping\MappingException
343
     * @throws UnexpectedValueException
344
     * @throws ReflectionException
345
     */
346 379
    private function convertClassAnnotationsToClassMetadata(
347
        array $classAnnotations,
348
        ReflectionClass $reflectionClass,
349
        Mapping\ClassMetadata $metadata,
350
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
351
    ) : Mapping\ClassMetadata {
352
        switch (true) {
353 379
            case isset($classAnnotations[Annotation\Entity::class]):
354 372
                return $this->convertClassAnnotationsToEntityClassMetadata(
355 372
                    $classAnnotations,
356 372
                    $reflectionClass,
357 372
                    $metadata,
358 372
                    $metadataBuildingContext
359
                );
360
361
                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...
362
363 29
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
364 23
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
365 23
                    $classAnnotations,
366 23
                    $reflectionClass,
367 23
                    $metadata
368
                );
369 6
            case isset($classAnnotations[Annotation\Embeddable::class]):
370
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
371
                    $classAnnotations,
372
                    $reflectionClass,
373
                    $metadata
374
                );
375
            default:
376 6
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
377
        }
378
    }
379
380
    /**
381
     * @param Annotation\Annotation[] $classAnnotations
382
     *
383
     * @return Mapping\ClassMetadata
384
     *
385
     * @throws Mapping\MappingException
386
     * @throws ReflectionException
387
     * @throws UnexpectedValueException
388
     */
389 372
    private function convertClassAnnotationsToEntityClassMetadata(
390
        array $classAnnotations,
391
        ReflectionClass $reflectionClass,
392
        Mapping\ClassMetadata $metadata,
393
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
394
    ) {
395
        /** @var Annotation\Entity $entityAnnot */
396 372
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
397
398 372
        if ($entityAnnot->repositoryClass !== null) {
399 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
400
        }
401
402 372
        if ($entityAnnot->readOnly) {
403 1
            $metadata->asReadOnly();
404
        }
405
406 372
        $metadata->isMappedSuperclass = false;
407 372
        $metadata->isEmbeddedClass    = false;
408
409
        // Process table information
410 372
        $parent = $metadata->getParent();
411
412 372
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
413
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
414
            do {
415 29
                if (! $parent->isMappedSuperclass) {
416 29
                    $metadata->setTable($parent->table);
417
418 29
                    break;
419
                }
420
421 4
                $parent = $parent->getParent();
422 29
            } while ($parent !== null);
423
        } else {
424 372
            $tableBuilder = new Builder\TableMetadataBuilder($metadataBuildingContext);
425
426
            $tableBuilder
427 372
                ->withEntityClassMetadata($metadata)
428 372
                ->withTableAnnotation($classAnnotations[Annotation\Table::class] ?? null);
429
430 372
            $metadata->setTable($tableBuilder->build());
431
        }
432
433
        // Evaluate @ChangeTrackingPolicy annotation
434 372
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
435 6
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
436
437 6
            $metadata->setChangeTrackingPolicy(
438 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...
439
            );
440
        }
441
442
        // Evaluate @InheritanceType annotation
443 372
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
444 80
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
445
446 80
            $metadata->setInheritanceType(
447 80
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
448
            );
449
450 80
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
451 80
                $discriminatorColumnBuilder = new Builder\DiscriminatorColumnMetadataBuilder($metadataBuildingContext);
452
453
                $discriminatorColumnBuilder
454 80
                    ->withComponentMetadata($metadata)
455 80
                    ->withDiscriminatorColumnAnnotation($classAnnotations[Annotation\DiscriminatorColumn::class] ?? null);
456
457 80
                $metadata->setDiscriminatorColumn($discriminatorColumnBuilder->build());
458
459
                // Evaluate DiscriminatorMap annotation
460 80
                if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
461 77
                    $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
462 77
                    $discriminatorMap           = $discriminatorMapAnnotation->value;
463
464 77
                    $metadata->setDiscriminatorMap($discriminatorMap);
465
                }
466
            }
467
        }
468
469 372
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
470 372
        $this->attachEntityListeners($classAnnotations, $metadata);
471
472 372
        return $metadata;
473
    }
474
475
    /**
476
     * @param Annotation\Annotation[] $classAnnotations
477
     *
478
     * @throws Mapping\MappingException
479
     * @throws ReflectionException
480
     */
481 23
    private function convertClassAnnotationsToMappedSuperClassMetadata(
482
        array $classAnnotations,
483
        ReflectionClass $reflectionClass,
484
        Mapping\ClassMetadata $metadata
485
    ) : Mapping\ClassMetadata {
486
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
487 23
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
488
489 23
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
490 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
491
        }
492
493 23
        $metadata->isMappedSuperclass = true;
494 23
        $metadata->isEmbeddedClass    = false;
495
496 23
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
497 23
        $this->attachEntityListeners($classAnnotations, $metadata);
498
499 23
        return $metadata;
500
    }
501
502
    /**
503
     * @param Annotation\Annotation[] $classAnnotations
504
     */
505
    private function convertClassAnnotationsToEmbeddableClassMetadata(
506
        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

506
        /** @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...
507
        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

507
        /** @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...
508
        Mapping\ClassMetadata $metadata
509
    ) : Mapping\ClassMetadata {
510
        $metadata->isMappedSuperclass = false;
511
        $metadata->isEmbeddedClass    = true;
512
513
        return $metadata;
514
    }
515
516
    /**
517
     * @param Annotation\Annotation[] $propertyAnnotations
518
     *
519
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
520
     */
521 372
    private function convertPropertyAnnotationsToProperty(
522
        array $propertyAnnotations,
523
        ReflectionProperty $reflectionProperty,
524
        Mapping\ClassMetadata $metadata,
525
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
526
    ) : ?Mapping\Property {
527
        switch (true) {
528 372
            case isset($propertyAnnotations[Annotation\Column::class]):
529 367
                $fieldBuilder  = new Builder\FieldMetadataBuilder($metadataBuildingContext);
530
                $fieldMetadata = $fieldBuilder
531 367
                    ->withComponentMetadata($metadata)
532 367
                    ->withFieldName($reflectionProperty->getName())
533 367
                    ->withColumnAnnotation($propertyAnnotations[Annotation\Column::class])
534 367
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
535 367
                    ->withVersionAnnotation($propertyAnnotations[Annotation\Version::class] ?? null)
536 367
                    ->withGeneratedValueAnnotation($propertyAnnotations[Annotation\GeneratedValue::class] ?? null)
537 367
                    ->withSequenceGeneratorAnnotation($propertyAnnotations[Annotation\SequenceGenerator::class] ?? null)
538 367
                    ->withCustomIdGeneratorAnnotation($propertyAnnotations[Annotation\CustomIdGenerator::class] ?? null)
539 367
                    ->build();
540
541
                // Prevent column duplication
542 367
                $columnName = $fieldMetadata->getColumnName();
543
544 367
                if ($metadata->checkPropertyDuplication($columnName)) {
0 ignored issues
show
Bug introduced by
It seems like $columnName 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

544
                if ($metadata->checkPropertyDuplication(/** @scrutinizer ignore-type */ $columnName)) {
Loading history...
545
                    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
546
                }
547
548 367
                $metadata->fieldNames[$fieldMetadata->getColumnName()] = $fieldMetadata->getName();
549
550 367
                return $fieldMetadata;
551 254
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
552 111
                $oneToOneAssociationBuilder = new Builder\OneToOneAssociationMetadataBuilder($metadataBuildingContext);
553
                $associationMetadata        = $oneToOneAssociationBuilder
554 111
                    ->withComponentMetadata($metadata)
555 111
                    ->withFieldName($reflectionProperty->getName())
556 111
                    ->withOneToOneAnnotation($propertyAnnotations[Annotation\OneToOne::class] ?? null)
0 ignored issues
show
Bug introduced by
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

556
                    ->withOneToOneAnnotation(/** @scrutinizer ignore-type */ $propertyAnnotations[Annotation\OneToOne::class] ?? null)
Loading history...
557 111
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
558 111
                    ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null)
559 111
                    ->withJoinColumnsAnnotation($propertyAnnotations[Annotation\JoinColumns::class] ?? null)
560 111
                    ->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class] ?? null)
561 111
                    ->build();
562
563
                // Prevent column duplication
564 111
                foreach ($associationMetadata->getJoinColumns() as $joinColumnMetadata) {
565 105
                    $columnName = $joinColumnMetadata->getColumnName();
566
567
                    // @todo guilhermeblanco Open an issue to discuss making this scenario impossible.
568
                    //if ($metadata->checkPropertyDuplication($columnName)) {
569
                    //    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
570
                    //}
571
572 105
                    if ($associationMetadata->isOwningSide()) {
573 105
                        $metadata->fieldNames[$columnName] = $associationMetadata->getName();
574
                    }
575
                }
576
577 111
                return $associationMetadata;
578 202
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
579 141
                $manyToOneAssociationBuilder = new Builder\ManyToOneAssociationMetadataBuilder($metadataBuildingContext);
580
                $associationMetadata         = $manyToOneAssociationBuilder
581 141
                    ->withComponentMetadata($metadata)
582 141
                    ->withFieldName($reflectionProperty->getName())
583 141
                    ->withManyToOneAnnotation($propertyAnnotations[Annotation\ManyToOne::class] ?? null)
0 ignored issues
show
Bug introduced by
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

583
                    ->withManyToOneAnnotation(/** @scrutinizer ignore-type */ $propertyAnnotations[Annotation\ManyToOne::class] ?? null)
Loading history...
584 141
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
585 141
                    ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null)
586 141
                    ->withJoinColumnsAnnotation($propertyAnnotations[Annotation\JoinColumns::class] ?? null)
587 141
                    ->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class] ?? null)
588 141
                    ->build();
589
590
                // Prevent column duplication
591 140
                foreach ($associationMetadata->getJoinColumns() as $joinColumnMetadata) {
592 140
                    $columnName = $joinColumnMetadata->getColumnName();
593
594
                    // @todo guilhermeblanco Open an issue to discuss making this scenario impossible.
595
                    //if ($metadata->checkPropertyDuplication($columnName)) {
596
                    //    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
597
                    //}
598
599 140
                    if ($associationMetadata->isOwningSide()) {
600 140
                        $metadata->fieldNames[$columnName] = $associationMetadata->getName();
601
                    }
602
                }
603
604 140
                return $associationMetadata;
605 163
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
606 109
                $oneToManyAssociationBuilder = new Builder\OneToManyAssociationMetadataBuilder($metadataBuildingContext);
607
608
                return $oneToManyAssociationBuilder
609 109
                    ->withComponentMetadata($metadata)
610 109
                    ->withFieldName($reflectionProperty->getName())
611 109
                    ->withOneToManyAnnotation($propertyAnnotations[Annotation\OneToMany::class] ?? null)
0 ignored issues
show
Bug introduced by
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

611
                    ->withOneToManyAnnotation(/** @scrutinizer ignore-type */ $propertyAnnotations[Annotation\OneToMany::class] ?? null)
Loading history...
612 109
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
613 109
                    ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null)
614 109
                    ->withOrderByAnnotation($propertyAnnotations[Annotation\OrderBy::class] ?? null)
615 109
                    ->build();
616 104
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
617 89
                $manyToManyAssociationBuilder = new Builder\ManyToManyAssociationMetadataBuilder($metadataBuildingContext);
618
619
                return $manyToManyAssociationBuilder
620 89
                    ->withComponentMetadata($metadata)
621 89
                    ->withFieldName($reflectionProperty->getName())
622 89
                    ->withManyToManyAnnotation($propertyAnnotations[Annotation\ManyToMany::class] ?? null)
0 ignored issues
show
Bug introduced by
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

622
                    ->withManyToManyAnnotation(/** @scrutinizer ignore-type */ $propertyAnnotations[Annotation\ManyToMany::class] ?? null)
Loading history...
623 89
                    ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null)
624 89
                    ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null)
625 89
                    ->withJoinTableAnnotation($propertyAnnotations[Annotation\JoinTable::class] ?? null)
626 89
                    ->withOrderByAnnotation($propertyAnnotations[Annotation\OrderBy::class] ?? null)
627 89
                    ->build();
628 29
            case isset($propertyAnnotations[Annotation\Embedded::class]):
629
                return null;
630
            default:
631 29
                $transientBuilder = new Builder\TransientMetadataBuilder($metadataBuildingContext);
632
633
                return $transientBuilder
634 29
                    ->withComponentMetadata($metadata)
635 29
                    ->withFieldName($reflectionProperty->getName())
636 29
                    ->build();
637
        }
638
    }
639
640
    /**
641
     * @param Annotation\Annotation[] $classAnnotations
642
     */
643 373
    private function attachLifecycleCallbacks(
644
        array $classAnnotations,
645
        ReflectionClass $reflectionClass,
646
        Mapping\ClassMetadata $metadata
647
    ) : void {
648
        // Evaluate @HasLifecycleCallbacks annotation
649 373
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
650
            $eventMap = [
651 14
                Events::prePersist  => Annotation\PrePersist::class,
652 14
                Events::postPersist => Annotation\PostPersist::class,
653 14
                Events::preUpdate   => Annotation\PreUpdate::class,
654 14
                Events::postUpdate  => Annotation\PostUpdate::class,
655 14
                Events::preRemove   => Annotation\PreRemove::class,
656 14
                Events::postRemove  => Annotation\PostRemove::class,
657 14
                Events::postLoad    => Annotation\PostLoad::class,
658 14
                Events::preFlush    => Annotation\PreFlush::class,
659
            ];
660
661
            /** @var ReflectionMethod $reflectionMethod */
662 14
            foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
663 13
                $annotations = $this->getMethodAnnotations($reflectionMethod);
664
665 13
                foreach ($eventMap as $eventName => $annotationClassName) {
666 13
                    if (isset($annotations[$annotationClassName])) {
667 12
                        $metadata->addLifecycleCallback($eventName, $reflectionMethod->getName());
668
                    }
669
                }
670
            }
671
        }
672 373
    }
673
674
    /**
675
     * @param Annotation\Annotation[] $classAnnotations
676
     *
677
     * @throws ReflectionException
678
     * @throws Mapping\MappingException
679
     */
680 373
    private function attachEntityListeners(
681
        array $classAnnotations,
682
        Mapping\ClassMetadata $metadata
683
    ) : void {
684
        // Evaluate @EntityListeners annotation
685 373
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
686
            /** @var Annotation\EntityListeners $entityListenersAnnot */
687 8
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
688
            $eventMap             = [
689 8
                Events::prePersist  => Annotation\PrePersist::class,
690 8
                Events::postPersist => Annotation\PostPersist::class,
691 8
                Events::preUpdate   => Annotation\PreUpdate::class,
692 8
                Events::postUpdate  => Annotation\PostUpdate::class,
693 8
                Events::preRemove   => Annotation\PreRemove::class,
694 8
                Events::postRemove  => Annotation\PostRemove::class,
695 8
                Events::postLoad    => Annotation\PostLoad::class,
696 8
                Events::preFlush    => Annotation\PreFlush::class,
697
            ];
698
699 8
            foreach ($entityListenersAnnot->value as $listenerClassName) {
700 8
                if (! class_exists($listenerClassName)) {
701
                    throw Mapping\MappingException::entityListenerClassNotFound(
702
                        $listenerClassName,
703
                        $metadata->getClassName()
704
                    );
705
                }
706
707 8
                $listenerClass = new ReflectionClass($listenerClassName);
708
709
                /** @var ReflectionMethod $reflectionMethod */
710 8
                foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
711 8
                    $annotations = $this->getMethodAnnotations($reflectionMethod);
712
713 8
                    foreach ($eventMap as $eventName => $annotationClassName) {
714 8
                        if (isset($annotations[$annotationClassName])) {
715 6
                            $metadata->addEntityListener($eventName, $listenerClassName, $reflectionMethod->getName());
716
                        }
717
                    }
718
                }
719
            }
720
        }
721 373
    }
722
723
    /**
724
     * @param Annotation\Annotation[] $classAnnotations
725
     *
726
     * @throws Mapping\MappingException
727
     */
728 370
    private function attachPropertyOverrides(
729
        array $classAnnotations,
730
        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

730
        /** @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...
731
        Mapping\ClassMetadata $metadata,
732
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
733
    ) : void {
734
        // Evaluate AssociationOverrides annotation
735 370
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
736 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
737
738 5
            foreach ($associationOverridesAnnot->value as $associationOverrideAnnotation) {
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...
739 5
                $fieldName = $associationOverrideAnnotation->name;
740 5
                $property  = $metadata->getProperty($fieldName);
741
742 5
                if (! $property) {
743
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
744
                }
745
746 5
                $override = clone $property;
747
748
                // Check for JoinColumn/JoinColumns annotations
749 5
                if ($associationOverrideAnnotation->joinColumns) {
750 3
                    $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext);
751
752
                    $joinColumnBuilder
753 3
                        ->withComponentMetadata($metadata)
754 3
                        ->withFieldName($fieldName);
755
756 3
                    $joinColumns = [];
757
758 3
                    foreach ($associationOverrideAnnotation->joinColumns as $joinColumnAnnotation) {
759 3
                        $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation);
760
761 3
                        $joinColumnMetadata = $joinColumnBuilder->build();
762 3
                        $columnName         = $joinColumnMetadata->getColumnName();
763
764
                        // @todo guilhermeblanco Open an issue to discuss making this scenario impossible.
765
                        //if ($metadata->checkPropertyDuplication($columnName)) {
766
                        //    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
767
                        //}
768
769 3
                        if ($override->isOwningSide()) {
0 ignored issues
show
Bug introduced by
The method isOwningSide() 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

769
                        if ($override->/** @scrutinizer ignore-call */ isOwningSide()) {
Loading history...
770 3
                            $metadata->fieldNames[$columnName] = $fieldName;
771
                        }
772
773 3
                        $joinColumns[] = $joinColumnMetadata;
774
                    }
775
776 3
                    $override->setJoinColumns($joinColumns);
0 ignored issues
show
Bug introduced by
The method setJoinColumns() 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\ToOneAssociationMetadata. ( Ignorable by Annotation )

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

776
                    $override->/** @scrutinizer ignore-call */ 
777
                               setJoinColumns($joinColumns);
Loading history...
777
                }
778
779
                // Check for JoinTable annotations
780 5
                if ($associationOverrideAnnotation->joinTable) {
781 2
                    $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext);
782
783
                    $joinTableBuilder
784 2
                        ->withComponentMetadata($metadata)
785 2
                        ->withFieldName($fieldName)
786 2
                        ->withTargetEntity($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

786
                        ->withTargetEntity($property->/** @scrutinizer ignore-call */ getTargetEntity())
Loading history...
787 2
                        ->withJoinTableAnnotation($associationOverrideAnnotation->joinTable);
788
789 2
                    $override->setJoinTable($joinTableBuilder->build());
0 ignored issues
show
Bug introduced by
The method setJoinTable() 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\ManyToManyAssociationMetadata. ( Ignorable by Annotation )

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

789
                    $override->/** @scrutinizer ignore-call */ 
790
                               setJoinTable($joinTableBuilder->build());
Loading history...
790
                }
791
792
                // Check for inversedBy
793 5
                if ($associationOverrideAnnotation->inversedBy) {
794 1
                    $override->setInversedBy($associationOverrideAnnotation->inversedBy);
0 ignored issues
show
Bug introduced by
The method setInversedBy() 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

794
                    $override->/** @scrutinizer ignore-call */ 
795
                               setInversedBy($associationOverrideAnnotation->inversedBy);
Loading history...
795
                }
796
797
                // Check for fetch
798 5
                if ($associationOverrideAnnotation->fetch) {
799 1
                    $override->setFetchMode(constant(Mapping\FetchMode::class . '::' . $associationOverrideAnnotation->fetch));
0 ignored issues
show
Bug introduced by
The method setFetchMode() 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

799
                    $override->/** @scrutinizer ignore-call */ 
800
                               setFetchMode(constant(Mapping\FetchMode::class . '::' . $associationOverrideAnnotation->fetch));
Loading history...
800
                }
801
802 5
                $metadata->setPropertyOverride($override);
803
            }
804
        }
805
806
        // Evaluate AttributeOverrides annotation
807 370
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
808 3
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
809 3
            $fieldBuilder            = new Builder\FieldMetadataBuilder($metadataBuildingContext);
810
811
            $fieldBuilder
812 3
                ->withComponentMetadata($metadata)
813 3
                ->withIdAnnotation(null)
814 3
                ->withVersionAnnotation(null);
815
816 3
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnotation) {
817 3
                $fieldName = $attributeOverrideAnnotation->name;
818 3
                $property  = $metadata->getProperty($fieldName);
819
820 3
                if (! $property) {
821
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
822
                }
823
824
                $fieldBuilder
825 3
                    ->withFieldName($fieldName)
826 3
                    ->withColumnAnnotation($attributeOverrideAnnotation->column);
827
828 3
                $fieldMetadata = $fieldBuilder->build();
829 3
                $columnName    = $fieldMetadata->getColumnName();
830
831
                // Prevent column duplication
832 3
                if ($metadata->checkPropertyDuplication($columnName)) {
0 ignored issues
show
Bug introduced by
It seems like $columnName 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

832
                if ($metadata->checkPropertyDuplication(/** @scrutinizer ignore-type */ $columnName)) {
Loading history...
833
                    throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName);
834
                }
835
836 3
                $metadata->fieldNames[$fieldMetadata->getColumnName()] = $fieldName;
837
838 3
                $metadata->setPropertyOverride($fieldMetadata);
839
            }
840
        }
841 370
    }
842
843
    /**
844
     * Attempts to resolve the cascade modes.
845
     *
846
     * @param string   $className        The class name.
847
     * @param string   $fieldName        The field name.
848
     * @param string[] $originalCascades The original unprocessed field cascades.
849
     *
850
     * @return string[] The processed field cascades.
851
     *
852
     * @throws Mapping\MappingException If a cascade option is not valid.
853
     */
854
    private function getCascade(string $className, string $fieldName, array $originalCascades) : array
0 ignored issues
show
Unused Code introduced by
The method getCascade() is not used, and could be removed.

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

Loading history...
855
    {
856
        $cascadeTypes = ['remove', 'persist', 'refresh'];
857
        $cascades     = array_map('strtolower', $originalCascades);
858
859
        if (in_array('all', $cascades, true)) {
860
            $cascades = $cascadeTypes;
861
        }
862
863
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
864
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
865
866
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
867
        }
868
869
        return $cascades;
870
    }
871
872
    /**
873
     * Attempts to resolve the fetch mode.
874
     *
875
     * @param string $className The class name.
876
     * @param string $fetchMode The fetch mode.
877
     *
878
     * @return string The fetch mode as defined in ClassMetadata.
879
     *
880
     * @throws Mapping\MappingException If the fetch mode is not valid.
881
     */
882
    private function getFetchMode($className, $fetchMode) : string
0 ignored issues
show
Unused Code introduced by
The method getFetchMode() is not used, and could be removed.

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

Loading history...
883
    {
884
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
885
886
        if (! defined($fetchModeConstant)) {
887
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
888
        }
889
890
        return constant($fetchModeConstant);
891
    }
892
893
    /**
894
     * @return Annotation\Annotation[]
895
     */
896 379
    private function getClassAnnotations(ReflectionClass $reflectionClass) : array
897
    {
898 379
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
899
900 379
        foreach ($classAnnotations as $key => $annot) {
901 373
            if (! is_numeric($key)) {
902
                continue;
903
            }
904
905 373
            $classAnnotations[get_class($annot)] = $annot;
906
        }
907
908 379
        return $classAnnotations;
909
    }
910
911
    /**
912
     * @return Annotation\Annotation[]
913
     */
914 373
    private function getPropertyAnnotations(ReflectionProperty $reflectionProperty) : array
915
    {
916 373
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
917
918 372
        foreach ($propertyAnnotations as $key => $annot) {
919 372
            if (! is_numeric($key)) {
920
                continue;
921
            }
922
923 372
            $propertyAnnotations[get_class($annot)] = $annot;
924
        }
925
926 372
        return $propertyAnnotations;
927
    }
928
929
    /**
930
     * @return Annotation\Annotation[]
931
     */
932 21
    private function getMethodAnnotations(ReflectionMethod $reflectionMethod) : array
933
    {
934 21
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
935
936 21
        foreach ($methodAnnotations as $key => $annot) {
937 18
            if (! is_numeric($key)) {
938
                continue;
939
            }
940
941 18
            $methodAnnotations[get_class($annot)] = $annot;
942
        }
943
944 21
        return $methodAnnotations;
945
    }
946
}
947