Failed Conditions
Push — master ( 695edb...6e4daa )
by Marco
17:25 queued 06:57
created

AnnotationDriver::loadMetadataForClass()   C

Complexity

Conditions 9
Paths 20

Size

Total Lines 62
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 9.0022

Importance

Changes 0
Metric Value
cc 9
eloc 32
nc 20
nop 3
dl 0
loc 62
ccs 32
cts 33
cp 0.9697
crap 9.0022
rs 6.6867
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
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\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
    /**
44
     * @var int[]
45
     */
46
    protected $entityAnnotationClasses = [
47
        Annotation\Entity::class           => 1,
48
        Annotation\MappedSuperclass::class => 2,
49
    ];
50
51
    /**
52
     * The AnnotationReader.
53
     *
54
     * @var AnnotationReader
55
     */
56
    protected $reader;
57
58
    /**
59
     * The paths where to look for mapping files.
60
     *
61
     * @var string[]
62
     */
63
    protected $paths = [];
64
65
    /**
66
     * The paths excluded from path where to look for mapping files.
67
     *
68
     * @var string[]
69
     */
70
    protected $excludePaths = [];
71
72
    /**
73
     * The file extension of mapping documents.
74
     *
75
     * @var string
76
     */
77
    protected $fileExtension = '.php';
78
79
    /**
80
     * Cache for AnnotationDriver#getAllClassNames().
81
     *
82
     * @var string[]|null
83
     */
84
    protected $classNames;
85
86
    /**
87
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
88
     * docblock annotations.
89
     *
90
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
91
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
92
     */
93 2281
    public function __construct(Reader $reader, $paths = null)
94
    {
95 2281
        $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...
96 2281
        if ($paths) {
97 2193
            $this->addPaths((array) $paths);
98
        }
99 2281
    }
100
101
    /**
102
     * Appends lookup paths to metadata driver.
103
     *
104
     * @param string[] $paths
105
     */
106 2197
    public function addPaths(array $paths)
107
    {
108 2197
        $this->paths = array_unique(array_merge($this->paths, $paths));
109 2197
    }
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 193
    public function isTransient($className)
184
    {
185 193
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
186
187 193
        foreach ($classAnnotations as $annot) {
188 188
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
189 188
                return false;
190
            }
191
        }
192 12
        return true;
193
    }
194
195
    /**
196
     * {@inheritDoc}
197
     */
198 61
    public function getAllClassNames()
199
    {
200 61
        if ($this->classNames !== null) {
201 46
            return $this->classNames;
202
        }
203
204 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...
205
            throw Mapping\MappingException::pathRequired();
206
        }
207
208 61
        $classes       = [];
209 61
        $includedFiles = [];
210
211 61
        foreach ($this->paths as $path) {
212 61
            if (! is_dir($path)) {
213
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
214
            }
215
216 61
            $iterator = new \RegexIterator(
217 61
                new \RecursiveIteratorIterator(
218 61
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
219 61
                    \RecursiveIteratorIterator::LEAVES_ONLY
220
                ),
221 61
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
222 61
                \RecursiveRegexIterator::GET_MATCH
223
            );
224
225 61
            foreach ($iterator as $file) {
226 61
                $sourceFile = $file[0];
227
228 61
                if (! preg_match('(^phar:)i', $sourceFile)) {
229 61
                    $sourceFile = realpath($sourceFile);
230
                }
231
232 61
                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 61
                require_once $sourceFile;
242
243 61
                $includedFiles[] = $sourceFile;
244
            }
245
        }
246
247 61
        $declared = get_declared_classes();
248
249 61
        foreach ($declared as $className) {
250 61
            $rc         = new \ReflectionClass($className);
251 61
            $sourceFile = $rc->getFileName();
252 61
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
253 61
                $classes[] = $className;
254
            }
255
        }
256
257 61
        $this->classNames = $classes;
258
259 61
        return $classes;
260
    }
261
262
    /**
263
     * {@inheritDoc}
264
     *
265
     * @throws CacheException
266
     * @throws Mapping\MappingException
267
     * @throws \ReflectionException
268
     * @throws \RuntimeException
269
     */
270 369
    public function loadMetadataForClass(
271
        string $className,
272
        Mapping\ClassMetadata $metadata,
273
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
274
    ) : Mapping\ClassMetadata {
275 369
        $reflectionClass = $metadata->getReflectionClass();
276
277 369
        if (! $reflectionClass) {
278
            // this happens when running annotation driver in combination with
279
            // static reflection services. This is not the nicest fix
280
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
281
        }
282
283 369
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
284 369
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
285 369
            $classAnnotations,
286 369
            $reflectionClass,
287 369
            $metadata,
288 369
            $metadataBuildingContext
289
        );
290
291
        // Evaluate @Cache annotation
292 366
        if (isset($classAnnotations[Annotation\Cache::class])) {
293 18
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
294 18
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
295
296 18
            $classMetadata->setCache($cache);
297
        }
298
299
        // Evaluate annotations on properties/fields
300
        /* @var $reflProperty \ReflectionProperty */
301 366
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
302 366
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
303 76
                continue;
304
            }
305
306 366
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
307 365
            $property            = $this->convertPropertyAnnotationsToProperty(
308 365
                $propertyAnnotations,
309 365
                $reflectionProperty,
310 365
                $classMetadata
311
            );
312
313 365
            if ($classMetadata->isMappedSuperclass &&
314 365
                $property instanceof Mapping\ToManyAssociationMetadata &&
315 365
                ! $property->isOwningSide()) {
316 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
317 1
                    $classMetadata->getClassName(),
318 1
                    $property->getName()
319
                );
320
            }
321
322 364
            if (! $property) {
323 1
                continue;
324
            }
325
326 364
            $metadata->addProperty($property);
327
        }
328
329 363
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
330
331 363
        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...
332
    }
333
334
    /**
335
     * @param Annotation\Annotation[] $classAnnotations
336
     *
337
     * @throws Mapping\MappingException
338
     */
339 369
    private function convertClassAnnotationsToClassMetadata(
340
        array $classAnnotations,
341
        \ReflectionClass $reflectionClass,
342
        Mapping\ClassMetadata $metadata,
343
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
344
    ) : Mapping\ClassMetadata {
345
        switch (true) {
346 369
            case isset($classAnnotations[Annotation\Entity::class]):
347 365
                return $this->convertClassAnnotationsToEntityClassMetadata(
348 365
                    $classAnnotations,
349 365
                    $reflectionClass,
350 365
                    $metadata,
351 365
                    $metadataBuildingContext
352
                );
353
354
                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...
355
356 27
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
357 24
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
358 24
                    $classAnnotations,
359 24
                    $reflectionClass,
360 24
                    $metadata
361
                );
362
363 3
            case isset($classAnnotations[Annotation\Embeddable::class]):
364
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
365
                    $classAnnotations,
366
                    $reflectionClass,
367
                    $metadata
368
                );
369
370
            default:
371 3
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
372
        }
373
    }
374
375
    /**
376
     * @param Annotation\Annotation[] $classAnnotations
377
     *
378
     * @return Mapping\ClassMetadata
379
     *
380
     * @throws Mapping\MappingException
381
     * @throws \UnexpectedValueException
382
     */
383 365
    private function convertClassAnnotationsToEntityClassMetadata(
384
        array $classAnnotations,
385
        \ReflectionClass $reflectionClass,
386
        Mapping\ClassMetadata $metadata,
387
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
388
    ) {
389
        /** @var Annotation\Entity $entityAnnot */
390 365
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
391
392 365
        if ($entityAnnot->repositoryClass !== null) {
393 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
394
        }
395
396 365
        if ($entityAnnot->readOnly) {
397 1
            $metadata->asReadOnly();
398
        }
399
400 365
        $metadata->isMappedSuperclass = false;
401 365
        $metadata->isEmbeddedClass    = false;
402
403 365
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
404
405
        // Evaluate @ChangeTrackingPolicy annotation
406 365
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
407 5
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
408
409 5
            $metadata->setChangeTrackingPolicy(
410 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...
411
            );
412
        }
413
414
        // Evaluate @InheritanceType annotation
415 365
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
416 80
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
417
418 80
            $metadata->setInheritanceType(
419 80
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
420
            );
421
422 80
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
423 80
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
424
            }
425
        }
426
427 365
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
428 365
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
429 365
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
430
431 365
        return $metadata;
432
    }
433
434
    /**
435
     * @param Annotation\Annotation[] $classAnnotations
436
     */
437 24
    private function convertClassAnnotationsToMappedSuperClassMetadata(
438
        array $classAnnotations,
439
        \ReflectionClass $reflectionClass,
440
        Mapping\ClassMetadata $metadata
441
    ) : Mapping\ClassMetadata {
442
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
443 24
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
444
445 24
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
446 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
447
        }
448
449 24
        $metadata->isMappedSuperclass = true;
450 24
        $metadata->isEmbeddedClass    = false;
451
452 24
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
453 24
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
454 24
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
455
456 24
        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 365
    private function convertPropertyAnnotationsToProperty(
479
        array $propertyAnnotations,
480
        \ReflectionProperty $reflectionProperty,
481
        Mapping\ClassMetadata $metadata
482
    ) : ?Mapping\Property {
483
        switch (true) {
484 365
            case isset($propertyAnnotations[Annotation\Column::class]):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
485 360
                return $this->convertReflectionPropertyToFieldMetadata(
486 360
                    $reflectionProperty,
487 360
                    $propertyAnnotations,
488 360
                    $metadata
489
                );
490
491 253
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
492 111
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
493 111
                    $reflectionProperty,
494 111
                    $propertyAnnotations,
495 111
                    $metadata
496
                );
497
498 202
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
499 137
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
500 137
                    $reflectionProperty,
501 137
                    $propertyAnnotations,
502 137
                    $metadata
503
                );
504
505 164
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
506 107
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
507 107
                    $reflectionProperty,
508 107
                    $propertyAnnotations,
509 107
                    $metadata
510
                );
511
512 108
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
513 90
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
514 90
                    $reflectionProperty,
515 90
                    $propertyAnnotations,
516 90
                    $metadata
517
                );
518
519 33
            case isset($propertyAnnotations[Annotation\Embedded::class]):
520 1
                return null;
521
522
            default:
523 32
                return new Mapping\TransientMetadata($reflectionProperty->getName());
524
        }
525
    }
526
527
    /**
528
     * @param Annotation\Annotation[] $propertyAnnotations
529
     *
530
     * @throws Mapping\MappingException
531
     */
532 360
    private function convertReflectionPropertyToFieldMetadata(
533
        \ReflectionProperty $reflProperty,
534
        array $propertyAnnotations,
535
        Mapping\ClassMetadata $metadata
536
    ) : Mapping\FieldMetadata {
537 360
        $className   = $metadata->getClassName();
538 360
        $fieldName   = $reflProperty->getName();
539 360
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
540 360
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
541
542 360
        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 360
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
547
548
        // Check for Id
549 360
        if (isset($propertyAnnotations[Annotation\Id::class])) {
550 357
            $fieldMetadata->setPrimaryKey(true);
551
        }
552
553
        // Check for GeneratedValue strategy
554 360
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
555 307
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
556 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...
557 307
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
558
559 307
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
560 287
                $idGeneratorDefinition = [];
561
562
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
563
                switch (true) {
564 287
                    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 278
                    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 275
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
586
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
587
                }
588
589 287
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
590
            }
591
        }
592
593 360
        return $fieldMetadata;
594
    }
595
596
    /**
597
     * @param Annotation\Annotation[] $propertyAnnotations
598
     *
599
     * @return Mapping\OneToOneAssociationMetadata
600
     */
601 111
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
602
        \ReflectionProperty $reflectionProperty,
603
        array $propertyAnnotations,
604
        Mapping\ClassMetadata $metadata
605
    ) {
606 111
        $className     = $metadata->getClassName();
607 111
        $fieldName     = $reflectionProperty->getName();
608 111
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
609 111
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
610 111
        $targetEntity  = $oneToOneAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
611
612 111
        $assocMetadata->setTargetEntity($targetEntity);
613 111
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
614 111
        $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval);
0 ignored issues
show
Bug introduced by
Accessing orphanRemoval on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
615 111
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
616
617 111
        if (! empty($oneToOneAnnot->mappedBy)) {
0 ignored issues
show
Bug introduced by
Accessing mappedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
618 40
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
619 40
            $assocMetadata->setOwningSide(false);
620
        }
621
622 111
        if (! empty($oneToOneAnnot->inversedBy)) {
0 ignored issues
show
Bug introduced by
Accessing inversedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
623 53
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
624
        }
625
626
        // Check for Id
627 111
        if (isset($propertyAnnotations[Annotation\Id::class])) {
628 9
            $assocMetadata->setPrimaryKey(true);
629
        }
630
631 111
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
632
633
        // Check for JoinColumn/JoinColumns annotations
634
        switch (true) {
635 111
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
636 80
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
637
638 80
                $assocMetadata->addJoinColumn(
639 80
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
640
                );
641
642 80
                break;
643
644 52
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
645 3
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
646
647 3
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
648 3
                    $assocMetadata->addJoinColumn(
649 3
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
650
                    );
651
                }
652
653 3
                break;
654
        }
655
656 111
        return $assocMetadata;
657
    }
658
659
    /**
660
     * @param Annotation\Annotation[] $propertyAnnotations
661
     *
662
     * @return Mapping\ManyToOneAssociationMetadata
663
     */
664 137
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
665
        \ReflectionProperty $reflectionProperty,
666
        array $propertyAnnotations,
667
        Mapping\ClassMetadata $metadata
668
    ) {
669 137
        $className      = $metadata->getClassName();
670 137
        $fieldName      = $reflectionProperty->getName();
671 137
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
672 137
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
673 137
        $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 137
        $assocMetadata->setTargetEntity($targetEntity);
676 137
        $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 137
        $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 137
        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 91
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
681
        }
682
683
        // Check for Id
684 137
        if (isset($propertyAnnotations[Annotation\Id::class])) {
685 32
            $assocMetadata->setPrimaryKey(true);
686
        }
687
688 137
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
689
690
        // Check for JoinColumn/JoinColumns annotations
691
        switch (true) {
692 137
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
693 78
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
694
695 78
                $assocMetadata->addJoinColumn(
696 78
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
697
                );
698
699 78
                break;
700
701 68
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
702 16
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
703
704 16
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
705 16
                    $assocMetadata->addJoinColumn(
706 16
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
707
                    );
708
                }
709
710 16
                break;
711
        }
712
713 137
        return $assocMetadata;
714
    }
715
716
    /**
717
     * @param Annotation\Annotation[] $propertyAnnotations
718
     *
719
     * @throws Mapping\MappingException
720
     */
721 107
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
722
        \ReflectionProperty $reflectionProperty,
723
        array $propertyAnnotations,
724
        Mapping\ClassMetadata $metadata
725
    ) : Mapping\OneToManyAssociationMetadata {
726 107
        $className      = $metadata->getClassName();
727 107
        $fieldName      = $reflectionProperty->getName();
728 107
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
729 107
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
730 107
        $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 107
        $assocMetadata->setTargetEntity($targetEntity);
733 107
        $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 107
        $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 107
        $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 107
        $assocMetadata->setOwningSide(false);
737 107
        $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 107
        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 7
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
741
        }
742
743
        // Check for OrderBy
744 107
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
745 14
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
746
747 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...
748
        }
749
750
        // Check for Id
751 107
        if (isset($propertyAnnotations[Annotation\Id::class])) {
752
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
753
        }
754
755 107
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
756
757 107
        return $assocMetadata;
758
    }
759
760
    /**
761
     * @param Annotation\Annotation[] $propertyAnnotations
762
     *
763
     * @throws Mapping\MappingException
764
     */
765 90
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
766
        \ReflectionProperty $reflectionProperty,
767
        array $propertyAnnotations,
768
        Mapping\ClassMetadata $metadata
769
    ) : Mapping\ManyToManyAssociationMetadata {
770 90
        $className       = $metadata->getClassName();
771 90
        $fieldName       = $reflectionProperty->getName();
772 90
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
773 90
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
774 90
        $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 90
        $assocMetadata->setTargetEntity($targetEntity);
777 90
        $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 90
        $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 90
        $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 90
        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 36
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
783 36
            $assocMetadata->setOwningSide(false);
784
        }
785
786 90
        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 46
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
788
        }
789
790 90
        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 3
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
792
        }
793
794
        // Check for JoinTable
795 90
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
796 72
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
797 72
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
798
799 72
            $assocMetadata->setJoinTable($joinTableMetadata);
800
        }
801
802
        // Check for OrderBy
803 90
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
804 3
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
805
806 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...
807
        }
808
809
        // Check for Id
810 90
        if (isset($propertyAnnotations[Annotation\Id::class])) {
811
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
812
        }
813
814 90
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
815
816 90
        return $assocMetadata;
817
    }
818
819
    /**
820
     * Parse the given Column as FieldMetadata
821
     */
822 360
    private function convertColumnAnnotationToFieldMetadata(
823
        Annotation\Column $columnAnnot,
824
        string $fieldName,
825
        bool $isVersioned
826
    ) : Mapping\FieldMetadata {
827 360
        $fieldMetadata = $isVersioned
828 16
            ? new Mapping\VersionFieldMetadata($fieldName)
829 360
            : new Mapping\FieldMetadata($fieldName)
830
        ;
831
832 360
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
833
834 360
        if (! empty($columnAnnot->name)) {
835 77
            $fieldMetadata->setColumnName($columnAnnot->name);
836
        }
837
838 360
        if (! empty($columnAnnot->columnDefinition)) {
839 4
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
840
        }
841
842 360
        if (! empty($columnAnnot->length)) {
843 360
            $fieldMetadata->setLength($columnAnnot->length);
844
        }
845
846 360
        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 360
        $fieldMetadata->setScale($columnAnnot->scale);
851 360
        $fieldMetadata->setPrecision($columnAnnot->precision);
852 360
        $fieldMetadata->setNullable($columnAnnot->nullable);
853 360
        $fieldMetadata->setUnique($columnAnnot->unique);
854
855 360
        return $fieldMetadata;
856
    }
857
858
    /**
859
     * Parse the given Table as TableMetadata
860
     */
861 192
    private function convertTableAnnotationToTableMetadata(
862
        Annotation\Table $tableAnnot,
863
        Mapping\TableMetadata $tableMetadata
864
    ) : void {
865 192
        if (! empty($tableAnnot->name)) {
866 187
            $tableMetadata->setName($tableAnnot->name);
867
        }
868
869 192
        if (! empty($tableAnnot->schema)) {
870 5
            $tableMetadata->setSchema($tableAnnot->schema);
871
        }
872
873 192
        foreach ($tableAnnot->options as $optionName => $optionValue) {
874 4
            $tableMetadata->addOption($optionName, $optionValue);
875
        }
876
877 192
        foreach ($tableAnnot->indexes as $indexAnnot) {
878 13
            $tableMetadata->addIndex([
879 13
                'name'    => $indexAnnot->name,
880 13
                'columns' => $indexAnnot->columns,
881 13
                'unique'  => $indexAnnot->unique,
882 13
                'options' => $indexAnnot->options,
883 13
                'flags'   => $indexAnnot->flags,
884
            ]);
885
        }
886
887 192
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
888 6
            $tableMetadata->addUniqueConstraint([
889 6
                'name'    => $uniqueConstraintAnnot->name,
890 6
                'columns' => $uniqueConstraintAnnot->columns,
891 6
                'options' => $uniqueConstraintAnnot->options,
892 6
                'flags'   => $uniqueConstraintAnnot->flags,
893
            ]);
894
        }
895 192
    }
896
897
    /**
898
     * Parse the given JoinTable as JoinTableMetadata
899
     */
900 72
    private function convertJoinTableAnnotationToJoinTableMetadata(
901
        Annotation\JoinTable $joinTableAnnot
902
    ) : Mapping\JoinTableMetadata {
903 72
        $joinTable = new Mapping\JoinTableMetadata();
904
905 72
        if (! empty($joinTableAnnot->name)) {
906 70
            $joinTable->setName($joinTableAnnot->name);
907
        }
908
909 72
        if (! empty($joinTableAnnot->schema)) {
910
            $joinTable->setSchema($joinTableAnnot->schema);
911
        }
912
913 72
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
914 71
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
915
916 71
            $joinTable->addJoinColumn($joinColumn);
917
        }
918
919 72
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
920 71
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
921
922 71
            $joinTable->addInverseJoinColumn($joinColumn);
923
        }
924
925 72
        return $joinTable;
926
    }
927
928
    /**
929
     * Parse the given JoinColumn as JoinColumnMetadata
930
     */
931 180
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
932
        Annotation\JoinColumn $joinColumnAnnot
933
    ) : Mapping\JoinColumnMetadata {
934 180
        $joinColumn = new Mapping\JoinColumnMetadata();
935
936
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
937 180
        if (! empty($joinColumnAnnot->name)) {
938 173
            $joinColumn->setColumnName($joinColumnAnnot->name);
939
        }
940
941 180
        if (! empty($joinColumnAnnot->referencedColumnName)) {
942 180
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
943
        }
944
945 180
        $joinColumn->setNullable($joinColumnAnnot->nullable);
946 180
        $joinColumn->setUnique($joinColumnAnnot->unique);
947
948 180
        if (! empty($joinColumnAnnot->fieldName)) {
949
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
950
        }
951
952 180
        if (! empty($joinColumnAnnot->columnDefinition)) {
953 3
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
954
        }
955
956 180
        if ($joinColumnAnnot->onDelete) {
957 16
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
958
        }
959
960 180
        return $joinColumn;
961
    }
962
963
    /**
964
     * Parse the given Cache as CacheMetadata
965
     *
966
     * @param string|null $fieldName
967
     */
968 18
    private function convertCacheAnnotationToCacheMetadata(
969
        Annotation\Cache $cacheAnnot,
970
        Mapping\ClassMetadata $metadata,
971
        $fieldName = null
972
    ) : Mapping\CacheMetadata {
973 18
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
974 18
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
975
976 18
        $usage  = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
977 18
        $region = $cacheAnnot->region ?: $defaultRegion;
978
979 18
        return new Mapping\CacheMetadata($usage, $region);
980
    }
981
982
    /**
983
     * @return mixed[]
984
     */
985 15
    private function convertSqlResultSetMapping(Annotation\SqlResultSetMapping $resultSetMapping)
986
    {
987 15
        $entities = [];
988
989 15
        foreach ($resultSetMapping->entities as $entityResultAnnot) {
990
            $entityResult = [
991 15
                'fields'                => [],
992 15
                'entityClass'           => $entityResultAnnot->entityClass,
993 15
                'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
994
            ];
995
996 15
            foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
997 15
                $entityResult['fields'][] = [
998 15
                    'name'      => $fieldResultAnnot->name,
999 15
                    'column'    => $fieldResultAnnot->column,
1000
                ];
1001
            }
1002
1003 15
            $entities[] = $entityResult;
1004
        }
1005
1006 15
        $columns = [];
1007
1008 15
        foreach ($resultSetMapping->columns as $columnResultAnnot) {
1009 8
            $columns[] = [
1010 8
                'name' => $columnResultAnnot->name,
1011
            ];
1012
        }
1013
1014
        return [
1015 15
            'name'     => $resultSetMapping->name,
1016 15
            'entities' => $entities,
1017 15
            'columns'  => $columns,
1018
        ];
1019
    }
1020
1021
    /**
1022
     * @param Annotation\Annotation[] $classAnnotations
1023
     */
1024 365
    private function attachTable(
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
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
1029
    ) : void {
1030 365
        $parent = $metadata->getParent();
1031
1032 365
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
1033
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
1034
            do {
1035 29
                if (! $parent->isMappedSuperclass) {
1036 29
                    $metadata->setTable($parent->table);
1037
1038 29
                    break;
1039
                }
1040
1041 4
                $parent = $parent->getParent();
1042 4
            } while ($parent !== null);
1043
1044 29
            return;
1045
        }
1046
1047 365
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1048 365
        $tableMetadata  = new Mapping\TableMetadata();
1049
1050 365
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1051
1052
        // Evaluate @Table annotation
1053 365
        if (isset($classAnnotations[Annotation\Table::class])) {
1054 192
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1055
1056 192
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1057
        }
1058
1059 365
        $metadata->setTable($tableMetadata);
1060 365
    }
1061
1062
    /**
1063
     * @param Annotation\Annotation[] $classAnnotations
1064
     *
1065
     * @throws Mapping\MappingException
1066
     */
1067 80
    private function attachDiscriminatorColumn(
1068
        array $classAnnotations,
1069
        \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

1069
        /** @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...
1070
        Mapping\ClassMetadata $metadata
1071
    ) : void {
1072 80
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1073
1074 80
        $discriminatorColumn->setTableName($metadata->getTableName());
1075 80
        $discriminatorColumn->setColumnName('dtype');
1076 80
        $discriminatorColumn->setType(Type::getType('string'));
1077 80
        $discriminatorColumn->setLength(255);
1078
1079
        // Evaluate DiscriminatorColumn annotation
1080 80
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1081
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1082 64
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1083 64
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1084 60
                ? $discriminatorColumnAnnotation->type
1085 64
                : 'string';
1086
1087 64
            $discriminatorColumn->setType(Type::getType($typeName));
1088 64
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1089
1090 64
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1091 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1092
            }
1093
1094 64
            if (! empty($discriminatorColumnAnnotation->length)) {
1095 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1096
            }
1097
        }
1098
1099 80
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1100
1101
        // Evaluate DiscriminatorMap annotation
1102 80
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1103 79
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1104 79
            $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...
1105
1106 79
            $metadata->setDiscriminatorMap($discriminatorMap);
1107
        }
1108 80
    }
1109
1110
    /**
1111
     * @param Annotation\Annotation[] $classAnnotations
1112
     */
1113 366
    private function attachNamedNativeQueries(
1114
        array $classAnnotations,
1115
        \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

1115
        /** @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...
1116
        Mapping\ClassMetadata $metadata
1117
    ) : void {
1118
        // Evaluate @NamedNativeQueries annotation
1119 366
        if (isset($classAnnotations[Annotation\NamedNativeQueries::class])) {
1120 15
            $namedNativeQueriesAnnot = $classAnnotations[Annotation\NamedNativeQueries::class];
1121
1122 15
            foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
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...
1123 15
                $metadata->addNamedNativeQuery(
1124 15
                    $namedNativeQuery->name,
1125 15
                    $namedNativeQuery->query,
1126
                    [
1127 15
                        'resultClass'      => $namedNativeQuery->resultClass,
1128 15
                        'resultSetMapping' => $namedNativeQuery->resultSetMapping,
1129
                    ]
1130
                );
1131
            }
1132
        }
1133
1134
        // Evaluate @SqlResultSetMappings annotation
1135 366
        if (isset($classAnnotations[Annotation\SqlResultSetMappings::class])) {
1136 15
            $sqlResultSetMappingsAnnot = $classAnnotations[Annotation\SqlResultSetMappings::class];
1137
1138 15
            foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
1139 15
                $sqlResultSetMapping = $this->convertSqlResultSetMapping($resultSetMapping);
1140
1141 15
                $metadata->addSqlResultSetMapping($sqlResultSetMapping);
1142
            }
1143
        }
1144 366
    }
1145
1146
    /**
1147
     * @param Annotation\Annotation[] $classAnnotations
1148
     */
1149 366
    private function attachLifecycleCallbacks(
1150
        array $classAnnotations,
1151
        \ReflectionClass $reflectionClass,
1152
        Mapping\ClassMetadata $metadata
1153
    ) : void {
1154
        // Evaluate @HasLifecycleCallbacks annotation
1155 366
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1156
            /* @var $method \ReflectionMethod */
1157 15
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1158 14
                foreach ($this->getMethodCallbacks($method) as $callback) {
1159 14
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1160
                }
1161
            }
1162
        }
1163 366
    }
1164
1165
    /**
1166
     * @param Annotation\Annotation[] $classAnnotations
1167
     *
1168
     * @throws \ReflectionException
1169
     * @throws Mapping\MappingException
1170
     */
1171 366
    private function attachEntityListeners(
1172
        array $classAnnotations,
1173
        \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

1173
        /** @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...
1174
        Mapping\ClassMetadata $metadata
1175
    ) : void {
1176
        // Evaluate @EntityListeners annotation
1177 366
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1178
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1179 10
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1180
1181 10
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1182 10
                if (! class_exists($listenerClassName)) {
1183
                    throw Mapping\MappingException::entityListenerClassNotFound(
1184
                        $listenerClassName,
1185
                        $metadata->getClassName()
1186
                    );
1187
                }
1188
1189 10
                $listenerClass = new \ReflectionClass($listenerClassName);
1190
1191
                /* @var $method \ReflectionMethod */
1192 10
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1193 10
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1194 10
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1195
                    }
1196
                }
1197
            }
1198
        }
1199 366
    }
1200
1201
    /**
1202
     * @param Annotation\Annotation[] $classAnnotations
1203
     *
1204
     * @throws Mapping\MappingException
1205
     */
1206 363
    private function attachPropertyOverrides(
1207
        array $classAnnotations,
1208
        \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

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