Passed
Pull Request — master (#7448)
by Ilya
14:31
created

AnnotationDriver::getPaths()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\Reader;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Annotation;
11
use Doctrine\ORM\Cache\Exception\CacheException;
12
use Doctrine\ORM\Events;
13
use Doctrine\ORM\Mapping;
14
use FilesystemIterator;
15
use RecursiveDirectoryIterator;
16
use RecursiveIteratorIterator;
17
use RecursiveRegexIterator;
18
use ReflectionClass;
19
use ReflectionException;
20
use ReflectionMethod;
21
use ReflectionProperty;
22
use RegexIterator;
23
use RuntimeException;
24
use UnexpectedValueException;
25
use function array_diff;
26
use function array_intersect;
27
use function array_map;
28
use function array_merge;
29
use function array_unique;
30
use function class_exists;
31
use function constant;
32
use function count;
33
use function defined;
34
use function get_class;
35
use function get_declared_classes;
36
use function in_array;
37
use function is_dir;
38
use function is_numeric;
39
use function preg_match;
40
use function preg_quote;
41
use function realpath;
42
use function sprintf;
43
use function str_replace;
44
use function strpos;
45
use function strtolower;
46
use function strtoupper;
47
48
/**
49
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
50
 */
51
class AnnotationDriver implements MappingDriver
52
{
53
    /** @var int[] */
54
    protected $entityAnnotationClasses = [
55
        Annotation\Entity::class           => 1,
56
        Annotation\MappedSuperclass::class => 2,
57
    ];
58
59
    /**
60
     * The AnnotationReader.
61
     *
62
     * @var AnnotationReader
63
     */
64
    protected $reader;
65
66
    /**
67
     * The paths where to look for mapping files.
68
     *
69
     * @var string[]
70
     */
71
    protected $paths = [];
72
73
    /**
74
     * The paths excluded from path where to look for mapping files.
75
     *
76
     * @var string[]
77
     */
78
    protected $excludePaths = [];
79
80
    /**
81
     * The file extension of mapping documents.
82
     *
83
     * @var string
84
     */
85
    protected $fileExtension = '.php';
86
87
    /**
88
     * Cache for AnnotationDriver#getAllClassNames().
89
     *
90
     * @var string[]|null
91
     */
92
    protected $classNames;
93
94
    /**
95
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
96
     * docblock annotations.
97
     *
98
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
99
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
100
     */
101 2286
    public function __construct(Reader $reader, $paths = null)
102
    {
103 2286
        $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...
104 2286
        if ($paths) {
105 2200
            $this->addPaths((array) $paths);
106
        }
107 2286
    }
108
109
    /**
110
     * Appends lookup paths to metadata driver.
111
     *
112
     * @param string[] $paths
113
     */
114 2204
    public function addPaths(array $paths)
115
    {
116 2204
        $this->paths = array_unique(array_merge($this->paths, $paths));
117 2204
    }
118
119
    /**
120
     * Retrieves the defined metadata lookup paths.
121
     *
122
     * @return string[]
123
     */
124
    public function getPaths()
125
    {
126
        return $this->paths;
127
    }
128
129
    /**
130
     * Append exclude lookup paths to metadata driver.
131
     *
132
     * @param string[] $paths
133
     */
134
    public function addExcludePaths(array $paths)
135
    {
136
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
137
    }
138
139
    /**
140
     * Retrieve the defined metadata lookup exclude paths.
141
     *
142
     * @return string[]
143
     */
144
    public function getExcludePaths()
145
    {
146
        return $this->excludePaths;
147
    }
148
149
    /**
150
     * Retrieve the current annotation reader
151
     *
152
     * @return AnnotationReader
153
     */
154 1
    public function getReader()
155
    {
156 1
        return $this->reader;
157
    }
158
159
    /**
160
     * Gets the file extension used to look for mapping files under.
161
     *
162
     * @return string
163
     */
164
    public function getFileExtension()
165
    {
166
        return $this->fileExtension;
167
    }
168
169
    /**
170
     * Sets the file extension used to look for mapping files under.
171
     *
172
     * @param string $fileExtension The file extension to set.
173
     */
174
    public function setFileExtension($fileExtension)
175
    {
176
        $this->fileExtension = $fileExtension;
177
    }
178
179
    /**
180
     * Returns whether the class with the specified name is transient. Only non-transient
181
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
182
     *
183
     * A class is non-transient if it is annotated with an annotation
184
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
185
     *
186
     * @param string $className
187
     *
188
     * @return bool
189
     */
190 194
    public function isTransient($className)
191
    {
192 194
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
193
194 194
        foreach ($classAnnotations as $annot) {
195 189
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
196 189
                return false;
197
            }
198
        }
199 12
        return true;
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205 60
    public function getAllClassNames()
206
    {
207 60
        if ($this->classNames !== null) {
208 45
            return $this->classNames;
209
        }
210
211 60
        if (! $this->paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
212
            throw Mapping\MappingException::pathRequired();
213
        }
214
215 60
        $classes       = [];
216 60
        $includedFiles = [];
217
218 60
        foreach ($this->paths as $path) {
219 60
            if (! is_dir($path)) {
220
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
221
            }
222
223 60
            $iterator = new RegexIterator(
224 60
                new RecursiveIteratorIterator(
225 60
                    new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
226 60
                    RecursiveIteratorIterator::LEAVES_ONLY
227
                ),
228 60
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
229 60
                RecursiveRegexIterator::GET_MATCH
230
            );
231
232 60
            foreach ($iterator as $file) {
233 60
                $sourceFile = $file[0];
234
235 60
                if (! preg_match('(^phar:)i', $sourceFile)) {
236 60
                    $sourceFile = realpath($sourceFile);
237
                }
238
239 60
                foreach ($this->excludePaths as $excludePath) {
240
                    $exclude = str_replace('\\', '/', realpath($excludePath));
241
                    $current = str_replace('\\', '/', $sourceFile);
242
243
                    if (strpos($current, $exclude) !== false) {
244
                        continue 2;
245
                    }
246
                }
247
248 60
                require_once $sourceFile;
249
250 60
                $includedFiles[] = $sourceFile;
251
            }
252
        }
253
254 60
        $declared = get_declared_classes();
255
256 60
        foreach ($declared as $className) {
257 60
            $rc         = new ReflectionClass($className);
258 60
            $sourceFile = $rc->getFileName();
259 60
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
260 60
                $classes[] = $className;
261
            }
262
        }
263
264 60
        $this->classNames = $classes;
265
266 60
        return $classes;
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     *
272
     * @throws CacheException
273
     * @throws Mapping\MappingException
274
     * @throws ReflectionException
275
     * @throws RuntimeException
276
     */
277 376
    public function loadMetadataForClass(
278
        string $className,
279
        Mapping\ClassMetadata $metadata,
280
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
281
    ) : Mapping\ClassMetadata {
282 376
        $reflectionClass = $metadata->getReflectionClass();
283
284 376
        if (! $reflectionClass) {
285
            // this happens when running annotation driver in combination with
286
            // static reflection services. This is not the nicest fix
287
            $reflectionClass = new ReflectionClass($metadata->getClassName());
288
        }
289
290 376
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
291 376
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
292 376
            $classAnnotations,
293 376
            $reflectionClass,
294 376
            $metadata,
295 376
            $metadataBuildingContext
296
        );
297
298
        // Evaluate @Cache annotation
299 373
        if (isset($classAnnotations[Annotation\Cache::class])) {
300 18
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
301 18
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
302
303 18
            $classMetadata->setCache($cache);
304
        }
305
306
        // Evaluate annotations on properties/fields
307
        /** @var ReflectionProperty $reflProperty */
308 373
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
309 373
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
310 75
                continue;
311
            }
312
313 373
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
314 372
            $property            = $this->convertPropertyAnnotationsToProperty(
315 372
                $propertyAnnotations,
316 372
                $reflectionProperty,
317 372
                $classMetadata
318
            );
319
320 372
            if ($classMetadata->isMappedSuperclass &&
321 372
                $property instanceof Mapping\ToManyAssociationMetadata &&
322 372
                ! $property->isOwningSide()) {
323 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
324 1
                    $classMetadata->getClassName(),
325 1
                    $property->getName()
326
                );
327
            }
328
329 371
            if (! $property) {
330 1
                continue;
331
            }
332
333 371
            $metadata->addProperty($property);
334
        }
335
336 370
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
337
338 370
        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...
339
    }
340
341
    /**
342
     * @param Annotation\Annotation[] $classAnnotations
343
     *
344
     * @throws Mapping\MappingException
345
     */
346 376
    private function convertClassAnnotationsToClassMetadata(
347
        array $classAnnotations,
348
        ReflectionClass $reflectionClass,
349
        Mapping\ClassMetadata $metadata,
350
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
351
    ) : Mapping\ClassMetadata {
352
        switch (true) {
353 376
            case isset($classAnnotations[Annotation\Entity::class]):
354 372
                return $this->convertClassAnnotationsToEntityClassMetadata(
355 372
                    $classAnnotations,
356 372
                    $reflectionClass,
357 372
                    $metadata,
358 372
                    $metadataBuildingContext
359
                );
360
361
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
362
363 26
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
364 23
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
365 23
                    $classAnnotations,
366 23
                    $reflectionClass,
367 23
                    $metadata
368
                );
369
370 3
            case isset($classAnnotations[Annotation\Embeddable::class]):
371
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
372
                    $classAnnotations,
373
                    $reflectionClass,
374
                    $metadata
375
                );
376
377
            default:
378 3
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
379
        }
380
    }
381
382
    /**
383
     * @param Annotation\Annotation[] $classAnnotations
384
     *
385
     * @return Mapping\ClassMetadata
386
     *
387
     * @throws Mapping\MappingException
388
     * @throws UnexpectedValueException
389
     */
390 372
    private function convertClassAnnotationsToEntityClassMetadata(
391
        array $classAnnotations,
392
        ReflectionClass $reflectionClass,
393
        Mapping\ClassMetadata $metadata,
394
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
395
    ) {
396
        /** @var Annotation\Entity $entityAnnot */
397 372
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
398
399 372
        if ($entityAnnot->repositoryClass !== null) {
400 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
401
        }
402
403 372
        if ($entityAnnot->readOnly) {
404 1
            $metadata->asReadOnly();
405
        }
406
407 372
        $metadata->isMappedSuperclass = false;
408 372
        $metadata->isEmbeddedClass    = false;
409
410 372
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
411
412
        // Evaluate @ChangeTrackingPolicy annotation
413 372
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
414 5
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
415
416 5
            $metadata->setChangeTrackingPolicy(
417 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...
418
            );
419
        }
420
421
        // Evaluate @InheritanceType annotation
422 372
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
423 81
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
424
425 81
            $metadata->setInheritanceType(
426 81
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
427
            );
428
429 81
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
430 81
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
431
            }
432
        }
433
434 372
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
435 372
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
436
437 372
        return $metadata;
438
    }
439
440
    /**
441
     * @param Annotation\Annotation[] $classAnnotations
442
     */
443 23
    private function convertClassAnnotationsToMappedSuperClassMetadata(
444
        array $classAnnotations,
445
        ReflectionClass $reflectionClass,
446
        Mapping\ClassMetadata $metadata
447
    ) : Mapping\ClassMetadata {
448
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
449 23
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
450
451 23
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
452 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
453
        }
454
455 23
        $metadata->isMappedSuperclass = true;
456 23
        $metadata->isEmbeddedClass    = false;
457
458 23
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
459 23
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
460
461 23
        return $metadata;
462
    }
463
464
    /**
465
     * @param Annotation\Annotation[] $classAnnotations
466
     */
467
    private function convertClassAnnotationsToEmbeddableClassMetadata(
468
        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

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

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

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

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

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

1102
        /** @scrutinizer ignore-unused */ ReflectionClass $reflectionClass,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

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

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