Failed Conditions
Pull Request — master (#7242)
by Gabriel
08:46
created

AnnotationDriver   F

Complexity

Total Complexity 166

Size/Duplication

Total Lines 1344
Duplicated Lines 0 %

Test Coverage

Coverage 90.91%

Importance

Changes 0
Metric Value
dl 0
loc 1344
rs 0.6314
c 0
b 0
f 0
ccs 500
cts 550
cp 0.9091
wmc 166

39 Methods

Rating   Name   Duplication   Size   Complexity  
B convertTableAnnotationToTableMetadata() 0 32 6
B convertReflectionPropertyToManyToOneAssociationMetadata() 0 50 6
B convertJoinTableAnnotationToJoinTableMetadata() 0 26 5
A addPaths() 0 3 1
A setFileExtension() 0 3 1
A isTransient() 0 10 3
A getExcludePaths() 0 3 1
A getPaths() 0 3 1
A convertClassAnnotationsToEmbeddableClassMetadata() 0 9 1
B convertReflectionPropertyToOneToManyAssociationMetadata() 0 37 4
A getReader() 0 3 1
B convertJoinColumnAnnotationToJoinColumnMetadata() 0 30 6
C convertReflectionPropertyToFieldMetadata() 0 62 8
A convertCacheAnnotationToCacheMetadata() 0 12 3
B convertColumnAnnotationToFieldMetadata() 0 34 6
A getFileExtension() 0 3 1
B convertReflectionPropertyToManyToManyAssociationMetadata() 0 52 7
A addExcludePaths() 0 3 1
B convertReflectionPropertyToOneToOneAssociationMetadata() 0 56 7
A getCascade() 0 16 3
C getAllClassNames() 0 64 12
A attachAssociationPropertyCache() 0 19 2
A create() 0 7 2
A convertClassAnnotationsToMappedSuperClassMetadata() 0 19 2
B convertClassAnnotationsToEntityClassMetadata() 0 48 6
C convertPropertyAnnotationsToProperty() 0 46 7
B attachEntityListeners() 0 27 6
A __construct() 0 8 2
B convertClassAnnotationsToClassMetadata() 0 33 4
B attachTable() 0 36 6
C attachPropertyOverrides() 0 70 11
A attachLifecycleCallbacks() 0 14 4
A getPropertyAnnotations() 0 13 3
A getClassAnnotations() 0 13 3
B attachDiscriminatorColumn() 0 43 6
A getMethodAnnotations() 0 13 3
C loadMetadataForClass() 0 62 9
A getFetchMode() 0 9 2
B getMethodCallbacks() 0 26 4

How to fix   Complexity   

Complex Class

Complex classes like AnnotationDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationDriver, and based on these observations, apply Extract Interface, too.

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

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
334
    }
335
336
    /**
337
     * @param Annotation\Annotation[] $classAnnotations
338
     *
339
     * @throws Mapping\MappingException
340
     */
341 284
    private function convertClassAnnotationsToClassMetadata(
342
        array $classAnnotations,
343
        \ReflectionClass $reflectionClass,
344
        Mapping\ClassMetadata $metadata,
345
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
346
    ) : Mapping\ClassMetadata {
347
        switch (true) {
348 284
            case isset($classAnnotations[Annotation\Entity::class]):
349 280
                return $this->convertClassAnnotationsToEntityClassMetadata(
350 280
                    $classAnnotations,
351 280
                    $reflectionClass,
352 280
                    $metadata,
353 280
                    $metadataBuildingContext
354
                );
355
356
                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...
357
358 26
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
359 23
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
360 23
                    $classAnnotations,
361 23
                    $reflectionClass,
362 23
                    $metadata
363
                );
364
365 3
            case isset($classAnnotations[Annotation\Embeddable::class]):
366
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
367
                    $classAnnotations,
368
                    $reflectionClass,
369
                    $metadata
370
                );
371
372
            default:
373 3
                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 \UnexpectedValueException
384
     */
385 280
    private function convertClassAnnotationsToEntityClassMetadata(
386
        array $classAnnotations,
387
        \ReflectionClass $reflectionClass,
388
        Mapping\ClassMetadata $metadata,
389
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
390
    ) {
391
        /** @var Annotation\Entity $entityAnnot */
392 280
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
393
394 280
        if ($entityAnnot->repositoryClass !== null) {
395 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
396
        }
397
398 280
        if ($entityAnnot->readOnly) {
399
            $metadata->asReadOnly();
400
        }
401
402 280
        $metadata->isMappedSuperclass = false;
403 280
        $metadata->isEmbeddedClass    = false;
404
405 280
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
406
407
        // Evaluate @ChangeTrackingPolicy annotation
408 280
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
409 4
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
410
411 4
            $metadata->setChangeTrackingPolicy(
412 4
                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...
413
            );
414
        }
415
416
        // Evaluate @InheritanceType annotation
417 280
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
418 53
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
419
420 53
            $metadata->setInheritanceType(
421 53
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
422
            );
423
424 53
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
425 53
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
426
            }
427
        }
428
429 280
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
430 280
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
431
432 280
        return $metadata;
433
    }
434
435
    /**
436
     * @param Annotation\Annotation[] $classAnnotations
437
     */
438 23
    private function convertClassAnnotationsToMappedSuperClassMetadata(
439
        array $classAnnotations,
440
        \ReflectionClass $reflectionClass,
441
        Mapping\ClassMetadata $metadata
442
    ) : Mapping\ClassMetadata {
443
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
444 23
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
445
446 23
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
447 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
448
        }
449
450 23
        $metadata->isMappedSuperclass = true;
451 23
        $metadata->isEmbeddedClass    = false;
452
453 23
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
454 23
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
455
456 23
        return $metadata;
457
    }
458
459
    /**
460
     * @param Annotation\Annotation[] $classAnnotations
461
     */
462
    private function convertClassAnnotationsToEmbeddableClassMetadata(
463
        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

463
        /** @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...
464
        \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

464
        /** @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...
465
        Mapping\ClassMetadata $metadata
466
    ) : Mapping\ClassMetadata {
467
        $metadata->isMappedSuperclass = false;
468
        $metadata->isEmbeddedClass    = true;
469
470
        return $metadata;
471
    }
472
473
    /**
474
     * @param Annotation\Annotation[] $propertyAnnotations
475
     *
476
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
477
     */
478 280
    private function convertPropertyAnnotationsToProperty(
479
        array $propertyAnnotations,
480
        \ReflectionProperty $reflectionProperty,
481
        Mapping\ClassMetadata $metadata
482
    ) : ?Mapping\Property {
483
        switch (true) {
484 280
            case isset($propertyAnnotations[Annotation\Column::class]):
485 275
                return $this->convertReflectionPropertyToFieldMetadata(
486 275
                    $reflectionProperty,
487 275
                    $propertyAnnotations,
488 275
                    $metadata
489
                );
490
491 183
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
492 84
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
493 84
                    $reflectionProperty,
494 84
                    $propertyAnnotations,
495 84
                    $metadata
496
                );
497
498 144
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
499 90
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
500 90
                    $reflectionProperty,
501 90
                    $propertyAnnotations,
502 90
                    $metadata
503
                );
504
505 115
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
506 67
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
507 67
                    $reflectionProperty,
508 67
                    $propertyAnnotations,
509 67
                    $metadata
510
                );
511
512 84
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
513 71
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
514 71
                    $reflectionProperty,
515 71
                    $propertyAnnotations,
516 71
                    $metadata
517
                );
518
519 22
            case isset($propertyAnnotations[Annotation\Embedded::class]):
520
                return null;
521
522
            default:
523 22
                return new Mapping\TransientMetadata($reflectionProperty->getName());
524
        }
525
    }
526
527
    /**
528
     * @param Annotation\Annotation[] $propertyAnnotations
529
     *
530
     * @throws Mapping\MappingException
531
     */
532 275
    private function convertReflectionPropertyToFieldMetadata(
533
        \ReflectionProperty $reflProperty,
534
        array $propertyAnnotations,
535
        Mapping\ClassMetadata $metadata
536
    ) : Mapping\FieldMetadata {
537 275
        $className   = $metadata->getClassName();
538 275
        $fieldName   = $reflProperty->getName();
539 275
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
540 275
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
541
542 275
        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...
543
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
544
        }
545
546 275
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
547
548
        // Check for Id
549 275
        if (isset($propertyAnnotations[Annotation\Id::class])) {
550 272
            $fieldMetadata->setPrimaryKey(true);
551
        }
552
553
        // Check for GeneratedValue strategy
554 275
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
555 227
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
556 227
            $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...
557 227
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
558
559 227
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
560 214
                $idGeneratorDefinition = [];
561
562
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
563
                switch (true) {
564 214
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
565 9
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
566
567
                        $idGeneratorDefinition = [
568 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...
569 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...
570
                        ];
571
572 9
                        break;
573
574 205
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
575 3
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
576
577
                        $idGeneratorDefinition = [
578 3
                            'class' => $customGeneratorAnnot->class,
579 3
                            'arguments' => $customGeneratorAnnot->arguments,
580
                        ];
581
582 3
                        break;
583
584
                    /** @todo If it is not supported, why does this exist? */
585 202
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
586
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
587
                }
588
589 214
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
590
            }
591
        }
592
593 275
        return $fieldMetadata;
594
    }
595
596
    /**
597
     * @param Annotation\Annotation[] $propertyAnnotations
598
     *
599
     * @return Mapping\OneToOneAssociationMetadata
600
     */
601 84
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
602
        \ReflectionProperty $reflectionProperty,
603
        array $propertyAnnotations,
604
        Mapping\ClassMetadata $metadata
605
    ) {
606 84
        $className     = $metadata->getClassName();
607 84
        $fieldName     = $reflectionProperty->getName();
608 84
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
609 84
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
610 84
        $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...
611
612 84
        $assocMetadata->setTargetEntity($targetEntity);
613 84
        $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...
614 84
        $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...
615 84
        $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...
616
617 84
        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...
618 24
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
619 24
            $assocMetadata->setOwningSide(false);
620
        }
621
622 84
        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...
623 36
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
624
        }
625
626
        // Check for Id
627 84
        if (isset($propertyAnnotations[Annotation\Id::class])) {
628 8
            $assocMetadata->setPrimaryKey(true);
629
        }
630
631 84
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
632
633
        // Check for JoinColumn/JoinColumns annotations
634
        switch (true) {
635 84
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
636 62
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
637
638 62
                $assocMetadata->addJoinColumn(
639 62
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
640
                );
641
642 62
                break;
643
644 31
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
645 2
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
646
647 2
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
648 2
                    $assocMetadata->addJoinColumn(
649 2
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
650
                    );
651
                }
652
653 2
                break;
654
        }
655
656 84
        return $assocMetadata;
657
    }
658
659
    /**
660
     * @param Annotation\Annotation[] $propertyAnnotations
661
     *
662
     * @return Mapping\ManyToOneAssociationMetadata
663
     */
664 90
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
665
        \ReflectionProperty $reflectionProperty,
666
        array $propertyAnnotations,
667
        Mapping\ClassMetadata $metadata
668
    ) {
669 90
        $className      = $metadata->getClassName();
670 90
        $fieldName      = $reflectionProperty->getName();
671 90
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
672 90
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
673 90
        $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...
674
675 90
        $assocMetadata->setTargetEntity($targetEntity);
676 90
        $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...
677 90
        $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...
678
679 90
        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...
680 59
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
681
        }
682
683
        // Check for Id
684 90
        if (isset($propertyAnnotations[Annotation\Id::class])) {
685 21
            $assocMetadata->setPrimaryKey(true);
686
        }
687
688 90
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
689
690
        // Check for JoinColumn/JoinColumns annotations
691
        switch (true) {
692 90
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
693 54
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
694
695 54
                $assocMetadata->addJoinColumn(
696 54
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
697
                );
698
699 54
                break;
700
701 43
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
702 11
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
703
704 11
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
705 11
                    $assocMetadata->addJoinColumn(
706 11
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
707
                    );
708
                }
709
710 11
                break;
711
        }
712
713 90
        return $assocMetadata;
714
    }
715
716
    /**
717
     * @param Annotation\Annotation[] $propertyAnnotations
718
     *
719
     * @throws Mapping\MappingException
720
     */
721 67
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
722
        \ReflectionProperty $reflectionProperty,
723
        array $propertyAnnotations,
724
        Mapping\ClassMetadata $metadata
725
    ) : Mapping\OneToManyAssociationMetadata {
726 67
        $className      = $metadata->getClassName();
727 67
        $fieldName      = $reflectionProperty->getName();
728 67
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
729 67
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
730 67
        $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...
731
732 67
        $assocMetadata->setTargetEntity($targetEntity);
733 67
        $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...
734 67
        $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...
735 67
        $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...
736 67
        $assocMetadata->setOwningSide(false);
737 67
        $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...
738
739 67
        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...
740 5
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
741
        }
742
743
        // Check for OrderBy
744 67
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
745 10
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
746
747 10
            $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...
748
        }
749
750
        // Check for Id
751 67
        if (isset($propertyAnnotations[Annotation\Id::class])) {
752
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
753
        }
754
755 67
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
756
757 67
        return $assocMetadata;
758
    }
759
760
    /**
761
     * @param Annotation\Annotation[] $propertyAnnotations
762
     *
763
     * @throws Mapping\MappingException
764
     */
765 71
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
766
        \ReflectionProperty $reflectionProperty,
767
        array $propertyAnnotations,
768
        Mapping\ClassMetadata $metadata
769
    ) : Mapping\ManyToManyAssociationMetadata {
770 71
        $className       = $metadata->getClassName();
771 71
        $fieldName       = $reflectionProperty->getName();
772 71
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
773 71
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
774 71
        $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...
775
776 71
        $assocMetadata->setTargetEntity($targetEntity);
777 71
        $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...
778 71
        $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...
779 71
        $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...
780
781 71
        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...
782 28
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
783 28
            $assocMetadata->setOwningSide(false);
784
        }
785
786 71
        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...
787 37
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
788
        }
789
790 71
        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...
791 1
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
792
        }
793
794
        // Check for JoinTable
795 71
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
796 54
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
797 54
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
798
799 54
            $assocMetadata->setJoinTable($joinTableMetadata);
800
        }
801
802
        // Check for OrderBy
803 71
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
804 1
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
805
806 1
            $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...
807
        }
808
809
        // Check for Id
810 71
        if (isset($propertyAnnotations[Annotation\Id::class])) {
811
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
812
        }
813
814 71
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
815
816 71
        return $assocMetadata;
817
    }
818
819
    /**
820
     * Parse the given Column as FieldMetadata
821
     */
822 275
    private function convertColumnAnnotationToFieldMetadata(
823
        Annotation\Column $columnAnnot,
824
        string $fieldName,
825
        bool $isVersioned
826
    ) : Mapping\FieldMetadata {
827 275
        $fieldMetadata = $isVersioned
828 14
            ? new Mapping\VersionFieldMetadata($fieldName)
829 275
            : new Mapping\FieldMetadata($fieldName)
830
        ;
831
832 275
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
833
834 275
        if (! empty($columnAnnot->name)) {
835 53
            $fieldMetadata->setColumnName($columnAnnot->name);
836
        }
837
838 275
        if (! empty($columnAnnot->columnDefinition)) {
839 4
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
840
        }
841
842 275
        if (! empty($columnAnnot->length)) {
843 275
            $fieldMetadata->setLength($columnAnnot->length);
844
        }
845
846 275
        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...
847 6
            $fieldMetadata->setOptions($columnAnnot->options);
848
        }
849
850 275
        $fieldMetadata->setScale($columnAnnot->scale);
851 275
        $fieldMetadata->setPrecision($columnAnnot->precision);
852 275
        $fieldMetadata->setNullable($columnAnnot->nullable);
853 275
        $fieldMetadata->setUnique($columnAnnot->unique);
854
855 275
        return $fieldMetadata;
856
    }
857
858
    /**
859
     * Parse the given Table as TableMetadata
860
     */
861 144
    private function convertTableAnnotationToTableMetadata(
862
        Annotation\Table $tableAnnot,
863
        Mapping\TableMetadata $tableMetadata
864
    ) : void {
865 144
        if (! empty($tableAnnot->name)) {
866 142
            $tableMetadata->setName($tableAnnot->name);
867
        }
868
869 144
        if (! empty($tableAnnot->schema)) {
870 5
            $tableMetadata->setSchema($tableAnnot->schema);
871
        }
872
873 144
        foreach ($tableAnnot->options as $optionName => $optionValue) {
874 4
            $tableMetadata->addOption($optionName, $optionValue);
875
        }
876
877 144
        foreach ($tableAnnot->indexes as $indexAnnot) {
878 12
            $tableMetadata->addIndex([
879 12
                'name'    => $indexAnnot->name,
880 12
                'columns' => $indexAnnot->columns,
881 12
                'unique'  => $indexAnnot->unique,
882 12
                'options' => $indexAnnot->options,
883 12
                'flags'   => $indexAnnot->flags,
884
            ]);
885
        }
886
887 144
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
888 5
            $tableMetadata->addUniqueConstraint([
889 5
                'name'    => $uniqueConstraintAnnot->name,
890 5
                'columns' => $uniqueConstraintAnnot->columns,
891 5
                'options' => $uniqueConstraintAnnot->options,
892 5
                'flags'   => $uniqueConstraintAnnot->flags,
893
            ]);
894
        }
895 144
    }
896
897
    /**
898
     * Parse the given JoinTable as JoinTableMetadata
899
     */
900 54
    private function convertJoinTableAnnotationToJoinTableMetadata(
901
        Annotation\JoinTable $joinTableAnnot
902
    ) : Mapping\JoinTableMetadata {
903 54
        $joinTable = new Mapping\JoinTableMetadata();
904
905 54
        if (! empty($joinTableAnnot->name)) {
906 52
            $joinTable->setName($joinTableAnnot->name);
907
        }
908
909 54
        if (! empty($joinTableAnnot->schema)) {
910
            $joinTable->setSchema($joinTableAnnot->schema);
911
        }
912
913 54
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
914 54
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
915
916 54
            $joinTable->addJoinColumn($joinColumn);
917
        }
918
919 54
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
920 54
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
921
922 54
            $joinTable->addInverseJoinColumn($joinColumn);
923
        }
924
925 54
        return $joinTable;
926
    }
927
928
    /**
929
     * Parse the given JoinColumn as JoinColumnMetadata
930
     */
931 134
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
932
        Annotation\JoinColumn $joinColumnAnnot
933
    ) : Mapping\JoinColumnMetadata {
934 134
        $joinColumn = new Mapping\JoinColumnMetadata();
935
936
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
937 134
        if (! empty($joinColumnAnnot->name)) {
938 131
            $joinColumn->setColumnName($joinColumnAnnot->name);
939
        }
940
941 134
        if (! empty($joinColumnAnnot->referencedColumnName)) {
942 134
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
943
        }
944
945 134
        $joinColumn->setNullable($joinColumnAnnot->nullable);
946 134
        $joinColumn->setUnique($joinColumnAnnot->unique);
947
948 134
        if (! empty($joinColumnAnnot->fieldName)) {
949
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
950
        }
951
952 134
        if (! empty($joinColumnAnnot->columnDefinition)) {
953 3
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
954
        }
955
956 134
        if ($joinColumnAnnot->onDelete) {
957 12
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
958
        }
959
960 134
        return $joinColumn;
961
    }
962
963
    /**
964
     * Parse the given Cache as CacheMetadata
965
     *
966
     * @param string|null $fieldName
967
     */
968 14
    private function convertCacheAnnotationToCacheMetadata(
969
        Annotation\Cache $cacheAnnot,
970
        Mapping\ClassMetadata $metadata,
971
        $fieldName = null
972
    ) : Mapping\CacheMetadata {
973 14
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
974 14
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
975
976 14
        $usage  = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
977 14
        $region = $cacheAnnot->region ?: $defaultRegion;
978
979 14
        return new Mapping\CacheMetadata($usage, $region);
980
    }
981
982
    /**
983
     * @param Annotation\Annotation[] $classAnnotations
984
     */
985 280
    private function attachTable(
986
        array $classAnnotations,
987
        \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

987
        /** @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...
988
        Mapping\ClassMetadata $metadata,
989
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
990
    ) : void {
991 280
        $parent = $metadata->getParent();
992
993 280
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
994
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
995
            do {
996 18
                if (! $parent->isMappedSuperclass) {
997 18
                    $metadata->setTable($parent->table);
998
999 18
                    break;
1000
                }
1001
1002 4
                $parent = $parent->getParent();
1003 4
            } while ($parent !== null);
1004
1005 18
            return;
1006
        }
1007
1008 280
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1009 280
        $tableMetadata  = new Mapping\TableMetadata();
1010
1011 280
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1012
1013
        // Evaluate @Table annotation
1014 280
        if (isset($classAnnotations[Annotation\Table::class])) {
1015 144
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1016
1017 144
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1018
        }
1019
1020 280
        $metadata->setTable($tableMetadata);
1021 280
    }
1022
1023
    /**
1024
     * @param Annotation\Annotation[] $classAnnotations
1025
     *
1026
     * @throws Mapping\MappingException
1027
     */
1028 53
    private function attachDiscriminatorColumn(
1029
        array $classAnnotations,
1030
        \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

1030
        /** @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...
1031
        Mapping\ClassMetadata $metadata
1032
    ) : void {
1033 53
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1034
1035 53
        $discriminatorColumn->setTableName($metadata->getTableName());
1036 53
        $discriminatorColumn->setColumnName('dtype');
1037 53
        $discriminatorColumn->setType(Type::getType('string'));
1038 53
        $discriminatorColumn->setLength(255);
1039
1040
        // Evaluate DiscriminatorColumn annotation
1041 53
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1042
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1043 41
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1044 41
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1045 37
                ? $discriminatorColumnAnnotation->type
1046 41
                : 'string';
1047
1048 41
            $discriminatorColumn->setType(Type::getType($typeName));
1049 41
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1050
1051 41
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1052 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1053
            }
1054
1055 41
            if (! empty($discriminatorColumnAnnotation->length)) {
1056 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1057
            }
1058
        }
1059
1060 53
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1061
1062
        // Evaluate DiscriminatorMap annotation
1063 53
        if (! isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1064 1
            return;
1065
        }
1066
1067 52
        $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1068 52
        $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...
1069
1070 52
        $metadata->setDiscriminatorMap($discriminatorMap);
1071 52
    }
1072
1073
    /**
1074
     * @param Annotation\Annotation[] $classAnnotations
1075
     */
1076 281
    private function attachLifecycleCallbacks(
1077
        array $classAnnotations,
1078
        \ReflectionClass $reflectionClass,
1079
        Mapping\ClassMetadata $metadata
1080
    ) : void {
1081
        // Evaluate @HasLifecycleCallbacks annotation
1082 281
        if (! isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1083 275
            return;
1084
        }
1085
1086
        /** @var \ReflectionMethod $method */
1087 10
        foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1088 9
            foreach ($this->getMethodCallbacks($method) as $callback) {
1089 9
                $metadata->addLifecycleCallback($method->getName(), $callback);
1090
            }
1091
        }
1092 10
    }
1093
1094
    /**
1095
     * @param Annotation\Annotation[] $classAnnotations
1096
     *
1097
     * @throws \ReflectionException
1098
     * @throws Mapping\MappingException
1099
     */
1100 281
    private function attachEntityListeners(
1101
        array $classAnnotations,
1102
        \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

1102
        /** @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...
1103
        Mapping\ClassMetadata $metadata
1104
    ) : void {
1105
        // Evaluate @EntityListeners annotation
1106 281
        if (! isset($classAnnotations[Annotation\EntityListeners::class])) {
1107 278
            return;
1108
        }
1109
1110
        /** @var Annotation\EntityListeners $entityListenersAnnot */
1111 7
        $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1112
1113 7
        foreach ($entityListenersAnnot->value as $listenerClassName) {
1114 7
            if (! class_exists($listenerClassName)) {
1115
                throw Mapping\MappingException::entityListenerClassNotFound(
1116
                    $listenerClassName,
1117
                    $metadata->getClassName()
1118
                );
1119
            }
1120
1121 7
            $listenerClass = new \ReflectionClass($listenerClassName);
1122
1123
            /** @var \ReflectionMethod $method */
1124 7
            foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1125 7
                foreach ($this->getMethodCallbacks($method) as $callback) {
1126 7
                    $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1127
                }
1128
            }
1129
        }
1130 7
    }
1131
1132
    /**
1133
     * @param Annotation\Annotation[] $classAnnotations
1134
     *
1135
     * @throws Mapping\MappingException
1136
     */
1137 278
    private function attachPropertyOverrides(
1138
        array $classAnnotations,
1139
        \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

1139
        /** @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...
1140
        Mapping\ClassMetadata $metadata
1141
    ) : void {
1142
        // Evaluate AssociationOverrides annotation
1143 278
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1144 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1145
1146 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...
1147 5
                $fieldName = $associationOverride->name;
1148 5
                $property  = $metadata->getProperty($fieldName);
1149
1150 5
                if (! $property) {
1151
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1152
                }
1153
1154 5
                $existingClass = get_class($property);
1155 5
                $override      = new $existingClass($fieldName);
1156
1157
                // Check for JoinColumn/JoinColumns annotations
1158 5
                if ($associationOverride->joinColumns) {
1159 3
                    $joinColumns = [];
1160
1161 3
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1162 3
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1163
                    }
1164
1165 3
                    $override->setJoinColumns($joinColumns);
1166
                }
1167
1168
                // Check for JoinTable annotations
1169 5
                if ($associationOverride->joinTable) {
1170 2
                    $joinTableAnnot    = $associationOverride->joinTable;
1171 2
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1172
1173 2
                    $override->setJoinTable($joinTableMetadata);
1174
                }
1175
1176
                // Check for inversedBy
1177 5
                if ($associationOverride->inversedBy) {
1178 1
                    $override->setInversedBy($associationOverride->inversedBy);
1179
                }
1180
1181
                // Check for fetch
1182 5
                if ($associationOverride->fetch) {
1183 1
                    $override->setFetchMode(
1184 1
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1185
                    );
1186
                }
1187
1188 5
                $metadata->setPropertyOverride($override);
1189
            }
1190
        }
1191
1192
        // Evaluate AttributeOverrides annotation
1193 278
        if (! isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1194 278
            return;
1195
        }
1196
1197 3
        $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1198
1199 3
        foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1200 3
            $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1201 3
                $attributeOverrideAnnot->column,
1202 3
                $attributeOverrideAnnot->name,
1203 3
                false
1204
            );
1205
1206 3
            $metadata->setPropertyOverride($fieldMetadata);
1207
        }
1208 3
    }
1209
1210
    /**
1211
     * @param Annotation\Annotation[] $propertyAnnotations
1212
     */
1213 180
    private function attachAssociationPropertyCache(
1214
        array $propertyAnnotations,
1215
        \ReflectionProperty $reflectionProperty,
1216
        Mapping\AssociationMetadata $assocMetadata,
1217
        Mapping\ClassMetadata $metadata
1218
    ) : void {
1219
        // Check for Cache
1220 180
        if (! isset($propertyAnnotations[Annotation\Cache::class])) {
1221 175
            return;
1222
        }
1223
1224 10
        $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
1225 10
        $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata(
1226 10
            $cacheAnnot,
1227 10
            $metadata,
1228 10
            $reflectionProperty->getName()
1229
        );
1230
1231 10
        $assocMetadata->setCache($cacheMetadata);
1232 10
    }
1233
1234
    /**
1235
     * Attempts to resolve the cascade modes.
1236
     *
1237
     * @param string   $className        The class name.
1238
     * @param string   $fieldName        The field name.
1239
     * @param string[] $originalCascades The original unprocessed field cascades.
1240
     *
1241
     * @return string[] The processed field cascades.
1242
     *
1243
     * @throws Mapping\MappingException If a cascade option is not valid.
1244
     */
1245 180
    private function getCascade(string $className, string $fieldName, array $originalCascades)
1246
    {
1247 180
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1248 180
        $cascades     = array_map('strtolower', $originalCascades);
1249
1250 180
        if (in_array('all', $cascades, true)) {
1251 15
            $cascades = $cascadeTypes;
1252
        }
1253
1254 180
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1255
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1256
1257
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1258
        }
1259
1260 180
        return $cascades;
1261
    }
1262
1263
    /**
1264
     * Attempts to resolve the fetch mode.
1265
     *
1266
     * @param string $className The class name.
1267
     * @param string $fetchMode The fetch mode.
1268
     *
1269
     * @return string The fetch mode as defined in ClassMetadata.
1270
     *
1271
     * @throws Mapping\MappingException If the fetch mode is not valid.
1272
     */
1273 180
    private function getFetchMode($className, $fetchMode) : string
1274
    {
1275 180
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1276
1277 180
        if (! defined($fetchModeConstant)) {
1278
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1279
        }
1280
1281 180
        return constant($fetchModeConstant);
1282
    }
1283
1284
    /**
1285
     * Parses the given method.
1286
     *
1287
     * @return string[]
1288
     */
1289 16
    private function getMethodCallbacks(\ReflectionMethod $method) : array
1290
    {
1291 16
        $annotations = $this->getMethodAnnotations($method);
1292
        $events      = [
1293 16
            Events::prePersist  => Annotation\PrePersist::class,
1294 16
            Events::postPersist => Annotation\PostPersist::class,
1295 16
            Events::preUpdate   => Annotation\PreUpdate::class,
1296 16
            Events::postUpdate  => Annotation\PostUpdate::class,
1297 16
            Events::preRemove   => Annotation\PreRemove::class,
1298 16
            Events::postRemove  => Annotation\PostRemove::class,
1299 16
            Events::postLoad    => Annotation\PostLoad::class,
1300 16
            Events::preFlush    => Annotation\PreFlush::class,
1301
        ];
1302
1303
        // Check for callbacks
1304 16
        $callbacks = [];
1305
1306 16
        foreach ($events as $eventName => $annotationClassName) {
1307 16
            if (! isset($annotations[$annotationClassName]) && $method->getName() !== $eventName) {
1308 16
                continue;
1309
            }
1310
1311 16
            $callbacks[] = $eventName;
1312
        }
1313
1314 16
        return $callbacks;
1315
    }
1316
1317
    /**
1318
     * @return Annotation\Annotation[]
1319
     */
1320 284
    private function getClassAnnotations(\ReflectionClass $reflectionClass) : array
1321
    {
1322 284
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1323
1324 284
        foreach ($classAnnotations as $key => $annot) {
1325 281
            if (! is_numeric($key)) {
1326
                continue;
1327
            }
1328
1329 281
            $classAnnotations[get_class($annot)] = $annot;
1330
        }
1331
1332 284
        return $classAnnotations;
1333
    }
1334
1335
    /**
1336
     * @return Annotation\Annotation[]
1337
     */
1338 281
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty) : array
1339
    {
1340 281
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1341
1342 280
        foreach ($propertyAnnotations as $key => $annot) {
1343 280
            if (! is_numeric($key)) {
1344
                continue;
1345
            }
1346
1347 280
            $propertyAnnotations[get_class($annot)] = $annot;
1348
        }
1349
1350 280
        return $propertyAnnotations;
1351
    }
1352
1353
    /**
1354
     * @return Annotation\Annotation[]
1355
     */
1356 16
    private function getMethodAnnotations(\ReflectionMethod $reflectionMethod) : array
1357
    {
1358 16
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1359
1360 16
        foreach ($methodAnnotations as $key => $annot) {
1361 13
            if (! is_numeric($key)) {
1362
                continue;
1363
            }
1364
1365 13
            $methodAnnotations[get_class($annot)] = $annot;
1366
        }
1367
1368 16
        return $methodAnnotations;
1369
    }
1370
1371
    /**
1372
     * Factory method for the Annotation Driver.
1373
     *
1374
     * @param string|string[] $paths
1375
     *
1376
     * @return AnnotationDriver
1377
     */
1378
    public static function create($paths = [], ?AnnotationReader $reader = null)
1379
    {
1380
        if ($reader === null) {
1381
            $reader = new AnnotationReader();
1382
        }
1383
1384
        return new self($reader, $paths);
1385
    }
1386
}
1387