Passed
Pull Request — master (#7113)
by Michael
14:58 queued 03:10
created

convertClassAnnotationsToEmbeddableClassMetadata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 9
ccs 0
cts 4
cp 0
crap 2
rs 9.6666
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
    /**
44
     * @var int[]
45
     */
46
    protected $entityAnnotationClasses = [
47
        Annotation\Entity::class           => 1,
48
        Annotation\MappedSuperclass::class => 2,
49
    ];
50
51
    /**
52
     * The AnnotationReader.
53
     *
54
     * @var AnnotationReader
55
     */
56
    protected $reader;
57
58
    /**
59
     * The paths where to look for mapping files.
60
     *
61
     * @var string[]
62
     */
63
    protected $paths = [];
64
65
    /**
66
     * The paths excluded from path where to look for mapping files.
67
     *
68
     * @var string[]
69
     */
70
    protected $excludePaths = [];
71
72
    /**
73
     * The file extension of mapping documents.
74
     *
75
     * @var string
76
     */
77
    protected $fileExtension = '.php';
78
79
    /**
80
     * Cache for AnnotationDriver#getAllClassNames().
81
     *
82
     * @var string[]|null
83
     */
84
    protected $classNames;
85
86
    /**
87
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
88
     * docblock annotations.
89
     *
90
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
91
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
92
     */
93 2272
    public function __construct(Reader $reader, $paths = null)
94
    {
95 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...
96 2272
        if ($paths) {
97 2186
            $this->addPaths((array) $paths);
98
        }
99 2272
    }
100
101
    /**
102
     * Appends lookup paths to metadata driver.
103
     *
104
     * @param string[] $paths
105
     */
106 2190
    public function addPaths(array $paths)
107
    {
108 2190
        $this->paths = array_unique(array_merge($this->paths, $paths));
109 2190
    }
110
111
    /**
112
     * Retrieves the defined metadata lookup paths.
113
     *
114
     * @return string[]
115
     */
116
    public function getPaths()
117
    {
118
        return $this->paths;
119
    }
120
121
    /**
122
     * Append exclude lookup paths to metadata driver.
123
     *
124
     * @param string[] $paths
125
     */
126
    public function addExcludePaths(array $paths)
127
    {
128
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
129
    }
130
131
    /**
132
     * Retrieve the defined metadata lookup exclude paths.
133
     *
134
     * @return string[]
135
     */
136
    public function getExcludePaths()
137
    {
138
        return $this->excludePaths;
139
    }
140
141
    /**
142
     * Retrieve the current annotation reader
143
     *
144
     * @return AnnotationReader
145
     */
146 1
    public function getReader()
147
    {
148 1
        return $this->reader;
149
    }
150
151
    /**
152
     * Gets the file extension used to look for mapping files under.
153
     *
154
     * @return string
155
     */
156
    public function getFileExtension()
157
    {
158
        return $this->fileExtension;
159
    }
160
161
    /**
162
     * Sets the file extension used to look for mapping files under.
163
     *
164
     * @param string $fileExtension The file extension to set.
165
     *
166
     */
167
    public function setFileExtension($fileExtension)
168
    {
169
        $this->fileExtension = $fileExtension;
170
    }
171
172
    /**
173
     * Returns whether the class with the specified name is transient. Only non-transient
174
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
175
     *
176
     * A class is non-transient if it is annotated with an annotation
177
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
178
     *
179
     * @param string $className
180
     *
181
     * @return bool
182
     */
183 193
    public function isTransient($className)
184
    {
185 193
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
186
187 193
        foreach ($classAnnotations as $annot) {
188 188
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
189 188
                return false;
190
            }
191
        }
192 12
        return true;
193
    }
194
195
    /**
196
     * {@inheritDoc}
197
     */
198 61
    public function getAllClassNames()
199
    {
200 61
        if ($this->classNames !== null) {
201 46
            return $this->classNames;
202
        }
203
204 61
        if (! $this->paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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

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

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

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

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

461
        /** @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...
462
        \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

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

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

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

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

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