AnnotationDriver   F
last analyzed

Complexity

Total Complexity 166

Size/Duplication

Total Lines 1328
Duplicated Lines 0 %

Test Coverage

Coverage 91.33%

Importance

Changes 0
Metric Value
eloc 512
dl 0
loc 1328
ccs 495
cts 542
cp 0.9133
rs 2
c 0
b 0
f 0
wmc 166

39 Methods

Rating   Name   Duplication   Size   Complexity  
A addPaths() 0 3 1
A setFileExtension() 0 3 1
A getExcludePaths() 0 3 1
A getPaths() 0 3 1
A getReader() 0 3 1
A getFileExtension() 0 3 1
A addExcludePaths() 0 3 1
A getCascade() 0 16 3
C getAllClassNames() 0 62 12
A convertTableAnnotationToTableMetadata() 0 32 6
B convertReflectionPropertyToManyToOneAssociationMetadata() 0 50 6
A attachAssociationPropertyCache() 0 16 2
A create() 0 7 2
A convertJoinTableAnnotationToJoinTableMetadata() 0 26 5
A isTransient() 0 10 3
A convertClassAnnotationsToEmbeddableClassMetadata() 0 9 1
A convertReflectionPropertyToOneToManyAssociationMetadata() 0 37 4
A convertClassAnnotationsToMappedSuperClassMetadata() 0 19 2
A convertJoinColumnAnnotationToJoinColumnMetadata() 0 30 6
B convertClassAnnotationsToEntityClassMetadata() 0 48 6
B convertPropertyAnnotationsToProperty() 0 46 7
A attachEntityListeners() 0 24 6
B convertReflectionPropertyToFieldMetadata() 0 62 8
A convertCacheAnnotationToCacheMetadata() 0 12 3
A convertColumnAnnotationToFieldMetadata() 0 34 6
A __construct() 0 5 2
B convertReflectionPropertyToManyToManyAssociationMetadata() 0 52 7
A convertClassAnnotationsToClassMetadata() 0 33 4
A attachTable() 0 36 6
B attachPropertyOverrides() 0 67 11
A attachLifecycleCallbacks() 0 11 4
A getPropertyAnnotations() 0 13 3
A getClassAnnotations() 0 13 3
B attachDiscriminatorColumn() 0 40 6
A getMethodAnnotations() 0 13 3
B loadMetadataForClass() 0 62 9
B convertReflectionPropertyToOneToOneAssociationMetadata() 0 56 7
A getFetchMode() 0 9 2
A getMethodCallbacks() 0 24 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 2274
    public function __construct(Reader $reader, $paths = null)
92
    {
93 2274
        $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 2274
        if ($paths) {
95 2188
            $this->addPaths((array) $paths);
96
        }
97 2274
    }
98
99
    /**
100
     * Appends lookup paths to metadata driver.
101
     *
102
     * @param string[] $paths
103
     */
104 2192
    public function addPaths(array $paths)
105
    {
106 2192
        $this->paths = array_unique(array_merge($this->paths, $paths));
107 2192
    }
108
109
    /**
110
     * Retrieves the defined metadata lookup paths.
111
     *
112
     * @return string[]
113
     */
114
    public function getPaths()
115
    {
116
        return $this->paths;
117
    }
118
119
    /**
120
     * Append exclude lookup paths to metadata driver.
121
     *
122
     * @param string[] $paths
123
     */
124
    public function addExcludePaths(array $paths)
125
    {
126
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
127
    }
128
129
    /**
130
     * Retrieve the defined metadata lookup exclude paths.
131
     *
132
     * @return string[]
133
     */
134
    public function getExcludePaths()
135
    {
136
        return $this->excludePaths;
137
    }
138
139
    /**
140
     * Retrieve the current annotation reader
141
     *
142
     * @return AnnotationReader
143
     */
144 1
    public function getReader()
145
    {
146 1
        return $this->reader;
147
    }
148
149
    /**
150
     * Gets the file extension used to look for mapping files under.
151
     *
152
     * @return string
153
     */
154
    public function getFileExtension()
155
    {
156
        return $this->fileExtension;
157
    }
158
159
    /**
160
     * Sets the file extension used to look for mapping files under.
161
     *
162
     * @param string $fileExtension The file extension to set.
163
     *
164
     */
165
    public function setFileExtension($fileExtension)
166
    {
167
        $this->fileExtension = $fileExtension;
168
    }
169
170
    /**
171
     * Returns whether the class with the specified name is transient. Only non-transient
172
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
173
     *
174
     * A class is non-transient if it is annotated with an annotation
175
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
176
     *
177
     * @param string $className
178
     *
179
     * @return bool
180
     */
181 193
    public function isTransient($className)
182
    {
183 193
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
184
185 193
        foreach ($classAnnotations as $annot) {
186 188
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
187 188
                return false;
188
            }
189
        }
190 12
        return true;
191
    }
192
193
    /**
194
     * {@inheritDoc}
195
     */
196 61
    public function getAllClassNames()
197
    {
198 61
        if ($this->classNames !== null) {
199 46
            return $this->classNames;
200
        }
201
202 61
        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...
203
            throw Mapping\MappingException::pathRequired();
204
        }
205
206 61
        $classes       = [];
207 61
        $includedFiles = [];
208
209 61
        foreach ($this->paths as $path) {
210 61
            if (! is_dir($path)) {
211
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
212
            }
213
214 61
            $iterator = new \RegexIterator(
215 61
                new \RecursiveIteratorIterator(
216 61
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
217 61
                    \RecursiveIteratorIterator::LEAVES_ONLY
218
                ),
219 61
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
220 61
                \RecursiveRegexIterator::GET_MATCH
221
            );
222
223 61
            foreach ($iterator as $file) {
224 61
                $sourceFile = $file[0];
225
226 61
                if (! preg_match('(^phar:)i', $sourceFile)) {
227 61
                    $sourceFile = realpath($sourceFile);
228
                }
229
230 61
                foreach ($this->excludePaths as $excludePath) {
231
                    $exclude = str_replace('\\', '/', realpath($excludePath));
232
                    $current = str_replace('\\', '/', $sourceFile);
233
234
                    if (strpos($current, $exclude) !== false) {
235
                        continue 2;
236
                    }
237
                }
238
239 61
                require_once $sourceFile;
240
241 61
                $includedFiles[] = $sourceFile;
242
            }
243
        }
244
245 61
        $declared = get_declared_classes();
246
247 61
        foreach ($declared as $className) {
248 61
            $rc         = new \ReflectionClass($className);
249 61
            $sourceFile = $rc->getFileName();
250 61
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
251 61
                $classes[] = $className;
252
            }
253
        }
254
255 61
        $this->classNames = $classes;
256
257 61
        return $classes;
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     *
263
     * @throws CacheException
264
     * @throws Mapping\MappingException
265
     * @throws \ReflectionException
266
     * @throws \RuntimeException
267
     */
268 371
    public function loadMetadataForClass(
269
        string $className,
270
        Mapping\ClassMetadata $metadata,
271
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
272
    ) : Mapping\ClassMetadata {
273 371
        $reflectionClass = $metadata->getReflectionClass();
274
275 371
        if (! $reflectionClass) {
276
            // this happens when running annotation driver in combination with
277
            // static reflection services. This is not the nicest fix
278
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
279
        }
280
281 371
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
282 371
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
283 371
            $classAnnotations,
284 371
            $reflectionClass,
285 371
            $metadata,
286 371
            $metadataBuildingContext
287
        );
288
289
        // Evaluate @Cache annotation
290 368
        if (isset($classAnnotations[Annotation\Cache::class])) {
291 18
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
292 18
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
293
294 18
            $classMetadata->setCache($cache);
295
        }
296
297
        // Evaluate annotations on properties/fields
298
        /** @var \ReflectionProperty $reflProperty */
299 368
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
300 368
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
301 76
                continue;
302
            }
303
304 368
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
305 367
            $property            = $this->convertPropertyAnnotationsToProperty(
306 367
                $propertyAnnotations,
307 367
                $reflectionProperty,
308 367
                $classMetadata
309
            );
310
311 367
            if ($classMetadata->isMappedSuperclass &&
312 367
                $property instanceof Mapping\ToManyAssociationMetadata &&
313 367
                ! $property->isOwningSide()) {
314 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
315 1
                    $classMetadata->getClassName(),
316 1
                    $property->getName()
317
                );
318
            }
319
320 366
            if (! $property) {
321 1
                continue;
322
            }
323
324 366
            $metadata->addProperty($property);
325
        }
326
327 365
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
328
329 365
        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...
330
    }
331
332
    /**
333
     * @param Annotation\Annotation[] $classAnnotations
334
     *
335
     * @throws Mapping\MappingException
336
     */
337 371
    private function convertClassAnnotationsToClassMetadata(
338
        array $classAnnotations,
339
        \ReflectionClass $reflectionClass,
340
        Mapping\ClassMetadata $metadata,
341
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
342
    ) : Mapping\ClassMetadata {
343
        switch (true) {
344 371
            case isset($classAnnotations[Annotation\Entity::class]):
345 367
                return $this->convertClassAnnotationsToEntityClassMetadata(
346 367
                    $classAnnotations,
347 367
                    $reflectionClass,
348 367
                    $metadata,
349 367
                    $metadataBuildingContext
350
                );
351
352
                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...
353
354 27
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
355 24
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
356 24
                    $classAnnotations,
357 24
                    $reflectionClass,
358 24
                    $metadata
359
                );
360
361 3
            case isset($classAnnotations[Annotation\Embeddable::class]):
362
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
363
                    $classAnnotations,
364
                    $reflectionClass,
365
                    $metadata
366
                );
367
368
            default:
369 3
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
370
        }
371
    }
372
373
    /**
374
     * @param Annotation\Annotation[] $classAnnotations
375
     *
376
     * @return Mapping\ClassMetadata
377
     *
378
     * @throws Mapping\MappingException
379
     * @throws \UnexpectedValueException
380
     */
381 367
    private function convertClassAnnotationsToEntityClassMetadata(
382
        array $classAnnotations,
383
        \ReflectionClass $reflectionClass,
384
        Mapping\ClassMetadata $metadata,
385
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
386
    ) {
387
        /** @var Annotation\Entity $entityAnnot */
388 367
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
389
390 367
        if ($entityAnnot->repositoryClass !== null) {
391 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
392
        }
393
394 367
        if ($entityAnnot->readOnly) {
395 1
            $metadata->asReadOnly();
396
        }
397
398 367
        $metadata->isMappedSuperclass = false;
399 367
        $metadata->isEmbeddedClass    = false;
400
401 367
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
402
403
        // Evaluate @ChangeTrackingPolicy annotation
404 367
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
405 5
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
406
407 5
            $metadata->setChangeTrackingPolicy(
408 5
                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...
409
            );
410
        }
411
412
        // Evaluate @InheritanceType annotation
413 367
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
414 79
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
415
416 79
            $metadata->setInheritanceType(
417 79
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
418
            );
419
420 79
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
421 79
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
422
            }
423
        }
424
425 367
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
426 367
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
427
428 367
        return $metadata;
429
    }
430
431
    /**
432
     * @param Annotation\Annotation[] $classAnnotations
433
     */
434 24
    private function convertClassAnnotationsToMappedSuperClassMetadata(
435
        array $classAnnotations,
436
        \ReflectionClass $reflectionClass,
437
        Mapping\ClassMetadata $metadata
438
    ) : Mapping\ClassMetadata {
439
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
440 24
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
441
442 24
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
443 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
444
        }
445
446 24
        $metadata->isMappedSuperclass = true;
447 24
        $metadata->isEmbeddedClass    = false;
448
449 24
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
450 24
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
451
452 24
        return $metadata;
453
    }
454
455
    /**
456
     * @param Annotation\Annotation[] $classAnnotations
457
     */
458
    private function convertClassAnnotationsToEmbeddableClassMetadata(
459
        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

459
        /** @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...
460
        \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

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

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

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

1094
        /** @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...
1095
        Mapping\ClassMetadata $metadata
1096
    ) : void {
1097
        // Evaluate @EntityListeners annotation
1098 368
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1099
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1100 9
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1101
1102 9
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1103 9
                if (! class_exists($listenerClassName)) {
1104
                    throw Mapping\MappingException::entityListenerClassNotFound(
1105
                        $listenerClassName,
1106
                        $metadata->getClassName()
1107
                    );
1108
                }
1109
1110 9
                $listenerClass = new \ReflectionClass($listenerClassName);
1111
1112
                /** @var \ReflectionMethod $method */
1113 9
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1114 9
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1115 9
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1116
                    }
1117
                }
1118
            }
1119
        }
1120 368
    }
1121
1122
    /**
1123
     * @param Annotation\Annotation[] $classAnnotations
1124
     *
1125
     * @throws Mapping\MappingException
1126
     */
1127 365
    private function attachPropertyOverrides(
1128
        array $classAnnotations,
1129
        \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

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