Passed
Push — master ( 89e39b...ec508a )
by Marco
11:02
created

AnnotationDriver::convertSqlResultSetMapping()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 19
nc 6
nop 1
dl 0
loc 33
ccs 0
cts 18
cp 0
crap 20
rs 8.5806
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\CacheException;
12
use Doctrine\ORM\Events;
13
use Doctrine\ORM\Mapping;
14
use function array_diff;
15
use function array_intersect;
16
use function array_map;
17
use function array_merge;
18
use function array_unique;
19
use function class_exists;
20
use function constant;
21
use function count;
22
use function defined;
23
use function get_class;
24
use function get_declared_classes;
25
use function in_array;
26
use function is_dir;
27
use function is_numeric;
28
use function preg_match;
29
use function preg_quote;
30
use function realpath;
31
use function sprintf;
32
use function str_replace;
33
use function strpos;
34
use function strtolower;
35
use function strtoupper;
36
37
/**
38
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
39
 *
40
 */
41
class AnnotationDriver implements MappingDriver
42
{
43
    /** @var int[] */
44
    protected $entityAnnotationClasses = [
45
        Annotation\Entity::class           => 1,
46
        Annotation\MappedSuperclass::class => 2,
47
    ];
48
49
    /**
50
     * The AnnotationReader.
51
     *
52
     * @var AnnotationReader
53
     */
54
    protected $reader;
55
56
    /**
57
     * The paths where to look for mapping files.
58
     *
59
     * @var string[]
60
     */
61
    protected $paths = [];
62
63
    /**
64
     * The paths excluded from path where to look for mapping files.
65
     *
66
     * @var string[]
67
     */
68
    protected $excludePaths = [];
69
70
    /**
71
     * The file extension of mapping documents.
72
     *
73
     * @var string
74
     */
75
    protected $fileExtension = '.php';
76
77
    /**
78
     * Cache for AnnotationDriver#getAllClassNames().
79
     *
80
     * @var string[]|null
81
     */
82
    protected $classNames;
83
84
    /**
85
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
86
     * docblock annotations.
87
     *
88
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
89
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
90
     */
91 2272
    public function __construct(Reader $reader, $paths = null)
92
    {
93 2272
        $this->reader = $reader;
0 ignored issues
show
Documentation Bug introduced by
$reader is of type Doctrine\Common\Annotations\Reader, but the property $reader was declared to be of type Doctrine\Common\Annotations\AnnotationReader. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

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

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

class Alien {}

class Dalek extends Alien {}

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

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
94 2272
        if ($paths) {
95 2186
            $this->addPaths((array) $paths);
96
        }
97 2272
    }
98
99
    /**
100
     * Appends lookup paths to metadata driver.
101
     *
102
     * @param string[] $paths
103
     */
104 2190
    public function addPaths(array $paths)
105
    {
106 2190
        $this->paths = array_unique(array_merge($this->paths, $paths));
107 2190
    }
108
109
    /**
110
     * Retrieves the defined metadata lookup paths.
111
     *
112
     * @return string[]
113
     */
114
    public function getPaths()
115
    {
116
        return $this->paths;
117
    }
118
119
    /**
120
     * Append exclude lookup paths to metadata driver.
121
     *
122
     * @param string[] $paths
123
     */
124
    public function addExcludePaths(array $paths)
125
    {
126
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
127
    }
128
129
    /**
130
     * Retrieve the defined metadata lookup exclude paths.
131
     *
132
     * @return string[]
133
     */
134
    public function getExcludePaths()
135
    {
136
        return $this->excludePaths;
137
    }
138
139
    /**
140
     * Retrieve the current annotation reader
141
     *
142
     * @return AnnotationReader
143
     */
144 1
    public function getReader()
145
    {
146 1
        return $this->reader;
147
    }
148
149
    /**
150
     * Gets the file extension used to look for mapping files under.
151
     *
152
     * @return string
153
     */
154
    public function getFileExtension()
155
    {
156
        return $this->fileExtension;
157
    }
158
159
    /**
160
     * Sets the file extension used to look for mapping files under.
161
     *
162
     * @param string $fileExtension The file extension to set.
163
     *
164
     */
165
    public function setFileExtension($fileExtension)
166
    {
167
        $this->fileExtension = $fileExtension;
168
    }
169
170
    /**
171
     * Returns whether the class with the specified name is transient. Only non-transient
172
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
173
     *
174
     * A class is non-transient if it is annotated with an annotation
175
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
176
     *
177
     * @param string $className
178
     *
179
     * @return bool
180
     */
181 193
    public function isTransient($className)
182
    {
183 193
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
184
185 193
        foreach ($classAnnotations as $annot) {
186 188
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
187 188
                return false;
188
            }
189
        }
190 12
        return true;
191
    }
192
193
    /**
194
     * {@inheritDoc}
195
     */
196 61
    public function getAllClassNames()
197
    {
198 61
        if ($this->classNames !== null) {
199 46
            return $this->classNames;
200
        }
201
202 61
        if (! $this->paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
203
            throw Mapping\MappingException::pathRequired();
204
        }
205
206 61
        $classes       = [];
207 61
        $includedFiles = [];
208
209 61
        foreach ($this->paths as $path) {
210 61
            if (! is_dir($path)) {
211
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
212
            }
213
214 61
            $iterator = new \RegexIterator(
215 61
                new \RecursiveIteratorIterator(
216 61
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
217 61
                    \RecursiveIteratorIterator::LEAVES_ONLY
218
                ),
219 61
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
220 61
                \RecursiveRegexIterator::GET_MATCH
221
            );
222
223 61
            foreach ($iterator as $file) {
224 61
                $sourceFile = $file[0];
225
226 61
                if (! preg_match('(^phar:)i', $sourceFile)) {
227 61
                    $sourceFile = realpath($sourceFile);
228
                }
229
230 61
                foreach ($this->excludePaths as $excludePath) {
231
                    $exclude = str_replace('\\', '/', realpath($excludePath));
232
                    $current = str_replace('\\', '/', $sourceFile);
233
234
                    if (strpos($current, $exclude) !== false) {
235
                        continue 2;
236
                    }
237
                }
238
239 61
                require_once $sourceFile;
240
241 61
                $includedFiles[] = $sourceFile;
242
            }
243
        }
244
245 61
        $declared = get_declared_classes();
246
247 61
        foreach ($declared as $className) {
248 61
            $rc         = new \ReflectionClass($className);
249 61
            $sourceFile = $rc->getFileName();
250 61
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
251 61
                $classes[] = $className;
252
            }
253
        }
254
255 61
        $this->classNames = $classes;
256
257 61
        return $classes;
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     *
263
     * @throws CacheException
264
     * @throws Mapping\MappingException
265
     * @throws \ReflectionException
266
     * @throws \RuntimeException
267
     */
268 370
    public function loadMetadataForClass(
269
        string $className,
270
        Mapping\ClassMetadata $metadata,
271
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
272
    ) : Mapping\ClassMetadata {
273 370
        $reflectionClass = $metadata->getReflectionClass();
274
275 370
        if (! $reflectionClass) {
276
            // this happens when running annotation driver in combination with
277
            // static reflection services. This is not the nicest fix
278
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
279
        }
280
281 370
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
282 370
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
283 370
            $classAnnotations,
284 370
            $reflectionClass,
285 370
            $metadata,
286 370
            $metadataBuildingContext
287
        );
288
289
        // Evaluate @Cache annotation
290 367
        if (isset($classAnnotations[Annotation\Cache::class])) {
291 18
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
292 18
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
293
294 18
            $classMetadata->setCache($cache);
295
        }
296
297
        // Evaluate annotations on properties/fields
298
        /* @var $reflProperty \ReflectionProperty */
299 367
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
300 367
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
301 76
                continue;
302
            }
303
304 367
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
305 366
            $property            = $this->convertPropertyAnnotationsToProperty(
306 366
                $propertyAnnotations,
307 366
                $reflectionProperty,
308 366
                $classMetadata
309
            );
310
311 366
            if ($classMetadata->isMappedSuperclass &&
312 366
                $property instanceof Mapping\ToManyAssociationMetadata &&
313 366
                ! $property->isOwningSide()) {
314 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
315 1
                    $classMetadata->getClassName(),
316 1
                    $property->getName()
317
                );
318
            }
319
320 365
            if (! $property) {
321 1
                continue;
322
            }
323
324 365
            $metadata->addProperty($property);
325
        }
326
327 364
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
328
329 364
        return $classMetadata;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $classMetadata returns the type Doctrine\ORM\Mapping\ClassMetadata which is incompatible with the return type mandated by Doctrine\ORM\Mapping\Dri...:loadMetadataForClass() of void.

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

Let's take a look at an example:

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

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
330
    }
331
332
    /**
333
     * @param Annotation\Annotation[] $classAnnotations
334
     *
335
     * @throws Mapping\MappingException
336
     */
337 370
    private function convertClassAnnotationsToClassMetadata(
338
        array $classAnnotations,
339
        \ReflectionClass $reflectionClass,
340
        Mapping\ClassMetadata $metadata,
341
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
342
    ) : Mapping\ClassMetadata {
343
        switch (true) {
344 370
            case isset($classAnnotations[Annotation\Entity::class]):
345 366
                return $this->convertClassAnnotationsToEntityClassMetadata(
346 366
                    $classAnnotations,
347 366
                    $reflectionClass,
348 366
                    $metadata,
349 366
                    $metadataBuildingContext
350
                );
351
352
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

Loading history...
353
354 27
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
355 24
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
356 24
                    $classAnnotations,
357 24
                    $reflectionClass,
358 24
                    $metadata
359
                );
360
361 3
            case isset($classAnnotations[Annotation\Embeddable::class]):
362
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
363
                    $classAnnotations,
364
                    $reflectionClass,
365
                    $metadata
366
                );
367
368
            default:
369 3
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
370
        }
371
    }
372
373
    /**
374
     * @param Annotation\Annotation[] $classAnnotations
375
     *
376
     * @return Mapping\ClassMetadata
377
     *
378
     * @throws Mapping\MappingException
379
     * @throws \UnexpectedValueException
380
     */
381 366
    private function convertClassAnnotationsToEntityClassMetadata(
382
        array $classAnnotations,
383
        \ReflectionClass $reflectionClass,
384
        Mapping\ClassMetadata $metadata,
385
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
386
    ) {
387
        /** @var Annotation\Entity $entityAnnot */
388 366
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
389
390 366
        if ($entityAnnot->repositoryClass !== null) {
391 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
392
        }
393
394 366
        if ($entityAnnot->readOnly) {
395 1
            $metadata->asReadOnly();
396
        }
397
398 366
        $metadata->isMappedSuperclass = false;
399 366
        $metadata->isEmbeddedClass    = false;
400
401 366
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
402
403
        // Evaluate @ChangeTrackingPolicy annotation
404 366
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
405 5
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
406
407 5
            $metadata->setChangeTrackingPolicy(
408 5
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
409
            );
410
        }
411
412
        // Evaluate @InheritanceType annotation
413 366
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
414 79
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
415
416 79
            $metadata->setInheritanceType(
417 79
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
418
            );
419
420 79
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
421 79
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
422
            }
423
        }
424
425 366
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
426 366
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
427
428 366
        return $metadata;
429
    }
430
431
    /**
432
     * @param Annotation\Annotation[] $classAnnotations
433
     */
434 24
    private function convertClassAnnotationsToMappedSuperClassMetadata(
435
        array $classAnnotations,
436
        \ReflectionClass $reflectionClass,
437
        Mapping\ClassMetadata $metadata
438
    ) : Mapping\ClassMetadata {
439
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
440 24
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
441
442 24
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
443 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
444
        }
445
446 24
        $metadata->isMappedSuperclass = true;
447 24
        $metadata->isEmbeddedClass    = false;
448
449 24
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
450 24
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
451
452 24
        return $metadata;
453
    }
454
455
    /**
456
     * @param Annotation\Annotation[] $classAnnotations
457
     */
458
    private function convertClassAnnotationsToEmbeddableClassMetadata(
459
        array $classAnnotations,
0 ignored issues
show
Unused Code introduced by
The parameter $classAnnotations is not used and could be removed. ( Ignorable by Annotation )

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

459
        /** @scrutinizer ignore-unused */ array $classAnnotations,

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

Loading history...
460
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed. ( Ignorable by Annotation )

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

460
        /** @scrutinizer ignore-unused */ \ReflectionClass $reflectionClass,

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

Loading history...
461
        Mapping\ClassMetadata $metadata
462
    ) : Mapping\ClassMetadata {
463
        $metadata->isMappedSuperclass = false;
464
        $metadata->isEmbeddedClass    = true;
465
466
        return $metadata;
467
    }
468
469
    /**
470
     * @param Annotation\Annotation[] $propertyAnnotations
471
     *
472
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
473
     */
474 366
    private function convertPropertyAnnotationsToProperty(
475
        array $propertyAnnotations,
476
        \ReflectionProperty $reflectionProperty,
477
        Mapping\ClassMetadata $metadata
478
    ) : ?Mapping\Property {
479
        switch (true) {
480 366
            case isset($propertyAnnotations[Annotation\Column::class]):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
481 361
                return $this->convertReflectionPropertyToFieldMetadata(
482 361
                    $reflectionProperty,
483 361
                    $propertyAnnotations,
484 361
                    $metadata
485
                );
486
487 254
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
488 112
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
489 112
                    $reflectionProperty,
490 112
                    $propertyAnnotations,
491 112
                    $metadata
492
                );
493
494 202
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
495 139
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
496 139
                    $reflectionProperty,
497 139
                    $propertyAnnotations,
498 139
                    $metadata
499
                );
500
501 164
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
502 108
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
503 108
                    $reflectionProperty,
504 108
                    $propertyAnnotations,
505 108
                    $metadata
506
                );
507
508 106
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
509 89
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
510 89
                    $reflectionProperty,
511 89
                    $propertyAnnotations,
512 89
                    $metadata
513
                );
514
515 31
            case isset($propertyAnnotations[Annotation\Embedded::class]):
516 1
                return null;
517
518
            default:
519 30
                return new Mapping\TransientMetadata($reflectionProperty->getName());
520
        }
521
    }
522
523
    /**
524
     * @param Annotation\Annotation[] $propertyAnnotations
525
     *
526
     * @throws Mapping\MappingException
527
     */
528 361
    private function convertReflectionPropertyToFieldMetadata(
529
        \ReflectionProperty $reflProperty,
530
        array $propertyAnnotations,
531
        Mapping\ClassMetadata $metadata
532
    ) : Mapping\FieldMetadata {
533 361
        $className   = $metadata->getClassName();
534 361
        $fieldName   = $reflProperty->getName();
535 361
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
536 361
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
537
538 361
        if ($columnAnnot->type === null) {
0 ignored issues
show
Bug introduced by
Accessing type on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
539
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
540
        }
541
542 361
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
543
544
        // Check for Id
545 361
        if (isset($propertyAnnotations[Annotation\Id::class])) {
546 357
            $fieldMetadata->setPrimaryKey(true);
547
        }
548
549
        // Check for GeneratedValue strategy
550 361
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
551 306
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
552 306
            $strategy            = strtoupper($generatedValueAnnot->strategy);
0 ignored issues
show
Bug introduced by
Accessing strategy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
553 306
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
554
555 306
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
556 286
                $idGeneratorDefinition = [];
557
558
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
559
                switch (true) {
560 286
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
561 9
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
562
563
                        $idGeneratorDefinition = [
564 9
                            'sequenceName' => $seqGeneratorAnnot->sequenceName,
0 ignored issues
show
Bug introduced by
Accessing sequenceName on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
565 9
                            'allocationSize' => $seqGeneratorAnnot->allocationSize,
0 ignored issues
show
Bug introduced by
Accessing allocationSize on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
566
                        ];
567
568 9
                        break;
569
570 277
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
571 3
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
572
573
                        $idGeneratorDefinition = [
574 3
                            'class' => $customGeneratorAnnot->class,
575 3
                            'arguments' => $customGeneratorAnnot->arguments,
576
                        ];
577
578 3
                        break;
579
580
                    /* @todo If it is not supported, why does this exist? */
581 274
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
582
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
583
                }
584
585 286
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
586
            }
587
        }
588
589 361
        return $fieldMetadata;
590
    }
591
592
    /**
593
     * @param Annotation\Annotation[] $propertyAnnotations
594
     *
595
     * @return Mapping\OneToOneAssociationMetadata
596
     */
597 112
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
598
        \ReflectionProperty $reflectionProperty,
599
        array $propertyAnnotations,
600
        Mapping\ClassMetadata $metadata
601
    ) {
602 112
        $className     = $metadata->getClassName();
603 112
        $fieldName     = $reflectionProperty->getName();
604 112
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
605 112
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
606 112
        $targetEntity  = $oneToOneAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
607
608 112
        $assocMetadata->setTargetEntity($targetEntity);
609 112
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
610 112
        $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval);
0 ignored issues
show
Bug introduced by
Accessing orphanRemoval on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
611 112
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
612
613 112
        if (! empty($oneToOneAnnot->mappedBy)) {
0 ignored issues
show
Bug introduced by
Accessing mappedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
614 40
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
615 40
            $assocMetadata->setOwningSide(false);
616
        }
617
618 112
        if (! empty($oneToOneAnnot->inversedBy)) {
0 ignored issues
show
Bug introduced by
Accessing inversedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
619 52
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
620
        }
621
622
        // Check for Id
623 112
        if (isset($propertyAnnotations[Annotation\Id::class])) {
624 12
            $assocMetadata->setPrimaryKey(true);
625
        }
626
627 112
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
628
629
        // Check for JoinColumn/JoinColumns annotations
630
        switch (true) {
631 112
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
632 80
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
633
634 80
                $assocMetadata->addJoinColumn(
635 80
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
636
                );
637
638 80
                break;
639
640 53
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
641 3
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
642
643 3
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
644 3
                    $assocMetadata->addJoinColumn(
645 3
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
646
                    );
647
                }
648
649 3
                break;
650
        }
651
652 112
        return $assocMetadata;
653
    }
654
655
    /**
656
     * @param Annotation\Annotation[] $propertyAnnotations
657
     *
658
     * @return Mapping\ManyToOneAssociationMetadata
659
     */
660 139
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
661
        \ReflectionProperty $reflectionProperty,
662
        array $propertyAnnotations,
663
        Mapping\ClassMetadata $metadata
664
    ) {
665 139
        $className      = $metadata->getClassName();
666 139
        $fieldName      = $reflectionProperty->getName();
667 139
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
668 139
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
669 139
        $targetEntity   = $manyToOneAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
670
671 139
        $assocMetadata->setTargetEntity($targetEntity);
672 139
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
673 139
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
674
675 139
        if (! empty($manyToOneAnnot->inversedBy)) {
0 ignored issues
show
Bug introduced by
Accessing inversedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
676 93
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
677
        }
678
679
        // Check for Id
680 139
        if (isset($propertyAnnotations[Annotation\Id::class])) {
681 34
            $assocMetadata->setPrimaryKey(true);
682
        }
683
684 139
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
685
686
        // Check for JoinColumn/JoinColumns annotations
687
        switch (true) {
688 139
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
689 79
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
690
691 79
                $assocMetadata->addJoinColumn(
692 79
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
693
                );
694
695 79
                break;
696
697 69
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
698 16
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
699
700 16
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
701 16
                    $assocMetadata->addJoinColumn(
702 16
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
703
                    );
704
                }
705
706 16
                break;
707
        }
708
709 139
        return $assocMetadata;
710
    }
711
712
    /**
713
     * @param Annotation\Annotation[] $propertyAnnotations
714
     *
715
     * @throws Mapping\MappingException
716
     */
717 108
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
718
        \ReflectionProperty $reflectionProperty,
719
        array $propertyAnnotations,
720
        Mapping\ClassMetadata $metadata
721
    ) : Mapping\OneToManyAssociationMetadata {
722 108
        $className      = $metadata->getClassName();
723 108
        $fieldName      = $reflectionProperty->getName();
724 108
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
725 108
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
726 108
        $targetEntity   = $oneToManyAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
727
728 108
        $assocMetadata->setTargetEntity($targetEntity);
729 108
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
730 108
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
0 ignored issues
show
Bug introduced by
Accessing orphanRemoval on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
731 108
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
732 108
        $assocMetadata->setOwningSide(false);
733 108
        $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
0 ignored issues
show
Bug introduced by
Accessing mappedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
734
735 108
        if (! empty($oneToManyAnnot->indexBy)) {
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
736 8
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
737
        }
738
739
        // Check for OrderBy
740 108
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
741 14
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
742
743 14
            $assocMetadata->setOrderBy($orderByAnnot->value);
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
744
        }
745
746
        // Check for Id
747 108
        if (isset($propertyAnnotations[Annotation\Id::class])) {
748
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
749
        }
750
751 108
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
752
753 108
        return $assocMetadata;
754
    }
755
756
    /**
757
     * @param Annotation\Annotation[] $propertyAnnotations
758
     *
759
     * @throws Mapping\MappingException
760
     */
761 89
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
762
        \ReflectionProperty $reflectionProperty,
763
        array $propertyAnnotations,
764
        Mapping\ClassMetadata $metadata
765
    ) : Mapping\ManyToManyAssociationMetadata {
766 89
        $className       = $metadata->getClassName();
767 89
        $fieldName       = $reflectionProperty->getName();
768 89
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
769 89
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
770 89
        $targetEntity    = $manyToManyAnnot->targetEntity;
0 ignored issues
show
Bug introduced by
Accessing targetEntity on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
771
772 89
        $assocMetadata->setTargetEntity($targetEntity);
773 89
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade));
0 ignored issues
show
Bug introduced by
Accessing cascade on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
774 89
        $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval);
0 ignored issues
show
Bug introduced by
Accessing orphanRemoval on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
775 89
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch));
0 ignored issues
show
Bug introduced by
Accessing fetch on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
776
777 89
        if (! empty($manyToManyAnnot->mappedBy)) {
0 ignored issues
show
Bug introduced by
Accessing mappedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
778 36
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
779 36
            $assocMetadata->setOwningSide(false);
780
        }
781
782 89
        if (! empty($manyToManyAnnot->inversedBy)) {
0 ignored issues
show
Bug introduced by
Accessing inversedBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
783 45
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
784
        }
785
786 89
        if (! empty($manyToManyAnnot->indexBy)) {
0 ignored issues
show
Bug introduced by
Accessing indexBy on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
787 3
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
788
        }
789
790
        // Check for JoinTable
791 89
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
792 71
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
793 71
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
794
795 71
            $assocMetadata->setJoinTable($joinTableMetadata);
796
        }
797
798
        // Check for OrderBy
799 89
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
800 3
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
801
802 3
            $assocMetadata->setOrderBy($orderByAnnot->value);
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
803
        }
804
805
        // Check for Id
806 89
        if (isset($propertyAnnotations[Annotation\Id::class])) {
807
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
808
        }
809
810 89
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
811
812 89
        return $assocMetadata;
813
    }
814
815
    /**
816
     * Parse the given Column as FieldMetadata
817
     */
818 361
    private function convertColumnAnnotationToFieldMetadata(
819
        Annotation\Column $columnAnnot,
820
        string $fieldName,
821
        bool $isVersioned
822
    ) : Mapping\FieldMetadata {
823 361
        $fieldMetadata = $isVersioned
824 16
            ? new Mapping\VersionFieldMetadata($fieldName)
825 361
            : new Mapping\FieldMetadata($fieldName)
826
        ;
827
828 361
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
829
830 361
        if (! empty($columnAnnot->name)) {
831 78
            $fieldMetadata->setColumnName($columnAnnot->name);
832
        }
833
834 361
        if (! empty($columnAnnot->columnDefinition)) {
835 4
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
836
        }
837
838 361
        if (! empty($columnAnnot->length)) {
839 361
            $fieldMetadata->setLength($columnAnnot->length);
840
        }
841
842 361
        if ($columnAnnot->options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $columnAnnot->options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
843 6
            $fieldMetadata->setOptions($columnAnnot->options);
844
        }
845
846 361
        $fieldMetadata->setScale($columnAnnot->scale);
847 361
        $fieldMetadata->setPrecision($columnAnnot->precision);
848 361
        $fieldMetadata->setNullable($columnAnnot->nullable);
849 361
        $fieldMetadata->setUnique($columnAnnot->unique);
850
851 361
        return $fieldMetadata;
852
    }
853
854
    /**
855
     * Parse the given Table as TableMetadata
856
     */
857 192
    private function convertTableAnnotationToTableMetadata(
858
        Annotation\Table $tableAnnot,
859
        Mapping\TableMetadata $tableMetadata
860
    ) : void {
861 192
        if (! empty($tableAnnot->name)) {
862 187
            $tableMetadata->setName($tableAnnot->name);
863
        }
864
865 192
        if (! empty($tableAnnot->schema)) {
866 5
            $tableMetadata->setSchema($tableAnnot->schema);
867
        }
868
869 192
        foreach ($tableAnnot->options as $optionName => $optionValue) {
870 4
            $tableMetadata->addOption($optionName, $optionValue);
871
        }
872
873 192
        foreach ($tableAnnot->indexes as $indexAnnot) {
874 13
            $tableMetadata->addIndex([
875 13
                'name'    => $indexAnnot->name,
876 13
                'columns' => $indexAnnot->columns,
877 13
                'unique'  => $indexAnnot->unique,
878 13
                'options' => $indexAnnot->options,
879 13
                'flags'   => $indexAnnot->flags,
880
            ]);
881
        }
882
883 192
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
884 6
            $tableMetadata->addUniqueConstraint([
885 6
                'name'    => $uniqueConstraintAnnot->name,
886 6
                'columns' => $uniqueConstraintAnnot->columns,
887 6
                'options' => $uniqueConstraintAnnot->options,
888 6
                'flags'   => $uniqueConstraintAnnot->flags,
889
            ]);
890
        }
891 192
    }
892
893
    /**
894
     * Parse the given JoinTable as JoinTableMetadata
895
     */
896 71
    private function convertJoinTableAnnotationToJoinTableMetadata(
897
        Annotation\JoinTable $joinTableAnnot
898
    ) : Mapping\JoinTableMetadata {
899 71
        $joinTable = new Mapping\JoinTableMetadata();
900
901 71
        if (! empty($joinTableAnnot->name)) {
902 69
            $joinTable->setName($joinTableAnnot->name);
903
        }
904
905 71
        if (! empty($joinTableAnnot->schema)) {
906
            $joinTable->setSchema($joinTableAnnot->schema);
907
        }
908
909 71
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
910 70
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
911
912 70
            $joinTable->addJoinColumn($joinColumn);
913
        }
914
915 71
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
916 70
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
917
918 70
            $joinTable->addInverseJoinColumn($joinColumn);
919
        }
920
921 71
        return $joinTable;
922
    }
923
924
    /**
925
     * Parse the given JoinColumn as JoinColumnMetadata
926
     */
927 180
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
928
        Annotation\JoinColumn $joinColumnAnnot
929
    ) : Mapping\JoinColumnMetadata {
930 180
        $joinColumn = new Mapping\JoinColumnMetadata();
931
932
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
933 180
        if (! empty($joinColumnAnnot->name)) {
934 174
            $joinColumn->setColumnName($joinColumnAnnot->name);
935
        }
936
937 180
        if (! empty($joinColumnAnnot->referencedColumnName)) {
938 180
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
939
        }
940
941 180
        $joinColumn->setNullable($joinColumnAnnot->nullable);
942 180
        $joinColumn->setUnique($joinColumnAnnot->unique);
943
944 180
        if (! empty($joinColumnAnnot->fieldName)) {
945
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
946
        }
947
948 180
        if (! empty($joinColumnAnnot->columnDefinition)) {
949 3
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
950
        }
951
952 180
        if ($joinColumnAnnot->onDelete) {
953 16
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
954
        }
955
956 180
        return $joinColumn;
957
    }
958
959
    /**
960
     * Parse the given Cache as CacheMetadata
961
     *
962
     * @param string|null $fieldName
963
     */
964 18
    private function convertCacheAnnotationToCacheMetadata(
965
        Annotation\Cache $cacheAnnot,
966
        Mapping\ClassMetadata $metadata,
967
        $fieldName = null
968
    ) : Mapping\CacheMetadata {
969 18
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
970 18
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
971
972 18
        $usage  = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
973 18
        $region = $cacheAnnot->region ?: $defaultRegion;
974
975 18
        return new Mapping\CacheMetadata($usage, $region);
976
    }
977
978
    /**
979
     * @param Annotation\Annotation[] $classAnnotations
980
     */
981 366
    private function attachTable(
982
        array $classAnnotations,
983
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed. ( Ignorable by Annotation )

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

983
        /** @scrutinizer ignore-unused */ \ReflectionClass $reflectionClass,

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

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

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

1026
        /** @scrutinizer ignore-unused */ \ReflectionClass $reflectionClass,

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

Loading history...
1027
        Mapping\ClassMetadata $metadata
1028
    ) : void {
1029 79
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1030
1031 79
        $discriminatorColumn->setTableName($metadata->getTableName());
1032 79
        $discriminatorColumn->setColumnName('dtype');
1033 79
        $discriminatorColumn->setType(Type::getType('string'));
1034 79
        $discriminatorColumn->setLength(255);
1035
1036
        // Evaluate DiscriminatorColumn annotation
1037 79
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1038
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1039 63
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1040 63
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1041 59
                ? $discriminatorColumnAnnotation->type
1042 63
                : 'string';
1043
1044 63
            $discriminatorColumn->setType(Type::getType($typeName));
1045 63
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1046
1047 63
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1048 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1049
            }
1050
1051 63
            if (! empty($discriminatorColumnAnnotation->length)) {
1052 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1053
            }
1054
        }
1055
1056 79
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1057
1058
        // Evaluate DiscriminatorMap annotation
1059 79
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1060 78
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1061 78
            $discriminatorMap           = $discriminatorMapAnnotation->value;
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1062
1063 78
            $metadata->setDiscriminatorMap($discriminatorMap);
1064
        }
1065 79
    }
1066
1067
    /**
1068
     * @param Annotation\Annotation[] $classAnnotations
1069
     */
1070 367
    private function attachLifecycleCallbacks(
1071
        array $classAnnotations,
1072
        \ReflectionClass $reflectionClass,
1073
        Mapping\ClassMetadata $metadata
1074
    ) : void {
1075
        // Evaluate @HasLifecycleCallbacks annotation
1076 367
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1077
            /* @var $method \ReflectionMethod */
1078 15
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1079 14
                foreach ($this->getMethodCallbacks($method) as $callback) {
1080 14
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1081
                }
1082
            }
1083
        }
1084 367
    }
1085
1086
    /**
1087
     * @param Annotation\Annotation[] $classAnnotations
1088
     *
1089
     * @throws \ReflectionException
1090
     * @throws Mapping\MappingException
1091
     */
1092 367
    private function attachEntityListeners(
1093
        array $classAnnotations,
1094
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed. ( Ignorable by Annotation )

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

1094
        /** @scrutinizer ignore-unused */ \ReflectionClass $reflectionClass,

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

Loading history...
1095
        Mapping\ClassMetadata $metadata
1096
    ) : void {
1097
        // Evaluate @EntityListeners annotation
1098 367
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1099
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1100 9
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1101
1102 9
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1103 9
                if (! class_exists($listenerClassName)) {
1104
                    throw Mapping\MappingException::entityListenerClassNotFound(
1105
                        $listenerClassName,
1106
                        $metadata->getClassName()
1107
                    );
1108
                }
1109
1110 9
                $listenerClass = new \ReflectionClass($listenerClassName);
1111
1112
                /* @var $method \ReflectionMethod */
1113 9
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1114 9
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1115 9
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1116
                    }
1117
                }
1118
            }
1119
        }
1120 367
    }
1121
1122
    /**
1123
     * @param Annotation\Annotation[] $classAnnotations
1124
     *
1125
     * @throws Mapping\MappingException
1126
     */
1127 364
    private function attachPropertyOverrides(
1128
        array $classAnnotations,
1129
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed. ( Ignorable by Annotation )

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

1129
        /** @scrutinizer ignore-unused */ \ReflectionClass $reflectionClass,

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

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