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

AnnotationDriver::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 6
ccs 0
cts 5
cp 0
crap 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver\Annotation;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\DBAL\Types\Type;
9
use Doctrine\ORM\Annotation;
10
use Doctrine\ORM\Events;
11
use Doctrine\ORM\Mapping;
12
use function array_diff;
13
use function array_intersect;
14
use function array_map;
15
use function array_merge;
16
use function array_unique;
17
use function class_exists;
18
use function constant;
19
use function count;
20
use function defined;
21
use function get_class;
22
use function get_declared_classes;
23
use function in_array;
24
use function is_dir;
25
use function is_numeric;
26
use function preg_match;
27
use function preg_quote;
28
use function realpath;
29
use function sprintf;
30
use function str_replace;
31
use function strpos;
32
use function strtolower;
33
use function strtoupper;
34
35
/**
36
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
37
 */
38
class AnnotationDriver implements Mapping\Driver\MappingDriver
39
{
40
    /**
41
     * @var int[]
42
     */
43
    protected $entityAnnotationClasses = [
44
        Annotation\Entity::class           => 1,
45
        Annotation\MappedSuperclass::class => 2,
46
        Annotation\Embeddable::class       => 3,
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 AnnotationReader     $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
    public function __construct($reader, $paths = null)
92
    {
93
        $this->reader = $reader;
94
95
        if ($paths) {
96
            $this->addPaths((array) $paths);
97
        }
98
    }
99
100
    /**
101
     * Appends lookup paths to metadata driver.
102
     *
103
     * @param string[] $paths
104
     */
105
    public function addPaths(array $paths)
106
    {
107
        $this->paths = array_unique(array_merge($this->paths, $paths));
108
    }
109
110
    /**
111
     * Retrieves the defined metadata lookup paths.
112
     *
113
     * @return string[]
114
     */
115
    public function getPaths()
116
    {
117
        return $this->paths;
118
    }
119
120
    /**
121
     * Append exclude lookup paths to metadata driver.
122
     *
123
     * @param string[] $paths
124
     */
125
    public function addExcludePaths(array $paths)
126
    {
127
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
128
    }
129
130
    /**
131
     * Retrieve the defined metadata lookup exclude paths.
132
     *
133
     * @return string[]
134
     */
135
    public function getExcludePaths()
136
    {
137
        return $this->excludePaths;
138
    }
139
140
    /**
141
     * Retrieve the current annotation reader
142
     *
143
     * @return AnnotationReader
144
     */
145
    public function getReader()
146
    {
147
        return $this->reader;
148
    }
149
150
    /**
151
     * Gets the file extension used to look for mapping files under.
152
     *
153
     * @return string
154
     */
155
    public function getFileExtension()
156
    {
157
        return $this->fileExtension;
158
    }
159
160
    /**
161
     * Sets the file extension used to look for mapping files under.
162
     *
163
     * @param string $fileExtension The file extension to set.
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
    public function isTransient($className)
182
    {
183
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
184
185
        foreach ($classAnnotations as $annot) {
186
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
187
                return false;
188
            }
189
        }
190
        return true;
191
    }
192
193
    /**
194
     * {@inheritDoc}
195
     */
196
    public function getAllClassNames()
197
    {
198
        if ($this->classNames !== null) {
199
            return $this->classNames;
200
        }
201
202
        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
        $classes       = [];
207
        $includedFiles = [];
208
209
        foreach ($this->paths as $path) {
210
            if (! is_dir($path)) {
211
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
212
            }
213
214
            $iterator = new \RegexIterator(
215
                new \RecursiveIteratorIterator(
216
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
217
                    \RecursiveIteratorIterator::LEAVES_ONLY
218
                ),
219
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
220
                \RecursiveRegexIterator::GET_MATCH
221
            );
222
223
            foreach ($iterator as $file) {
224
                $sourceFile = $file[0];
225
226
                if (! preg_match('(^phar:)i', $sourceFile)) {
227
                    $sourceFile = realpath($sourceFile);
228
                }
229
230
                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
                require_once $sourceFile;
240
241
                $includedFiles[] = $sourceFile;
242
            }
243
        }
244
245
        $declared = get_declared_classes();
246
247
        foreach ($declared as $className) {
248
            $rc         = new \ReflectionClass($className);
249
            $sourceFile = $rc->getFileName();
250
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
251
                $classes[] = $className;
252
            }
253
        }
254
255
        $this->classNames = $classes;
256
257
        return $classes;
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     *
263
     * @throws \UnexpectedValueException
264
     * @throws \ReflectionException
265
     * @throws Mapping\MappingException
266
     */
267
    public function loadMetadataForClass(
268
        string $className,
269
        Mapping\ClassMetadata $metadata,
270
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
271
    ) : Mapping\ClassMetadata {
272
        $reflectionClass = $metadata->getReflectionClass();
273
274
        if (! $reflectionClass) {
275
            // this happens when running annotation driver in combination with
276
            // static reflection services. This is not the nicest fix
277
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
278
        }
279
280
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
281
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
282
            $reflectionClass,
283
            $classAnnotations,
284
            $metadata,
285
            $metadataBuildingContext
286
        );
287
288
        // Evaluate @Cache annotation
289
        if (isset($classAnnotations[Annotation\Cache::class])) {
290
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
291
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
292
293
            $classMetadata->setCache($cache);
294
        }
295
296
        // Evaluate annotations on properties/fields
297
        /* @var $reflProperty \ReflectionProperty */
298
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
299
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
300
                continue;
301
            }
302
303
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
304
            $property            = $this->convertPropertyAnnotationsToProperty(
305
                $propertyAnnotations,
306
                $reflectionProperty,
307
                $classMetadata,
308
                $metadataBuildingContext
309
            );
310
311
            if (! $property) {
312
                continue;
313
            }
314
315
            $metadata->addProperty($property);
316
        }
317
318
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
319
320
        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...
321
    }
322
323
    /**
324
     * @param Annotation\Annotation[] $classAnnotations
325
     *
326
     * @throws \UnexpectedValueException
327
     * @throws Mapping\MappingException
328
     */
329
    private function convertClassAnnotationsToClassMetadata(
330
        \ReflectionClass $reflectionClass,
331
        array $classAnnotations,
332
        Mapping\ClassMetadata $metadata,
333
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
334
    ) : Mapping\ClassMetadata {
335
        switch (true) {
336
            case isset($classAnnotations[Annotation\Entity::class]):
337
                $binder = new Binder\EntityClassMetadataBinder(
338
                    $reflectionClass,
339
                    $classAnnotations,
340
                    $metadata,
341
                    $metadataBuildingContext
342
                );
343
344
                break;
345
346
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
347
                $binder = new Binder\MappedSuperClassMetadataBinder(
348
                    $reflectionClass,
349
                    $classAnnotations,
350
                    $metadata,
351
                    $metadataBuildingContext
352
                );
353
                break;
354
355
            case isset($classAnnotations[Annotation\Embeddable::class]):
356
                $binder = new Binder\EmbeddableClassMetadataBinder(
357
                    $reflectionClass,
358
                    $classAnnotations,
359
                    $metadata,
360
                    $metadataBuildingContext
361
                );
362
                break;
363
364
            default:
365
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
366
        }
367
368
        return $binder->bind();
369
    }
370
371
    /**
372
     * @param Annotation\Annotation[] $classAnnotations
373
     *
374
     * @return Mapping\ClassMetadata
375
     *
376
     * @throws Mapping\MappingException
377
     * @throws \UnexpectedValueException
378
     */
379
    private function convertClassAnnotationsToEntityClassMetadata(
0 ignored issues
show
Unused Code introduced by
The method convertClassAnnotationsToEntityClassMetadata() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
380
        array $classAnnotations,
381
        \ReflectionClass $reflectionClass,
382
        Mapping\ClassMetadata $metadata,
383
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
384
    ) {
385
        /** @var Annotation\Entity $entityAnnot */
386
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
387
388
        if ($entityAnnot->repositoryClass !== null) {
389
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
390
        }
391
392
        if ($entityAnnot->readOnly) {
393
            $metadata->asReadOnly();
394
        }
395
396
        $metadata->isMappedSuperclass = false;
397
        $metadata->isEmbeddedClass    = false;
398
399
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
400
401
        // Evaluate @ChangeTrackingPolicy annotation
402
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
403
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
404
405
            $metadata->setChangeTrackingPolicy(
406
                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...
407
            );
408
        }
409
410
        // Evaluate @InheritanceType annotation
411
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
412
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
413
414
            $metadata->setInheritanceType(
415
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
416
            );
417
418
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
419
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
420
            }
421
        }
422
423
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
424
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
425
426
        return $metadata;
427
    }
428
429
    /**
430
     * @param Annotation\Annotation[] $classAnnotations
431
     */
432
    private function convertClassAnnotationsToMappedSuperClassMetadata(
0 ignored issues
show
Unused Code introduced by
The method convertClassAnnotationsT...pedSuperClassMetadata() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
433
        array $classAnnotations,
434
        \ReflectionClass $reflectionClass,
435
        Mapping\ClassMetadata $metadata,
436
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
437
    ) : Mapping\ClassMetadata {
438
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
439
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
440
441
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
442
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
443
        }
444
445
        $metadata->isMappedSuperclass = true;
446
        $metadata->isEmbeddedClass    = false;
447
448
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
449
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
450
451
        return $metadata;
452
    }
453
454
    /**
455
     * @param Annotation\Annotation[] $propertyAnnotations
456
     *
457
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
458
     *
459
     * @throws Mapping\MappingException
460
     */
461
    private function convertPropertyAnnotationsToProperty(
462
        array $propertyAnnotations,
463
        \ReflectionProperty $reflectionProperty,
464
        Mapping\ClassMetadata $metadata,
465
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
466
    ) : ?Mapping\Property {
467
        switch (true) {
468
            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...
469
                return $this->convertReflectionPropertyToFieldMetadata(
470
                    $reflectionProperty,
471
                    $propertyAnnotations,
472
                    $metadata,
473
                    $metadataBuildingContext
474
                );
475
476
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
477
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
478
                    $reflectionProperty,
479
                    $propertyAnnotations,
480
                    $metadata,
481
                    $metadataBuildingContext
482
                );
483
484
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
485
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
486
                    $reflectionProperty,
487
                    $propertyAnnotations,
488
                    $metadata,
489
                    $metadataBuildingContext
490
                );
491
492
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
493
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
494
                    $reflectionProperty,
495
                    $propertyAnnotations,
496
                    $metadata,
497
                    $metadataBuildingContext
498
                );
499
500
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
501
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
502
                    $reflectionProperty,
503
                    $propertyAnnotations,
504
                    $metadata,
505
                    $metadataBuildingContext
506
                );
507
508
            case isset($propertyAnnotations[Annotation\Embedded::class]):
509
                return null;
510
511
            default:
512
                return new Mapping\TransientMetadata($reflectionProperty->getName());
513
        }
514
    }
515
516
    /**
517
     * @param Annotation\Annotation[] $propertyAnnotations
518
     *
519
     * @throws Mapping\MappingException
520
     */
521
    private function convertReflectionPropertyToFieldMetadata(
522
        \ReflectionProperty $reflProperty,
523
        array $propertyAnnotations,
524
        Mapping\ClassMetadata $metadata,
525
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
526
    ) : Mapping\FieldMetadata {
527
        $className   = $metadata->getClassName();
528
        $fieldName   = $reflProperty->getName();
529
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
530
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
531
532
        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...
533
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
534
        }
535
536
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
537
538
        // Check for Id
539
        if (isset($propertyAnnotations[Annotation\Id::class])) {
540
            $fieldMetadata->setPrimaryKey(true);
541
        }
542
543
        // Check for GeneratedValue strategy
544
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
545
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
546
            $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...
547
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
548
549
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
550
                $idGeneratorDefinition = [];
551
552
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
553
                switch (true) {
554
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
555
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
556
557
                        $idGeneratorDefinition = [
558
                            '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...
559
                            '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...
560
                        ];
561
562
                        break;
563
564
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
565
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
566
567
                        $idGeneratorDefinition = [
568
                            'class' => $customGeneratorAnnot->class,
569
                            'arguments' => $customGeneratorAnnot->arguments,
570
                        ];
571
572
                        break;
573
574
                    /* @todo If it is not supported, why does this exist? */
575
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
576
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
577
                }
578
579
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
580
            }
581
        }
582
583
        return $fieldMetadata;
584
    }
585
586
    /**
587
     * @param Annotation\Annotation[] $propertyAnnotations
588
     *
589
     * @return Mapping\OneToOneAssociationMetadata
590
     */
591
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
592
        \ReflectionProperty $reflectionProperty,
593
        array $propertyAnnotations,
594
        Mapping\ClassMetadata $metadata,
595
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
596
    ) {
597
        $className     = $metadata->getClassName();
598
        $fieldName     = $reflectionProperty->getName();
599
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
600
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
601
        $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...
602
603
        $assocMetadata->setTargetEntity($targetEntity);
604
        $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...
605
        $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...
606
        $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...
607
608
        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...
609
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
610
        }
611
612
        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...
613
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
614
        }
615
616
        // Check for Id
617
        if (isset($propertyAnnotations[Annotation\Id::class])) {
618
            $assocMetadata->setPrimaryKey(true);
619
        }
620
621
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
622
623
        // Check for JoinColumn/JoinColumns annotations
624
        switch (true) {
625
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
626
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
627
628
                $assocMetadata->addJoinColumn(
629
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
630
                );
631
632
                break;
633
634
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
635
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
636
637
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
638
                    $assocMetadata->addJoinColumn(
639
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
640
                    );
641
                }
642
643
                break;
644
        }
645
646
        return $assocMetadata;
647
    }
648
649
    /**
650
     * @param Annotation\Annotation[] $propertyAnnotations
651
     *
652
     * @return Mapping\ManyToOneAssociationMetadata
653
     */
654
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
655
        \ReflectionProperty $reflectionProperty,
656
        array $propertyAnnotations,
657
        Mapping\ClassMetadata $metadata,
658
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
659
    ) {
660
        $className      = $metadata->getClassName();
661
        $fieldName      = $reflectionProperty->getName();
662
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
663
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
664
        $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...
665
666
        $assocMetadata->setTargetEntity($targetEntity);
667
        $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...
668
        $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...
669
670
        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...
671
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
672
        }
673
674
        // Check for Id
675
        if (isset($propertyAnnotations[Annotation\Id::class])) {
676
            $assocMetadata->setPrimaryKey(true);
677
        }
678
679
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
680
681
        // Check for JoinColumn/JoinColumns annotations
682
        switch (true) {
683
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
684
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
685
686
                $assocMetadata->addJoinColumn(
687
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
688
                );
689
690
                break;
691
692
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
693
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
694
695
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
696
                    $assocMetadata->addJoinColumn(
697
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
698
                    );
699
                }
700
701
                break;
702
        }
703
704
        return $assocMetadata;
705
    }
706
707
    /**
708
     * @param Annotation\Annotation[] $propertyAnnotations
709
     *
710
     * @throws Mapping\MappingException
711
     */
712
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
713
        \ReflectionProperty $reflectionProperty,
714
        array $propertyAnnotations,
715
        Mapping\ClassMetadata $metadata,
716
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
717
    ) : Mapping\OneToManyAssociationMetadata {
718
        $className      = $metadata->getClassName();
719
        $fieldName      = $reflectionProperty->getName();
720
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
721
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
722
        $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...
723
724
        $assocMetadata->setTargetEntity($targetEntity);
725
        $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...
726
        $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...
727
        $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...
728
729
        if (! empty($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...
730
            $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
731
        }
732
733
        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...
734
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
735
        }
736
737
        // Check for OrderBy
738
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
739
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
740
741
            $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...
742
        }
743
744
        // Check for Id
745
        if (isset($propertyAnnotations[Annotation\Id::class])) {
746
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
747
        }
748
749
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
750
751
        return $assocMetadata;
752
    }
753
754
    /**
755
     * @param Annotation\Annotation[] $propertyAnnotations
756
     *
757
     * @throws Mapping\MappingException
758
     */
759
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
760
        \ReflectionProperty $reflectionProperty,
761
        array $propertyAnnotations,
762
        Mapping\ClassMetadata $metadata,
763
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

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

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

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

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

1004
            $this->convertTableAnnotationToTableMetadata($table, /** @scrutinizer ignore-type */ $tableAnnot);
Loading history...
Bug introduced by
$table of type Doctrine\ORM\Mapping\TableMetadata is incompatible with the type Doctrine\ORM\Annotation\Table expected by parameter $tableAnnot of Doctrine\ORM\Mapping\Dri...tationToTableMetadata(). ( Ignorable by Annotation )

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

1004
            $this->convertTableAnnotationToTableMetadata(/** @scrutinizer ignore-type */ $table, $tableAnnot);
Loading history...
1005
        }
1006
1007
        $metadata->setTable($table);
1008
    }
1009
1010
    /**
1011
     * @param Annotation\Annotation[] $classAnnotations
1012
     *
1013
     * @throws Mapping\MappingException
1014
     */
1015
    private function attachDiscriminatorColumn(
1016
        array $classAnnotations,
1017
        \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

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

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

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

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

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

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

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

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

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

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

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