Failed Conditions
Push — master ( e747f7...5b15a6 )
by Guilherme
19:57
created

convertTableAnnotationToTableMetadata()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 20
nc 32
nop 2
dl 0
loc 36
ccs 21
cts 21
cp 1
crap 6
rs 8.9777
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 2299
    public function __construct(Reader $reader, $paths = null)
102
    {
103 2299
        $this->reader = $reader;
0 ignored issues
show
Documentation Bug introduced by
$reader is of type Doctrine\Common\Annotations\Reader, but the property $reader was declared to be of type Doctrine\Common\Annotations\AnnotationReader. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
104
105 2299
        if ($paths) {
106 2213
            $this->addPaths((array) $paths);
107
        }
108 2299
    }
109
110
    /**
111
     * Appends lookup paths to metadata driver.
112
     *
113
     * @param string[] $paths
114
     */
115 2217
    public function addPaths(array $paths)
116
    {
117 2217
        $this->paths = array_unique(array_merge($this->paths, $paths));
118 2217
    }
119
120
    /**
121
     * Retrieves the defined metadata lookup paths.
122
     *
123
     * @return string[]
124
     */
125
    public function getPaths()
126
    {
127
        return $this->paths;
128
    }
129
130
    /**
131
     * Append exclude lookup paths to metadata driver.
132
     *
133
     * @param string[] $paths
134
     */
135
    public function addExcludePaths(array $paths)
136
    {
137
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
138
    }
139
140
    /**
141
     * Retrieve the defined metadata lookup exclude paths.
142
     *
143
     * @return string[]
144
     */
145
    public function getExcludePaths()
146
    {
147
        return $this->excludePaths;
148
    }
149
150
    /**
151
     * Retrieve the current annotation reader
152
     *
153
     * @return Reader
154
     */
155 1
    public function getReader()
156
    {
157 1
        return $this->reader;
158
    }
159
160
    /**
161
     * Gets the file extension used to look for mapping files under.
162
     *
163
     * @return string
164
     */
165
    public function getFileExtension()
166
    {
167
        return $this->fileExtension;
168
    }
169
170
    /**
171
     * Sets the file extension used to look for mapping files under.
172
     *
173
     * @param string $fileExtension The file extension to set.
174
     */
175
    public function setFileExtension($fileExtension)
176
    {
177
        $this->fileExtension = $fileExtension;
178
    }
179
180
    /**
181
     * Returns whether the class with the specified name is transient. Only non-transient
182
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
183
     *
184
     * A class is non-transient if it is annotated with an annotation
185
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
186
     *
187
     * @param string $className
188
     *
189
     * @return bool
0 ignored issues
show
introduced by
Method \Doctrine\ORM\Mapping\Driver\AnnotationDriver::isTransient() has useless @return annotation.
Loading history...
190
     *
191
     * @throws ReflectionException
192
     */
193 193
    public function isTransient($className) : bool
194
    {
195 193
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
196
197 193
        foreach ($classAnnotations as $annot) {
198 188
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
199 188
                return false;
200
            }
201
        }
202
203 12
        return true;
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209 60
    public function getAllClassNames() : array
210
    {
211 60
        if ($this->classNames !== null) {
212 45
            return $this->classNames;
213
        }
214
215 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...
216
            throw Mapping\MappingException::pathRequired();
217
        }
218
219 60
        $classes       = [];
220 60
        $includedFiles = [];
221
222 60
        foreach ($this->paths as $path) {
223 60
            if (! is_dir($path)) {
224
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
225
            }
226
227 60
            $iterator = new RegexIterator(
228 60
                new RecursiveIteratorIterator(
229 60
                    new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
230 60
                    RecursiveIteratorIterator::LEAVES_ONLY
231
                ),
232 60
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
233 60
                RecursiveRegexIterator::GET_MATCH
234
            );
235
236 60
            foreach ($iterator as $file) {
237 60
                $sourceFile = $file[0];
238
239 60
                if (! preg_match('(^phar:)i', $sourceFile)) {
240 60
                    $sourceFile = realpath($sourceFile);
241
                }
242
243 60
                foreach ($this->excludePaths as $excludePath) {
244
                    $exclude = str_replace('\\', '/', realpath($excludePath));
245
                    $current = str_replace('\\', '/', $sourceFile);
246
247
                    if (strpos($current, $exclude) !== false) {
248
                        continue 2;
249
                    }
250
                }
251
252 60
                require_once $sourceFile;
253
254 60
                $includedFiles[] = $sourceFile;
255
            }
256
        }
257
258 60
        $declared = get_declared_classes();
259
260 60
        foreach ($declared as $className) {
261 60
            $rc         = new ReflectionClass($className);
262 60
            $sourceFile = $rc->getFileName();
263 60
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
264 60
                $classes[] = $className;
265
            }
266
        }
267
268 60
        $this->classNames = $classes;
269
270 60
        return $classes;
271
    }
272
273
    /**
274
     * {@inheritDoc}
275
     *
276
     * @throws CacheException
277
     * @throws Mapping\MappingException
278
     * @throws ReflectionException
279
     * @throws RuntimeException
280
     * @throws UnexpectedValueException
281
     */
282 380
    public function loadMetadataForClass(
283
        string $className,
284
        ?Mapping\ComponentMetadata $parent,
285
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
286
    ) : Mapping\ComponentMetadata {
287 380
        $reflectionClass  = new ReflectionClass($className);
288 380
        $metadata         = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
289 380
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
290 380
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
291 380
            $classAnnotations,
292 380
            $reflectionClass,
293 380
            $metadata,
294 380
            $metadataBuildingContext
295
        );
296
297
        // Evaluate @Cache annotation
298 374
        if (isset($classAnnotations[Annotation\Cache::class])) {
299 18
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
300 18
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
301
302 18
            $classMetadata->setCache($cache);
303
        }
304
305
        // Evaluate annotations on properties/fields
306
        /** @var ReflectionProperty $reflProperty */
307 374
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
308 374
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
309 74
                continue;
310
            }
311
312 374
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
313 373
            $property            = $this->convertPropertyAnnotationsToProperty(
314 373
                $propertyAnnotations,
315 373
                $reflectionProperty,
316 373
                $classMetadata,
317 373
                $metadataBuildingContext
318
            );
319
320 373
            if ($classMetadata->isMappedSuperclass &&
321 373
                $property instanceof Mapping\ToManyAssociationMetadata &&
322 373
                ! $property->isOwningSide()) {
323 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
324 1
                    $classMetadata->getClassName(),
325 1
                    $property->getName()
326
                );
327
            }
328
329 372
            $metadata->addProperty($property);
330
        }
331
332 371
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
333
334 371
        return $classMetadata;
335
    }
336
337
    /**
338
     * @param Annotation\Annotation[] $classAnnotations
339
     *
340
     * @throws Mapping\MappingException
341
     * @throws UnexpectedValueException
342
     * @throws ReflectionException
343
     */
344 380
    private function convertClassAnnotationsToClassMetadata(
345
        array $classAnnotations,
346
        ReflectionClass $reflectionClass,
347
        Mapping\ClassMetadata $metadata,
348
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
349
    ) : Mapping\ClassMetadata {
350
        switch (true) {
351 380
            case isset($classAnnotations[Annotation\Entity::class]):
352 373
                return $this->convertClassAnnotationsToEntityClassMetadata(
353 373
                    $classAnnotations,
354 373
                    $reflectionClass,
355 373
                    $metadata,
356 373
                    $metadataBuildingContext
357
                );
358
359
                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...
360
361 29
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
362 23
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
363 23
                    $classAnnotations,
364 23
                    $reflectionClass,
365 23
                    $metadata
366
                );
367 6
            case isset($classAnnotations[Annotation\Embeddable::class]):
368
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
369
                    $classAnnotations,
370
                    $reflectionClass,
371
                    $metadata
372
                );
373
            default:
374 6
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
375
        }
376
    }
377
378
    /**
379
     * @param Annotation\Annotation[] $classAnnotations
380
     *
381
     * @return Mapping\ClassMetadata
382
     *
383
     * @throws Mapping\MappingException
384
     * @throws ReflectionException
385
     * @throws UnexpectedValueException
386
     */
387 373
    private function convertClassAnnotationsToEntityClassMetadata(
388
        array $classAnnotations,
389
        ReflectionClass $reflectionClass,
390
        Mapping\ClassMetadata $metadata,
391
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
392
    ) {
393
        /** @var Annotation\Entity $entityAnnot */
394 373
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
395
396 373
        if ($entityAnnot->repositoryClass !== null) {
397 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
398
        }
399
400 373
        if ($entityAnnot->readOnly) {
401 1
            $metadata->asReadOnly();
402
        }
403
404 373
        $metadata->isMappedSuperclass = false;
405 373
        $metadata->isEmbeddedClass    = false;
406
407 373
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
408
409
        // Evaluate @ChangeTrackingPolicy annotation
410 373
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
411 6
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
412
413 6
            $metadata->setChangeTrackingPolicy(
414 6
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
415
            );
416
        }
417
418
        // Evaluate @InheritanceType annotation
419 373
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
420 80
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
421
422 80
            $metadata->setInheritanceType(
423 80
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
424
            );
425
426 80
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
427 80
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
428
            }
429
        }
430
431 373
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
432 373
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
433
434 373
        return $metadata;
435
    }
436
437
    /**
438
     * @param Annotation\Annotation[] $classAnnotations
439
     *
440
     * @throws Mapping\MappingException
441
     * @throws ReflectionException
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 373
    private function convertPropertyAnnotationsToProperty(
484
        array $propertyAnnotations,
485
        ReflectionProperty $reflectionProperty,
486
        Mapping\ClassMetadata $metadata,
487
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
488
    ) : ?Mapping\Property {
489
        switch (true) {
490 373
            case isset($propertyAnnotations[Annotation\Column::class]):
491 368
                return $this->convertReflectionPropertyToFieldMetadata(
492 368
                    $reflectionProperty,
493 368
                    $propertyAnnotations,
494 368
                    $metadata,
495 368
                    $metadataBuildingContext
496
                );
497 255
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
498 112
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
499 112
                    $reflectionProperty,
500 112
                    $propertyAnnotations,
501 112
                    $metadata,
502 112
                    $metadataBuildingContext
503
                );
504 203
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
505 141
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
506 141
                    $reflectionProperty,
507 141
                    $propertyAnnotations,
508 141
                    $metadata,
509 141
                    $metadataBuildingContext
510
                );
511 164
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
512 109
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
513 109
                    $reflectionProperty,
514 109
                    $propertyAnnotations,
515 109
                    $metadata,
516 109
                    $metadataBuildingContext
517
                );
518 105
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
519 89
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
520 89
                    $reflectionProperty,
521 89
                    $propertyAnnotations,
522 89
                    $metadata,
523 89
                    $metadataBuildingContext
524
                );
525 30
            case isset($propertyAnnotations[Annotation\Embedded::class]):
526
                return null;
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 368
    private function convertReflectionPropertyToFieldMetadata(
538
        ReflectionProperty $reflectionProperty,
539
        array $propertyAnnotations,
540
        Mapping\ClassMetadata $metadata,
541
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
542
    ) : Mapping\FieldMetadata {
543 368
        $className   = $metadata->getClassName();
544 368
        $fieldName   = $reflectionProperty->getName();
545 368
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
546 368
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
547
548 368
        if ($columnAnnot->type === null) {
0 ignored issues
show
Bug introduced by
Accessing type on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
549
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
550
        }
551
552 368
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
553
554
        // Check for Id
555 368
        if (isset($propertyAnnotations[Annotation\Id::class])) {
556 364
            $fieldMetadata->setPrimaryKey(true);
557
        }
558
559
        // Check for GeneratedValue strategy
560 368
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
561 312
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
562 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...
563 312
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
564
565 312
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
566 292
                $idGeneratorDefinition = [];
567
568
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
569
                switch (true) {
570 292
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
571 9
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
572
573
                        $idGeneratorDefinition = [
574 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...
575 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...
576
                        ];
577
578 9
                        break;
579
580 283
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
581 3
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
582
583
                        $idGeneratorDefinition = [
584 3
                            'class' => $customGeneratorAnnot->class,
585 3
                            'arguments' => $customGeneratorAnnot->arguments,
586
                        ];
587
588 3
                        break;
589
590
                    /** @todo If it is not supported, why does this exist? */
591 280
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
592
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
593
                }
594
595 292
                $fieldMetadata->setValueGenerator(
596 292
                    new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition)
597
                );
598
            }
599
        }
600
601 368
        return $fieldMetadata;
602
    }
603
604
    /**
605
     * @param Annotation\Annotation[] $propertyAnnotations
606
     *
607
     * @return Mapping\OneToOneAssociationMetadata
608
     */
609 112
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
610
        ReflectionProperty $reflectionProperty,
611
        array $propertyAnnotations,
612
        Mapping\ClassMetadata $metadata,
613
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
614
    ) {
615 112
        $className     = $metadata->getClassName();
616 112
        $fieldName     = $reflectionProperty->getName();
617 112
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
618 112
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
619 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...
620
621 112
        $assocMetadata->setTargetEntity($targetEntity);
622 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...
623 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...
624 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...
625
626 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...
627 40
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
628 40
            $assocMetadata->setOwningSide(false);
629
        }
630
631 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...
632 52
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
633
        }
634
635
        // Check for Id
636 112
        if (isset($propertyAnnotations[Annotation\Id::class])) {
637 12
            $assocMetadata->setPrimaryKey(true);
638
        }
639
640 112
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
641
642
        // Check for JoinColumn/JoinColumns annotations
643
        switch (true) {
644 112
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
645 80
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
646
647 80
                $assocMetadata->addJoinColumn(
648 80
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
649
                );
650
651 80
                break;
652
653 53
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
654 3
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
655
656 3
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
657 3
                    $assocMetadata->addJoinColumn(
658 3
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
659
                    );
660
                }
661
662 3
                break;
663
        }
664
665 112
        return $assocMetadata;
666
    }
667
668
    /**
669
     * @param Annotation\Annotation[] $propertyAnnotations
670
     *
671
     * @return Mapping\ManyToOneAssociationMetadata
672
     */
673 141
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
674
        ReflectionProperty $reflectionProperty,
675
        array $propertyAnnotations,
676
        Mapping\ClassMetadata $metadata,
677
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
678
    ) {
679 141
        $className      = $metadata->getClassName();
680 141
        $fieldName      = $reflectionProperty->getName();
681 141
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
682 141
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
683 141
        $targetEntity   = $manyToOneAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
684
685 141
        $assocMetadata->setTargetEntity($targetEntity);
686 141
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
687 141
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
688
689 141
        if (! empty($manyToOneAnnot->inversedBy)) {
0 ignored issues
show
Bug introduced by
Accessing inversedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
690 94
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
691
        }
692
693
        // Check for Id
694 141
        if (isset($propertyAnnotations[Annotation\Id::class])) {
695 34
            $assocMetadata->setPrimaryKey(true);
696
        }
697
698 141
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
699
700
        // Check for JoinColumn/JoinColumns annotations
701
        switch (true) {
702 141
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
703 81
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
704
705 81
                $assocMetadata->addJoinColumn(
706 81
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
707
                );
708
709 81
                break;
710
711 69
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
712 16
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
713
714 16
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
715 16
                    $assocMetadata->addJoinColumn(
716 16
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
717
                    );
718
                }
719
720 16
                break;
721
        }
722
723 141
        return $assocMetadata;
724
    }
725
726
    /**
727
     * @param Annotation\Annotation[] $propertyAnnotations
728
     *
729
     * @throws Mapping\MappingException
730
     */
731 109
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
732
        ReflectionProperty $reflectionProperty,
733
        array $propertyAnnotations,
734
        Mapping\ClassMetadata $metadata,
735
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
736
    ) : Mapping\OneToManyAssociationMetadata {
737 109
        $className      = $metadata->getClassName();
738 109
        $fieldName      = $reflectionProperty->getName();
739 109
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
740 109
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
741 109
        $targetEntity   = $oneToManyAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
742
743 109
        $assocMetadata->setTargetEntity($targetEntity);
744 109
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
745 109
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
0 ignored issues
show
Bug introduced by
Accessing orphanRemoval on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
746 109
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
747 109
        $assocMetadata->setOwningSide(false);
748 109
        $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
0 ignored issues
show
Bug introduced by
Accessing mappedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
749
750 109
        if (! empty($oneToManyAnnot->indexBy)) {
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
751 8
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
752
        }
753
754
        // Check for OrderBy
755 109
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
756 14
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
757
758 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...
759
        }
760
761
        // Check for Id
762 109
        if (isset($propertyAnnotations[Annotation\Id::class])) {
763
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
764
        }
765
766 109
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
767
768 109
        return $assocMetadata;
769
    }
770
771
    /**
772
     * @param Annotation\Annotation[] $propertyAnnotations
773
     *
774
     * @throws Mapping\MappingException
775
     */
776 89
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
777
        ReflectionProperty $reflectionProperty,
778
        array $propertyAnnotations,
779
        Mapping\ClassMetadata $metadata,
780
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
781
    ) : Mapping\ManyToManyAssociationMetadata {
782 89
        $className       = $metadata->getClassName();
783 89
        $fieldName       = $reflectionProperty->getName();
784 89
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
785 89
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
786 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...
787
788 89
        $assocMetadata->setTargetEntity($targetEntity);
789 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...
790 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...
791 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...
792
793 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...
794 36
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
795 36
            $assocMetadata->setOwningSide(false);
796
        }
797
798 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...
799 45
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
800
        }
801
802 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...
803 3
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
804
        }
805
806
        // Check for JoinTable
807 89
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
808 71
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
809 71
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
810
811 71
            $assocMetadata->setJoinTable($joinTableMetadata);
812
        }
813
814
        // Check for OrderBy
815 89
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
816 3
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
817
818 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...
819
        }
820
821
        // Check for Id
822 89
        if (isset($propertyAnnotations[Annotation\Id::class])) {
823
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
824
        }
825
826 89
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
827
828 89
        return $assocMetadata;
829
    }
830
831
    /**
832
     * Parse the given Column as FieldMetadata
833
     */
834 368
    private function convertColumnAnnotationToFieldMetadata(
835
        Annotation\Column $columnAnnot,
836
        string $fieldName,
837
        bool $isVersioned
838
    ) : Mapping\FieldMetadata {
839 368
        $fieldMetadata = $isVersioned
840 17
            ? new Mapping\VersionFieldMetadata($fieldName)
841 368
            : new Mapping\FieldMetadata($fieldName);
842
843 368
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
844
845 368
        if (! empty($columnAnnot->name)) {
846 77
            $fieldMetadata->setColumnName($columnAnnot->name);
847
        }
848
849 368
        if (! empty($columnAnnot->columnDefinition)) {
850 4
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
851
        }
852
853 368
        if (! empty($columnAnnot->length)) {
854 368
            $fieldMetadata->setLength($columnAnnot->length);
855
        }
856
857 368
        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...
858 7
            $fieldMetadata->setOptions($columnAnnot->options);
859
        }
860
861 368
        $fieldMetadata->setScale($columnAnnot->scale);
862 368
        $fieldMetadata->setPrecision($columnAnnot->precision);
863 368
        $fieldMetadata->setNullable($columnAnnot->nullable);
864 368
        $fieldMetadata->setUnique($columnAnnot->unique);
865
866 368
        return $fieldMetadata;
867
    }
868
869
    /**
870
     * Parse the given Table as TableMetadata
871
     */
872 192
    private function convertTableAnnotationToTableMetadata(
873
        Annotation\Table $tableAnnot,
874
        Mapping\TableMetadata $tableMetadata
875
    ) : Mapping\TableMetadata {
876 192
        if (! empty($tableAnnot->name)) {
877 187
            $tableMetadata->setName($tableAnnot->name);
878
        }
879
880 192
        if (! empty($tableAnnot->schema)) {
881 5
            $tableMetadata->setSchema($tableAnnot->schema);
882
        }
883
884 192
        foreach ($tableAnnot->options as $optionName => $optionValue) {
885 4
            $tableMetadata->addOption($optionName, $optionValue);
886
        }
887
888 192
        foreach ($tableAnnot->indexes as $indexAnnot) {
889 13
            $tableMetadata->addIndex([
890 13
                'name'    => $indexAnnot->name,
891 13
                'columns' => $indexAnnot->columns,
892 13
                'unique'  => $indexAnnot->unique,
893 13
                'options' => $indexAnnot->options,
894 13
                'flags'   => $indexAnnot->flags,
895
            ]);
896
        }
897
898 192
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
899 6
            $tableMetadata->addUniqueConstraint([
900 6
                'name'    => $uniqueConstraintAnnot->name,
901 6
                'columns' => $uniqueConstraintAnnot->columns,
902 6
                'options' => $uniqueConstraintAnnot->options,
903 6
                'flags'   => $uniqueConstraintAnnot->flags,
904
            ]);
905
        }
906
907 192
        return $tableMetadata;
908
    }
909
910
    /**
911
     * Parse the given JoinTable as JoinTableMetadata
912
     */
913 71
    private function convertJoinTableAnnotationToJoinTableMetadata(
914
        Annotation\JoinTable $joinTableAnnot
915
    ) : Mapping\JoinTableMetadata {
916 71
        $joinTable = new Mapping\JoinTableMetadata();
917
918 71
        if (! empty($joinTableAnnot->name)) {
919 69
            $joinTable->setName($joinTableAnnot->name);
920
        }
921
922 71
        if (! empty($joinTableAnnot->schema)) {
923
            $joinTable->setSchema($joinTableAnnot->schema);
924
        }
925
926 71
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
927 70
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
928
929 70
            $joinTable->addJoinColumn($joinColumn);
930
        }
931
932 71
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
933 70
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
934
935 70
            $joinTable->addInverseJoinColumn($joinColumn);
936
        }
937
938 71
        return $joinTable;
939
    }
940
941
    /**
942
     * Parse the given JoinColumn as JoinColumnMetadata
943
     */
944 182
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
945
        Annotation\JoinColumn $joinColumnAnnot
946
    ) : Mapping\JoinColumnMetadata {
947 182
        $joinColumn = new Mapping\JoinColumnMetadata();
948
949
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
950 182
        if (! empty($joinColumnAnnot->name)) {
951 175
            $joinColumn->setColumnName($joinColumnAnnot->name);
952
        }
953
954 182
        if (! empty($joinColumnAnnot->referencedColumnName)) {
955 182
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
956
        }
957
958 182
        $joinColumn->setNullable($joinColumnAnnot->nullable);
959 182
        $joinColumn->setUnique($joinColumnAnnot->unique);
960
961 182
        if (! empty($joinColumnAnnot->fieldName)) {
962
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
963
        }
964
965 182
        if (! empty($joinColumnAnnot->columnDefinition)) {
966 3
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
967
        }
968
969 182
        if ($joinColumnAnnot->onDelete) {
970 16
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
971
        }
972
973 182
        return $joinColumn;
974
    }
975
976
    /**
977
     * Parse the given Cache as CacheMetadata
978
     *
979
     * @param string|null $fieldName
980
     */
981 18
    private function convertCacheAnnotationToCacheMetadata(
982
        Annotation\Cache $cacheAnnot,
983
        Mapping\ClassMetadata $metadata,
984
        $fieldName = null
985
    ) : Mapping\CacheMetadata {
986 18
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
987 18
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
988
989 18
        $usage  = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
990 18
        $region = $cacheAnnot->region ?: $defaultRegion;
991
992 18
        return new Mapping\CacheMetadata($usage, $region);
993
    }
994
995
    /**
996
     * @param Annotation\Annotation[] $classAnnotations
997
     */
998 373
    private function attachTable(
999
        array $classAnnotations,
1000
        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

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

1043
        /** @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...
1044
        Mapping\ClassMetadata $metadata,
1045
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
1046
    ) : void {
1047 80
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1048
1049 80
        $discriminatorColumn->setTableName($metadata->getTableName());
1050 80
        $discriminatorColumn->setColumnName('dtype');
1051 80
        $discriminatorColumn->setType(Type::getType('string'));
1052 80
        $discriminatorColumn->setLength(255);
1053
1054
        // Evaluate DiscriminatorColumn annotation
1055 80
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1056
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1057 62
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1058 62
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1059 58
                ? $discriminatorColumnAnnotation->type
1060 62
                : 'string';
1061
1062 62
            $discriminatorColumn->setType(Type::getType($typeName));
1063 62
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1064
1065 62
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1066 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1067
            }
1068
1069 62
            if (! empty($discriminatorColumnAnnotation->length)) {
1070 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1071
            }
1072
        }
1073
1074 80
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1075
1076
        // Evaluate DiscriminatorMap annotation
1077 80
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1078 77
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1079 77
            $discriminatorMap           = $discriminatorMapAnnotation->value;
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1080
1081 77
            $metadata->setDiscriminatorMap($discriminatorMap);
1082
        }
1083 80
    }
1084
1085
    /**
1086
     * @param Annotation\Annotation[] $classAnnotations
1087
     */
1088 374
    private function attachLifecycleCallbacks(
1089
        array $classAnnotations,
1090
        ReflectionClass $reflectionClass,
1091
        Mapping\ClassMetadata $metadata
1092
    ) : void {
1093
        // Evaluate @HasLifecycleCallbacks annotation
1094 374
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1095
            /** @var ReflectionMethod $method */
1096 14
            foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
1097 13
                foreach ($this->getMethodCallbacks($method) as $callback) {
1098 12
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1099
                }
1100
            }
1101
        }
1102 374
    }
1103
1104
    /**
1105
     * @param Annotation\Annotation[] $classAnnotations
1106
     *
1107
     * @throws ReflectionException
1108
     * @throws Mapping\MappingException
1109
     */
1110 374
    private function attachEntityListeners(
1111
        array $classAnnotations,
1112
        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

1112
        /** @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...
1113
        Mapping\ClassMetadata $metadata
1114
    ) : void {
1115
        // Evaluate @EntityListeners annotation
1116 374
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1117
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1118 9
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1119
1120 9
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1121 9
                if (! class_exists($listenerClassName)) {
1122
                    throw Mapping\MappingException::entityListenerClassNotFound(
1123
                        $listenerClassName,
1124
                        $metadata->getClassName()
1125
                    );
1126
                }
1127
1128 9
                $listenerClass = new ReflectionClass($listenerClassName);
1129
1130
                /** @var ReflectionMethod $method */
1131 9
                foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
1132 9
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1133 9
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1134
                    }
1135
                }
1136
            }
1137
        }
1138 374
    }
1139
1140
    /**
1141
     * @param Annotation\Annotation[] $classAnnotations
1142
     *
1143
     * @throws Mapping\MappingException
1144
     */
1145 371
    private function attachPropertyOverrides(
1146
        array $classAnnotations,
1147
        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

1147
        /** @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...
1148
        Mapping\ClassMetadata $metadata,
1149
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

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