Failed Conditions
Push — master ( becf73...b9880b )
by Guilherme
09:53
created

convertReflectionPropertyToFieldMetadata()   F

Complexity

Conditions 19
Paths 673

Size

Total Lines 119
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 20.1584

Importance

Changes 0
Metric Value
cc 19
eloc 63
nc 673
nop 4
dl 0
loc 119
ccs 52
cts 61
cp 0.8525
crap 20.1584
rs 0.8041
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
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;
10
use Doctrine\ORM\Annotation;
11
use Doctrine\ORM\Cache\Exception\CacheException;
12
use Doctrine\ORM\Events;
13
use Doctrine\ORM\Mapping;
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
use function strtolower;
46
use function strtoupper;
47
use function var_export;
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 2299
    public function __construct(Reader $reader, $paths = null)
103
    {
104 2299
        $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 2299
        if ($paths) {
107 2213
            $this->addPaths((array) $paths);
108
        }
109 2299
    }
110
111
    /**
112
     * Appends lookup paths to metadata driver.
113
     *
114
     * @param string[] $paths
115
     */
116 2217
    public function addPaths(array $paths)
117
    {
118 2217
        $this->paths = array_unique(array_merge($this->paths, $paths));
119 2217
    }
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 $annot) {
197 188
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
198 188
                return false;
199
            }
200
        }
201
202 12
        return true;
203
    }
204
205
    /**
206
     * {@inheritDoc}
207
     */
208 60
    public function getAllClassNames() : array
209
    {
210 60
        if ($this->classNames !== null) {
211 45
            return $this->classNames;
212
        }
213
214 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...
215
            throw Mapping\MappingException::pathRequired();
216
        }
217
218 60
        $classes       = [];
219 60
        $includedFiles = [];
220
221 60
        foreach ($this->paths as $path) {
222 60
            if (! is_dir($path)) {
223
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
224
            }
225
226 60
            $iterator = new RegexIterator(
227 60
                new RecursiveIteratorIterator(
228 60
                    new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
229 60
                    RecursiveIteratorIterator::LEAVES_ONLY
230
                ),
231 60
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
232 60
                RecursiveRegexIterator::GET_MATCH
233
            );
234
235 60
            foreach ($iterator as $file) {
236 60
                $sourceFile = $file[0];
237
238 60
                if (! preg_match('(^phar:)i', $sourceFile)) {
239 60
                    $sourceFile = realpath($sourceFile);
240
                }
241
242 60
                foreach ($this->excludePaths as $excludePath) {
243
                    $exclude = str_replace('\\', '/', realpath($excludePath));
244
                    $current = str_replace('\\', '/', $sourceFile);
245
246
                    if (strpos($current, $exclude) !== false) {
247
                        continue 2;
248
                    }
249
                }
250
251 60
                require_once $sourceFile;
252
253 60
                $includedFiles[] = $sourceFile;
254
            }
255
        }
256
257 60
        $declared = get_declared_classes();
258
259 60
        foreach ($declared as $className) {
260 60
            $rc         = new ReflectionClass($className);
261 60
            $sourceFile = $rc->getFileName();
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 380
    public function loadMetadataForClass(
282
        string $className,
283
        ?Mapping\ComponentMetadata $parent,
284
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
285
    ) : Mapping\ComponentMetadata {
286 380
        $reflectionClass  = new ReflectionClass($className);
287 380
        $metadata         = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
288 380
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
289 380
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
290 380
            $classAnnotations,
291 380
            $reflectionClass,
292 380
            $metadata,
293 380
            $metadataBuildingContext
294
        );
295
296
        // Evaluate @Cache annotation
297 374
        if (isset($classAnnotations[Annotation\Cache::class])) {
298 18
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
299 18
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
300
301 18
            $classMetadata->setCache($cache);
302
        }
303
304
        // Evaluate annotations on properties/fields
305
        /** @var ReflectionProperty $reflProperty */
306 374
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
307 374
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
308 74
                continue;
309
            }
310
311 374
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
312 373
            $property            = $this->convertPropertyAnnotationsToProperty(
313 373
                $propertyAnnotations,
314 373
                $reflectionProperty,
315 373
                $classMetadata,
316 373
                $metadataBuildingContext
317
            );
318
319 373
            if ($classMetadata->isMappedSuperclass &&
320 373
                $property instanceof Mapping\ToManyAssociationMetadata &&
321 373
                ! $property->isOwningSide()) {
322 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
323 1
                    $classMetadata->getClassName(),
324 1
                    $property->getName()
325
                );
326
            }
327
328 372
            $metadata->addProperty($property);
329
        }
330
331 371
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
332
333 371
        return $classMetadata;
334
    }
335
336
    /**
337
     * @param Annotation\Annotation[] $classAnnotations
338
     *
339
     * @throws Mapping\MappingException
340
     * @throws UnexpectedValueException
341
     * @throws ReflectionException
342
     */
343 380
    private function convertClassAnnotationsToClassMetadata(
344
        array $classAnnotations,
345
        ReflectionClass $reflectionClass,
346
        Mapping\ClassMetadata $metadata,
347
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
348
    ) : Mapping\ClassMetadata {
349
        switch (true) {
350 380
            case isset($classAnnotations[Annotation\Entity::class]):
351 373
                return $this->convertClassAnnotationsToEntityClassMetadata(
352 373
                    $classAnnotations,
353 373
                    $reflectionClass,
354 373
                    $metadata,
355 373
                    $metadataBuildingContext
356
                );
357
358
                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...
359
360 29
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
361 23
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
362 23
                    $classAnnotations,
363 23
                    $reflectionClass,
364 23
                    $metadata
365
                );
366 6
            case isset($classAnnotations[Annotation\Embeddable::class]):
367
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
368
                    $classAnnotations,
369
                    $reflectionClass,
370
                    $metadata
371
                );
372
            default:
373 6
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
374
        }
375
    }
376
377
    /**
378
     * @param Annotation\Annotation[] $classAnnotations
379
     *
380
     * @return Mapping\ClassMetadata
381
     *
382
     * @throws Mapping\MappingException
383
     * @throws ReflectionException
384
     * @throws UnexpectedValueException
385
     */
386 373
    private function convertClassAnnotationsToEntityClassMetadata(
387
        array $classAnnotations,
388
        ReflectionClass $reflectionClass,
389
        Mapping\ClassMetadata $metadata,
390
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
391
    ) {
392
        /** @var Annotation\Entity $entityAnnot */
393 373
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
394
395 373
        if ($entityAnnot->repositoryClass !== null) {
396 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
397
        }
398
399 373
        if ($entityAnnot->readOnly) {
400 1
            $metadata->asReadOnly();
401
        }
402
403 373
        $metadata->isMappedSuperclass = false;
404 373
        $metadata->isEmbeddedClass    = false;
405
406 373
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
407
408
        // Evaluate @ChangeTrackingPolicy annotation
409 373
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
410 6
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
411
412 6
            $metadata->setChangeTrackingPolicy(
413 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...
414
            );
415
        }
416
417
        // Evaluate @InheritanceType annotation
418 373
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
419 80
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
420
421 80
            $metadata->setInheritanceType(
422 80
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
423
            );
424
425 80
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
426 80
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
427
            }
428
        }
429
430 373
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
431 373
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
432
433 373
        return $metadata;
434
    }
435
436
    /**
437
     * @param Annotation\Annotation[] $classAnnotations
438
     *
439
     * @throws Mapping\MappingException
440
     * @throws ReflectionException
441
     */
442 23
    private function convertClassAnnotationsToMappedSuperClassMetadata(
443
        array $classAnnotations,
444
        ReflectionClass $reflectionClass,
445
        Mapping\ClassMetadata $metadata
446
    ) : Mapping\ClassMetadata {
447
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
448 23
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
449
450 23
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
451 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
452
        }
453
454 23
        $metadata->isMappedSuperclass = true;
455 23
        $metadata->isEmbeddedClass    = false;
456
457 23
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
458 23
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
459
460 23
        return $metadata;
461
    }
462
463
    /**
464
     * @param Annotation\Annotation[] $classAnnotations
465
     */
466
    private function convertClassAnnotationsToEmbeddableClassMetadata(
467
        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

467
        /** @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...
468
        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

468
        /** @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...
469
        Mapping\ClassMetadata $metadata
470
    ) : Mapping\ClassMetadata {
471
        $metadata->isMappedSuperclass = false;
472
        $metadata->isEmbeddedClass    = true;
473
474
        return $metadata;
475
    }
476
477
    /**
478
     * @param Annotation\Annotation[] $propertyAnnotations
479
     *
480
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
481
     */
482 373
    private function convertPropertyAnnotationsToProperty(
483
        array $propertyAnnotations,
484
        ReflectionProperty $reflectionProperty,
485
        Mapping\ClassMetadata $metadata,
486
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
487
    ) : ?Mapping\Property {
488
        switch (true) {
489 373
            case isset($propertyAnnotations[Annotation\Column::class]):
490 368
                return $this->convertReflectionPropertyToFieldMetadata(
491 368
                    $reflectionProperty,
492 368
                    $propertyAnnotations,
493 368
                    $metadata,
494 368
                    $metadataBuildingContext
495
                );
496 255
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
497 112
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
498 112
                    $reflectionProperty,
499 112
                    $propertyAnnotations,
500 112
                    $metadata,
501 112
                    $metadataBuildingContext
502
                );
503 203
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
504 141
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
505 141
                    $reflectionProperty,
506 141
                    $propertyAnnotations,
507 141
                    $metadata,
508 141
                    $metadataBuildingContext
509
                );
510 164
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
511 109
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
512 109
                    $reflectionProperty,
513 109
                    $propertyAnnotations,
514 109
                    $metadata,
515 109
                    $metadataBuildingContext
516
                );
517 105
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
518 89
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
519 89
                    $reflectionProperty,
520 89
                    $propertyAnnotations,
521 89
                    $metadata,
522 89
                    $metadataBuildingContext
523
                );
524 30
            case isset($propertyAnnotations[Annotation\Embedded::class]):
525
                return null;
526
            default:
527 30
                return new Mapping\TransientMetadata($reflectionProperty->getName());
528
        }
529
    }
530
531
    /**
532
     * @param Annotation\Annotation[] $propertyAnnotations
533
     *
534
     * @throws Mapping\MappingException
535
     */
536 368
    private function convertReflectionPropertyToFieldMetadata(
537
        ReflectionProperty $reflectionProperty,
538
        array $propertyAnnotations,
539
        Mapping\ClassMetadata $metadata,
540
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
541
    ) : Mapping\FieldMetadata {
542 368
        $className   = $metadata->getClassName();
543 368
        $fieldName   = $reflectionProperty->getName();
544 368
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
545 368
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
546
547 368
        if ($columnAnnot->type === null) {
0 ignored issues
show
Bug introduced by
Accessing type on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
548
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
549
        }
550
551 368
        $fieldMetadata = new Mapping\FieldMetadata($fieldName);
552 368
        $columnName    = ! empty($columnAnnot->name)
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
553 77
            ? $columnAnnot->name
554 368
            : $metadataBuildingContext->getNamingStrategy()->propertyToColumnName($fieldName, $className);
555
556 368
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
557 368
        $fieldMetadata->setVersioned($isVersioned);
558 368
        $fieldMetadata->setColumnName($columnName);
559
560 368
        if (! $metadata->isMappedSuperclass) {
561 361
            $fieldMetadata->setTableName($metadata->getTableName());
562
        }
563
564 368
        if (! empty($columnAnnot->columnDefinition)) {
0 ignored issues
show
Bug introduced by
Accessing columnDefinition on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
565 4
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
566
        }
567
568 368
        if (! empty($columnAnnot->length)) {
0 ignored issues
show
Bug introduced by
Accessing length on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
569 368
            $fieldMetadata->setLength($columnAnnot->length);
570
        }
571
572 368
        if ($columnAnnot->options) {
0 ignored issues
show
Bug introduced by
Accessing options on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
573 7
            $fieldMetadata->setOptions($columnAnnot->options);
574
        }
575
576 368
        $fieldMetadata->setScale($columnAnnot->scale);
0 ignored issues
show
Bug introduced by
Accessing scale on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
577 368
        $fieldMetadata->setPrecision($columnAnnot->precision);
0 ignored issues
show
Bug introduced by
Accessing precision on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
578 368
        $fieldMetadata->setNullable($columnAnnot->nullable);
0 ignored issues
show
Bug introduced by
Accessing nullable on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
579 368
        $fieldMetadata->setUnique($columnAnnot->unique);
0 ignored issues
show
Bug introduced by
Accessing unique on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
580
581
        // Check for Id
582 368
        if (isset($propertyAnnotations[Annotation\Id::class])) {
583 364
            $fieldMetadata->setPrimaryKey(true);
584
585 364
            if ($fieldMetadata->getType()->canRequireSQLConversion()) {
586
                throw Mapping\MappingException::sqlConversionNotAllowedForPrimaryKeyProperties($className, $fieldMetadata);
587
            }
588
        }
589
590
        // Prevent PK and version on same field
591 368
        if ($fieldMetadata->isPrimaryKey() && $fieldMetadata->isVersioned()) {
592
            throw Mapping\MappingException::cannotVersionIdField($className, $fieldName);
593
        }
594
595
        // Prevent column duplication
596 368
        if ($metadata->checkPropertyDuplication($columnName)) {
597
            throw Mapping\MappingException::duplicateColumnName($className, $columnName);
598
        }
599
600
        // Check for GeneratedValue strategy
601 368
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
602 312
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
603 312
            $strategy            = strtoupper($generatedValueAnnot->strategy);
0 ignored issues
show
Bug introduced by
Accessing strategy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
604 312
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
605
606 312
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
607 292
                $idGeneratorDefinition = [];
608
609
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
610
                switch (true) {
611 292
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
612 9
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
613
614
                        $idGeneratorDefinition = [
615 9
                            'sequenceName' => $seqGeneratorAnnot->sequenceName,
0 ignored issues
show
Bug introduced by
Accessing sequenceName on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
616 9
                            'allocationSize' => $seqGeneratorAnnot->allocationSize,
0 ignored issues
show
Bug introduced by
Accessing allocationSize on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
617
                        ];
618
619 9
                        break;
620
621 283
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
622 3
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
623
624
                        $idGeneratorDefinition = [
625 3
                            'class' => $customGeneratorAnnot->class,
626 3
                            'arguments' => $customGeneratorAnnot->arguments,
627
                        ];
628
629 3
                        if (! isset($idGeneratorDefinition['class'])) {
630
                            throw new Mapping\MappingException(
631
                                sprintf('Cannot instantiate custom generator, no class has been defined')
632
                            );
633
                        }
634
635 3
                        if (! class_exists($idGeneratorDefinition['class'])) {
636
                            throw new Mapping\MappingException(
637
                                sprintf('Cannot instantiate custom generator : %s', var_export($idGeneratorDefinition, true))
638
                            );
639
                        }
640
641 3
                        break;
642
643
                    /** @todo If it is not supported, why does this exist? */
644 280
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
645
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
646
                }
647
648 292
                $fieldMetadata->setValueGenerator(
649 292
                    new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition)
650
                );
651
            }
652
        }
653
654 368
        return $fieldMetadata;
655
    }
656
657
    /**
658
     * @param Annotation\Annotation[] $propertyAnnotations
659
     *
660
     * @return Mapping\OneToOneAssociationMetadata
661
     */
662 112
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
663
        ReflectionProperty $reflectionProperty,
664
        array $propertyAnnotations,
665
        Mapping\ClassMetadata $metadata,
666
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext 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

666
        /** @scrutinizer ignore-unused */ Mapping\ClassMetadataBuildingContext $metadataBuildingContext

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...
667
    ) {
668 112
        $className     = $metadata->getClassName();
669 112
        $fieldName     = $reflectionProperty->getName();
670 112
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
671 112
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
672 112
        $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...
673
674 112
        $assocMetadata->setTargetEntity($targetEntity);
675 112
        $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...
676 112
        $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...
677 112
        $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...
678
679 112
        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...
680 40
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
681 40
            $assocMetadata->setOwningSide(false);
682
        }
683
684 112
        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...
685 52
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
686
        }
687
688
        // Check for Id
689 112
        if (isset($propertyAnnotations[Annotation\Id::class])) {
690 12
            $assocMetadata->setPrimaryKey(true);
691
        }
692
693 112
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
694
695
        // Check for JoinColumn/JoinColumns annotations
696
        switch (true) {
697 112
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
698 80
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
699
700 80
                $assocMetadata->addJoinColumn(
701 80
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
702
                );
703
704 80
                break;
705
706 53
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
707 3
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
708
709 3
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
710 3
                    $assocMetadata->addJoinColumn(
711 3
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
712
                    );
713
                }
714
715 3
                break;
716
        }
717
718 112
        return $assocMetadata;
719
    }
720
721
    /**
722
     * @param Annotation\Annotation[] $propertyAnnotations
723
     *
724
     * @return Mapping\ManyToOneAssociationMetadata
725
     */
726 141
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
727
        ReflectionProperty $reflectionProperty,
728
        array $propertyAnnotations,
729
        Mapping\ClassMetadata $metadata,
730
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext 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 */ Mapping\ClassMetadataBuildingContext $metadataBuildingContext

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
    ) {
732 141
        $className      = $metadata->getClassName();
733 141
        $fieldName      = $reflectionProperty->getName();
734 141
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
735 141
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
736 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...
737
738 141
        $assocMetadata->setTargetEntity($targetEntity);
739 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...
740 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...
741
742 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...
743 94
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
744
        }
745
746
        // Check for Id
747 141
        if (isset($propertyAnnotations[Annotation\Id::class])) {
748 34
            $assocMetadata->setPrimaryKey(true);
749
        }
750
751 141
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
752
753
        // Check for JoinColumn/JoinColumns annotations
754
        switch (true) {
755 141
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
756 81
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
757
758 81
                $assocMetadata->addJoinColumn(
759 81
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
760
                );
761
762 81
                break;
763
764 69
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
765 16
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
766
767 16
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
768 16
                    $assocMetadata->addJoinColumn(
769 16
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
770
                    );
771
                }
772
773 16
                break;
774
        }
775
776 141
        return $assocMetadata;
777
    }
778
779
    /**
780
     * @param Annotation\Annotation[] $propertyAnnotations
781
     *
782
     * @throws Mapping\MappingException
783
     */
784 109
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
785
        ReflectionProperty $reflectionProperty,
786
        array $propertyAnnotations,
787
        Mapping\ClassMetadata $metadata,
788
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext 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

788
        /** @scrutinizer ignore-unused */ Mapping\ClassMetadataBuildingContext $metadataBuildingContext

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...
789
    ) : Mapping\OneToManyAssociationMetadata {
790 109
        $className      = $metadata->getClassName();
791 109
        $fieldName      = $reflectionProperty->getName();
792 109
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
793 109
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
794 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...
795
796 109
        $assocMetadata->setTargetEntity($targetEntity);
797 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...
798 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...
799 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...
800 109
        $assocMetadata->setOwningSide(false);
801 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...
802
803 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...
804 8
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
805
        }
806
807
        // Check for OrderBy
808 109
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
809 14
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
810
811 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...
812
        }
813
814
        // Check for Id
815 109
        if (isset($propertyAnnotations[Annotation\Id::class])) {
816
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
817
        }
818
819 109
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
820
821 109
        return $assocMetadata;
822
    }
823
824
    /**
825
     * @param Annotation\Annotation[] $propertyAnnotations
826
     *
827
     * @throws Mapping\MappingException
828
     */
829 89
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
830
        ReflectionProperty $reflectionProperty,
831
        array $propertyAnnotations,
832
        Mapping\ClassMetadata $metadata,
833
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext 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

833
        /** @scrutinizer ignore-unused */ Mapping\ClassMetadataBuildingContext $metadataBuildingContext

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...
834
    ) : Mapping\ManyToManyAssociationMetadata {
835 89
        $className       = $metadata->getClassName();
836 89
        $fieldName       = $reflectionProperty->getName();
837 89
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
838 89
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
839 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...
840
841 89
        $assocMetadata->setTargetEntity($targetEntity);
842 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...
843 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...
844 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...
845
846 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...
847 36
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
848 36
            $assocMetadata->setOwningSide(false);
849
        }
850
851 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...
852 45
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
853
        }
854
855 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...
856 3
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
857
        }
858
859
        // Check for JoinTable
860 89
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
861 71
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
862 71
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
863
864 71
            $assocMetadata->setJoinTable($joinTableMetadata);
865
        }
866
867
        // Check for OrderBy
868 89
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
869 3
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
870
871 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...
872
        }
873
874
        // Check for Id
875 89
        if (isset($propertyAnnotations[Annotation\Id::class])) {
876
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
877
        }
878
879 89
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
880
881 89
        return $assocMetadata;
882
    }
883
884
    /**
885
     * Parse the given Column as FieldMetadata
886
     */
887 3
    private function convertColumnAnnotationToFieldMetadata(
888
        Annotation\Column $columnAnnot,
889
        string $fieldName,
890
        bool $isVersioned
891
    ) : Mapping\FieldMetadata {
892 3
        $fieldMetadata = new Mapping\FieldMetadata($fieldName);
893
894 3
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
895 3
        $fieldMetadata->setVersioned($isVersioned);
896
897 3
        if (! empty($columnAnnot->name)) {
898 3
            $fieldMetadata->setColumnName($columnAnnot->name);
899
        }
900
901 3
        if (! empty($columnAnnot->columnDefinition)) {
902
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
903
        }
904
905 3
        if (! empty($columnAnnot->length)) {
906 3
            $fieldMetadata->setLength($columnAnnot->length);
907
        }
908
909 3
        if ($columnAnnot->options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $columnAnnot->options of type array 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...
910
            $fieldMetadata->setOptions($columnAnnot->options);
911
        }
912
913 3
        $fieldMetadata->setScale($columnAnnot->scale);
914 3
        $fieldMetadata->setPrecision($columnAnnot->precision);
915 3
        $fieldMetadata->setNullable($columnAnnot->nullable);
916 3
        $fieldMetadata->setUnique($columnAnnot->unique);
917
918 3
        return $fieldMetadata;
919
    }
920
921
    /**
922
     * Parse the given Table as TableMetadata
923
     */
924 192
    private function convertTableAnnotationToTableMetadata(
925
        Annotation\Table $tableAnnot,
926
        Mapping\TableMetadata $tableMetadata
927
    ) : Mapping\TableMetadata {
928 192
        if (! empty($tableAnnot->name)) {
929 187
            $tableMetadata->setName($tableAnnot->name);
930
        }
931
932 192
        if (! empty($tableAnnot->schema)) {
933 5
            $tableMetadata->setSchema($tableAnnot->schema);
934
        }
935
936 192
        foreach ($tableAnnot->options as $optionName => $optionValue) {
937 4
            $tableMetadata->addOption($optionName, $optionValue);
938
        }
939
940 192
        foreach ($tableAnnot->indexes as $indexAnnot) {
941 13
            $tableMetadata->addIndex([
942 13
                'name'    => $indexAnnot->name,
943 13
                'columns' => $indexAnnot->columns,
944 13
                'unique'  => $indexAnnot->unique,
945 13
                'options' => $indexAnnot->options,
946 13
                'flags'   => $indexAnnot->flags,
947
            ]);
948
        }
949
950 192
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
951 6
            $tableMetadata->addUniqueConstraint([
952 6
                'name'    => $uniqueConstraintAnnot->name,
953 6
                'columns' => $uniqueConstraintAnnot->columns,
954 6
                'options' => $uniqueConstraintAnnot->options,
955 6
                'flags'   => $uniqueConstraintAnnot->flags,
956
            ]);
957
        }
958
959 192
        return $tableMetadata;
960
    }
961
962
    /**
963
     * Parse the given JoinTable as JoinTableMetadata
964
     */
965 71
    private function convertJoinTableAnnotationToJoinTableMetadata(
966
        Annotation\JoinTable $joinTableAnnot
967
    ) : Mapping\JoinTableMetadata {
968 71
        $joinTable = new Mapping\JoinTableMetadata();
969
970 71
        if (! empty($joinTableAnnot->name)) {
971 69
            $joinTable->setName($joinTableAnnot->name);
972
        }
973
974 71
        if (! empty($joinTableAnnot->schema)) {
975
            $joinTable->setSchema($joinTableAnnot->schema);
976
        }
977
978 71
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
979 70
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
980
981 70
            $joinTable->addJoinColumn($joinColumn);
982
        }
983
984 71
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
985 70
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
986
987 70
            $joinTable->addInverseJoinColumn($joinColumn);
988
        }
989
990 71
        return $joinTable;
991
    }
992
993
    /**
994
     * Parse the given JoinColumn as JoinColumnMetadata
995
     */
996 182
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
997
        Annotation\JoinColumn $joinColumnAnnot
998
    ) : Mapping\JoinColumnMetadata {
999 182
        $joinColumn = new Mapping\JoinColumnMetadata();
1000
1001
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
1002 182
        if (! empty($joinColumnAnnot->name)) {
1003 175
            $joinColumn->setColumnName($joinColumnAnnot->name);
1004
        }
1005
1006 182
        if (! empty($joinColumnAnnot->referencedColumnName)) {
1007 182
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
1008
        }
1009
1010 182
        $joinColumn->setNullable($joinColumnAnnot->nullable);
1011 182
        $joinColumn->setUnique($joinColumnAnnot->unique);
1012
1013 182
        if (! empty($joinColumnAnnot->fieldName)) {
1014
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
1015
        }
1016
1017 182
        if (! empty($joinColumnAnnot->columnDefinition)) {
1018 3
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
1019
        }
1020
1021 182
        if ($joinColumnAnnot->onDelete) {
1022 16
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
1023
        }
1024
1025 182
        return $joinColumn;
1026
    }
1027
1028
    /**
1029
     * Parse the given Cache as CacheMetadata
1030
     *
1031
     * @param string|null $fieldName
1032
     */
1033 18
    private function convertCacheAnnotationToCacheMetadata(
1034
        Annotation\Cache $cacheAnnot,
1035
        Mapping\ClassMetadata $metadata,
1036
        $fieldName = null
1037
    ) : Mapping\CacheMetadata {
1038 18
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
1039 18
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
1040
1041 18
        $usage  = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
1042 18
        $region = $cacheAnnot->region ?: $defaultRegion;
1043
1044 18
        return new Mapping\CacheMetadata($usage, $region);
1045
    }
1046
1047
    /**
1048
     * @param Annotation\Annotation[] $classAnnotations
1049
     */
1050 373
    private function attachTable(
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 373
        $parent = $metadata->getParent();
1057
1058 373
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
1059
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
1060
            do {
1061 29
                if (! $parent->isMappedSuperclass) {
1062 29
                    $metadata->setTable($parent->table);
1063
1064 29
                    break;
1065
                }
1066
1067 4
                $parent = $parent->getParent();
1068 4
            } while ($parent !== null);
1069
1070 29
            return;
1071
        }
1072
1073 373
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1074 373
        $tableMetadata  = new Mapping\TableMetadata();
1075
1076 373
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1077
1078
        // Evaluate @Table annotation
1079 373
        if (isset($classAnnotations[Annotation\Table::class])) {
1080 192
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1081
1082 192
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1083
        }
1084
1085 373
        $metadata->setTable($tableMetadata);
1086 373
    }
1087
1088
    /**
1089
     * @param Annotation\Annotation[] $classAnnotations
1090
     *
1091
     * @throws Mapping\MappingException
1092
     */
1093 80
    private function attachDiscriminatorColumn(
1094
        array $classAnnotations,
1095
        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

1095
        /** @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...
1096
        Mapping\ClassMetadata $metadata,
1097
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext 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

1097
        /** @scrutinizer ignore-unused */ Mapping\ClassMetadataBuildingContext $metadataBuildingContext

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...
1098
    ) : void {
1099 80
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1100
1101 80
        $discriminatorColumn->setTableName($metadata->getTableName());
1102 80
        $discriminatorColumn->setColumnName('dtype');
1103 80
        $discriminatorColumn->setType(Type::getType('string'));
1104 80
        $discriminatorColumn->setLength(255);
1105
1106
        // Evaluate DiscriminatorColumn annotation
1107 80
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1108
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1109 62
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1110 62
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1111 58
                ? $discriminatorColumnAnnotation->type
1112 62
                : 'string';
1113
1114 62
            $discriminatorColumn->setType(Type::getType($typeName));
1115 62
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1116
1117 62
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1118 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1119
            }
1120
1121 62
            if (! empty($discriminatorColumnAnnotation->length)) {
1122 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1123
            }
1124
        }
1125
1126 80
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1127
1128
        // Evaluate DiscriminatorMap annotation
1129 80
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1130 77
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1131 77
            $discriminatorMap           = $discriminatorMapAnnotation->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...
1132
1133 77
            $metadata->setDiscriminatorMap($discriminatorMap);
1134
        }
1135 80
    }
1136
1137
    /**
1138
     * @param Annotation\Annotation[] $classAnnotations
1139
     */
1140 374
    private function attachLifecycleCallbacks(
1141
        array $classAnnotations,
1142
        ReflectionClass $reflectionClass,
1143
        Mapping\ClassMetadata $metadata
1144
    ) : void {
1145
        // Evaluate @HasLifecycleCallbacks annotation
1146 374
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1147
            /** @var ReflectionMethod $method */
1148 14
            foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
1149 13
                foreach ($this->getMethodCallbacks($method) as $callback) {
1150 12
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1151
                }
1152
            }
1153
        }
1154 374
    }
1155
1156
    /**
1157
     * @param Annotation\Annotation[] $classAnnotations
1158
     *
1159
     * @throws ReflectionException
1160
     * @throws Mapping\MappingException
1161
     */
1162 374
    private function attachEntityListeners(
1163
        array $classAnnotations,
1164
        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

1164
        /** @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...
1165
        Mapping\ClassMetadata $metadata
1166
    ) : void {
1167
        // Evaluate @EntityListeners annotation
1168 374
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1169
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1170 9
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1171
1172 9
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1173 9
                if (! class_exists($listenerClassName)) {
1174
                    throw Mapping\MappingException::entityListenerClassNotFound(
1175
                        $listenerClassName,
1176
                        $metadata->getClassName()
1177
                    );
1178
                }
1179
1180 9
                $listenerClass = new ReflectionClass($listenerClassName);
1181
1182
                /** @var ReflectionMethod $method */
1183 9
                foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
1184 9
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1185 9
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1186
                    }
1187
                }
1188
            }
1189
        }
1190 374
    }
1191
1192
    /**
1193
     * @param Annotation\Annotation[] $classAnnotations
1194
     *
1195
     * @throws Mapping\MappingException
1196
     */
1197 371
    private function attachPropertyOverrides(
1198
        array $classAnnotations,
1199
        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

1199
        /** @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...
1200
        Mapping\ClassMetadata $metadata,
1201
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext 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

1201
        /** @scrutinizer ignore-unused */ Mapping\ClassMetadataBuildingContext $metadataBuildingContext

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...
1202
    ) : void {
1203
        // Evaluate AssociationOverrides annotation
1204 371
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1205 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1206
1207 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...
1208 5
                $fieldName = $associationOverride->name;
1209 5
                $property  = $metadata->getProperty($fieldName);
1210
1211 5
                if (! $property) {
1212
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1213
                }
1214
1215 5
                $existingClass = get_class($property);
1216 5
                $override      = new $existingClass($fieldName);
1217
1218
                // Check for JoinColumn/JoinColumns annotations
1219 5
                if ($associationOverride->joinColumns) {
1220 3
                    $joinColumns = [];
1221
1222 3
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1223 3
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1224
                    }
1225
1226 3
                    $override->setJoinColumns($joinColumns);
1227
                }
1228
1229
                // Check for JoinTable annotations
1230 5
                if ($associationOverride->joinTable) {
1231 2
                    $joinTableAnnot    = $associationOverride->joinTable;
1232 2
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1233
1234 2
                    $override->setJoinTable($joinTableMetadata);
1235
                }
1236
1237
                // Check for inversedBy
1238 5
                if ($associationOverride->inversedBy) {
1239 1
                    $override->setInversedBy($associationOverride->inversedBy);
1240
                }
1241
1242
                // Check for fetch
1243 5
                if ($associationOverride->fetch) {
1244 1
                    $override->setFetchMode(
1245 1
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1246
                    );
1247
                }
1248
1249 5
                $metadata->setPropertyOverride($override);
1250
            }
1251
        }
1252
1253
        // Evaluate AttributeOverrides annotation
1254 371
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1255 3
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1256
1257 3
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1258 3
                $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1259 3
                    $attributeOverrideAnnot->column,
1260 3
                    $attributeOverrideAnnot->name,
1261 3
                    false
1262
                );
1263
1264 3
                $metadata->setPropertyOverride($fieldMetadata);
1265
            }
1266
        }
1267 371
    }
1268
1269
    /**
1270
     * @param Annotation\Annotation[] $propertyAnnotations
1271
     */
1272 252
    private function attachAssociationPropertyCache(
1273
        array $propertyAnnotations,
1274
        ReflectionProperty $reflectionProperty,
1275
        Mapping\AssociationMetadata $assocMetadata,
1276
        Mapping\ClassMetadata $metadata
1277
    ) : void {
1278
        // Check for Cache
1279 252
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
1280 14
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
1281 14
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata(
1282 14
                $cacheAnnot,
1283 14
                $metadata,
1284 14
                $reflectionProperty->getName()
1285
            );
1286
1287 14
            $assocMetadata->setCache($cacheMetadata);
1288
        }
1289 252
    }
1290
1291
    /**
1292
     * Attempts to resolve the cascade modes.
1293
     *
1294
     * @param string   $className        The class name.
1295
     * @param string   $fieldName        The field name.
1296
     * @param string[] $originalCascades The original unprocessed field cascades.
1297
     *
1298
     * @return string[] The processed field cascades.
1299
     *
1300
     * @throws Mapping\MappingException If a cascade option is not valid.
1301
     */
1302 252
    private function getCascade(string $className, string $fieldName, array $originalCascades) : array
1303
    {
1304 252
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1305 252
        $cascades     = array_map('strtolower', $originalCascades);
1306
1307 252
        if (in_array('all', $cascades, true)) {
1308 23
            $cascades = $cascadeTypes;
1309
        }
1310
1311 252
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1312
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1313
1314
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1315
        }
1316
1317 252
        return $cascades;
1318
    }
1319
1320
    /**
1321
     * Attempts to resolve the fetch mode.
1322
     *
1323
     * @param string $className The class name.
1324
     * @param string $fetchMode The fetch mode.
1325
     *
1326
     * @return string The fetch mode as defined in ClassMetadata.
1327
     *
1328
     * @throws Mapping\MappingException If the fetch mode is not valid.
1329
     */
1330 252
    private function getFetchMode($className, $fetchMode) : string
1331
    {
1332 252
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1333
1334 252
        if (! defined($fetchModeConstant)) {
1335
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1336
        }
1337
1338 252
        return constant($fetchModeConstant);
1339
    }
1340
1341
    /**
1342
     * Parses the given method.
1343
     *
1344
     * @return string[]
1345
     */
1346 22
    private function getMethodCallbacks(ReflectionMethod $method) : array
1347
    {
1348 22
        $annotations = $this->getMethodAnnotations($method);
1349
        $events      = [
1350 22
            Events::prePersist  => Annotation\PrePersist::class,
1351 22
            Events::postPersist => Annotation\PostPersist::class,
1352 22
            Events::preUpdate   => Annotation\PreUpdate::class,
1353 22
            Events::postUpdate  => Annotation\PostUpdate::class,
1354 22
            Events::preRemove   => Annotation\PreRemove::class,
1355 22
            Events::postRemove  => Annotation\PostRemove::class,
1356 22
            Events::postLoad    => Annotation\PostLoad::class,
1357 22
            Events::preFlush    => Annotation\PreFlush::class,
1358
        ];
1359
1360
        // Check for callbacks
1361 22
        $callbacks = [];
1362
1363 22
        foreach ($events as $eventName => $annotationClassName) {
1364 22
            if (isset($annotations[$annotationClassName]) || $method->getName() === $eventName) {
1365 21
                $callbacks[] = $eventName;
1366
            }
1367
        }
1368
1369 22
        return $callbacks;
1370
    }
1371
1372
    /**
1373
     * @return Annotation\Annotation[]
1374
     */
1375 380
    private function getClassAnnotations(ReflectionClass $reflectionClass) : array
1376
    {
1377 380
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1378
1379 380
        foreach ($classAnnotations as $key => $annot) {
1380 374
            if (! is_numeric($key)) {
1381
                continue;
1382
            }
1383
1384 374
            $classAnnotations[get_class($annot)] = $annot;
1385
        }
1386
1387 380
        return $classAnnotations;
1388
    }
1389
1390
    /**
1391
     * @return Annotation\Annotation[]
1392
     */
1393 374
    private function getPropertyAnnotations(ReflectionProperty $reflectionProperty) : array
1394
    {
1395 374
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1396
1397 373
        foreach ($propertyAnnotations as $key => $annot) {
1398 373
            if (! is_numeric($key)) {
1399
                continue;
1400
            }
1401
1402 373
            $propertyAnnotations[get_class($annot)] = $annot;
1403
        }
1404
1405 373
        return $propertyAnnotations;
1406
    }
1407
1408
    /**
1409
     * @return Annotation\Annotation[]
1410
     */
1411 22
    private function getMethodAnnotations(ReflectionMethod $reflectionMethod) : array
1412
    {
1413 22
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1414
1415 22
        foreach ($methodAnnotations as $key => $annot) {
1416 17
            if (! is_numeric($key)) {
1417
                continue;
1418
            }
1419
1420 17
            $methodAnnotations[get_class($annot)] = $annot;
1421
        }
1422
1423 22
        return $methodAnnotations;
1424
    }
1425
1426
    /**
1427
     * Factory method for the Annotation Driver.
1428
     *
1429
     * @param string|string[] $paths
1430
     *
1431
     * @return AnnotationDriver
1432
     */
1433
    public static function create($paths = [], ?AnnotationReader $reader = null)
1434
    {
1435
        if ($reader === null) {
1436
            $reader = new AnnotationReader();
1437
        }
1438
1439
        return new self($reader, $paths);
1440
    }
1441
}
1442