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

AnnotationDriver::convertSqlResultSetMapping()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 19
nc 6
nop 1
dl 0
loc 33
ccs 0
cts 27
cp 0
crap 20
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver\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
    /** @var int[] */
41
    protected $entityAnnotationClasses = [
42
        Annotation\Entity::class           => 1,
43
        Annotation\MappedSuperclass::class => 2,
44
        Annotation\Embeddable::class       => 3,
45
    ];
46
47
    /**
48
     * The AnnotationReader.
49
     *
50
     * @var AnnotationReader
51
     */
52
    protected $reader;
53
54
    /**
55
     * The paths where to look for mapping files.
56
     *
57
     * @var string[]
58
     */
59
    protected $paths = [];
60
61
    /**
62
     * The paths excluded from path where to look for mapping files.
63
     *
64
     * @var string[]
65
     */
66
    protected $excludePaths = [];
67
68
    /**
69
     * The file extension of mapping documents.
70
     *
71
     * @var string
72
     */
73
    protected $fileExtension = '.php';
74
75
    /**
76
     * Cache for AnnotationDriver#getAllClassNames().
77
     *
78
     * @var string[]|null
79
     */
80
    protected $classNames;
81
82
    /**
83
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
84
     * docblock annotations.
85
     *
86
     * @param AnnotationReader     $reader The AnnotationReader to use, duck-typed.
87
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
88
     */
89
    public function __construct($reader, $paths = null)
90
    {
91
        $this->reader = $reader;
92
93
        if ($paths) {
94
            $this->addPaths((array) $paths);
95
        }
96
    }
97
98
    /**
99
     * Appends lookup paths to metadata driver.
100
     *
101
     * @param string[] $paths
102
     */
103
    public function addPaths(array $paths)
104
    {
105
        $this->paths = array_unique(array_merge($this->paths, $paths));
106
    }
107
108
    /**
109
     * Retrieves the defined metadata lookup paths.
110
     *
111
     * @return string[]
112
     */
113
    public function getPaths()
114
    {
115
        return $this->paths;
116
    }
117
118
    /**
119
     * Append exclude lookup paths to metadata driver.
120
     *
121
     * @param string[] $paths
122
     */
123
    public function addExcludePaths(array $paths)
124
    {
125
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
126
    }
127
128
    /**
129
     * Retrieve the defined metadata lookup exclude paths.
130
     *
131
     * @return string[]
132
     */
133
    public function getExcludePaths()
134
    {
135
        return $this->excludePaths;
136
    }
137
138
    /**
139
     * Retrieve the current annotation reader
140
     *
141
     * @return AnnotationReader
142
     */
143
    public function getReader()
144
    {
145
        return $this->reader;
146
    }
147
148
    /**
149
     * Gets the file extension used to look for mapping files under.
150
     *
151
     * @return string
152
     */
153
    public function getFileExtension()
154
    {
155
        return $this->fileExtension;
156
    }
157
158
    /**
159
     * Sets the file extension used to look for mapping files under.
160
     *
161
     * @param string $fileExtension The file extension to set.
162
     */
163
    public function setFileExtension($fileExtension)
164
    {
165
        $this->fileExtension = $fileExtension;
166
    }
167
168
    /**
169
     * Returns whether the class with the specified name is transient. Only non-transient
170
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
171
     *
172
     * A class is non-transient if it is annotated with an annotation
173
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
174
     *
175
     * @param string $className
176
     *
177
     * @return bool
178
     */
179
    public function isTransient($className)
180
    {
181
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
182
183
        foreach ($classAnnotations as $annot) {
184
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
185
                return false;
186
            }
187
        }
188
        return true;
189
    }
190
191
    /**
192
     * {@inheritDoc}
193
     */
194
    public function getAllClassNames()
195
    {
196
        if ($this->classNames !== null) {
197
            return $this->classNames;
198
        }
199
200
        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...
201
            throw Mapping\MappingException::pathRequired();
202
        }
203
204
        $classes       = [];
205
        $includedFiles = [];
206
207
        foreach ($this->paths as $path) {
208
            if (! is_dir($path)) {
209
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
210
            }
211
212
            $iterator = new \RegexIterator(
213
                new \RecursiveIteratorIterator(
214
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
215
                    \RecursiveIteratorIterator::LEAVES_ONLY
216
                ),
217
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
218
                \RecursiveRegexIterator::GET_MATCH
219
            );
220
221
            foreach ($iterator as $file) {
222
                $sourceFile = $file[0];
223
224
                if (! preg_match('(^phar:)i', $sourceFile)) {
225
                    $sourceFile = realpath($sourceFile);
226
                }
227
228
                foreach ($this->excludePaths as $excludePath) {
229
                    $exclude = str_replace('\\', '/', realpath($excludePath));
230
                    $current = str_replace('\\', '/', $sourceFile);
231
232
                    if (strpos($current, $exclude) !== false) {
233
                        continue 2;
234
                    }
235
                }
236
237
                require_once $sourceFile;
238
239
                $includedFiles[] = $sourceFile;
240
            }
241
        }
242
243
        $declared = get_declared_classes();
244
245
        foreach ($declared as $className) {
246
            $rc         = new \ReflectionClass($className);
247
            $sourceFile = $rc->getFileName();
248
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
249
                $classes[] = $className;
250
            }
251
        }
252
253
        $this->classNames = $classes;
254
255
        return $classes;
256
    }
257
258
    /**
259
     * {@inheritdoc}
260
     *
261
     * @throws \UnexpectedValueException
262
     * @throws \ReflectionException
263
     * @throws Mapping\MappingException
264
     */
265
    public function loadMetadataForClass(
266
        string $className,
267
        Mapping\ClassMetadata $metadata,
268
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
269
    ) : Mapping\ClassMetadata {
270
        $reflectionClass = $metadata->getReflectionClass();
271
272
        if (! $reflectionClass) {
273
            // this happens when running annotation driver in combination with
274
            // static reflection services. This is not the nicest fix
275
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
276
        }
277
278
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
279
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
280
            $reflectionClass,
281
            $classAnnotations,
282
            $metadata,
283
            $metadataBuildingContext
284
        );
285
286
        // Evaluate @Cache annotation
287
        if (isset($classAnnotations[Annotation\Cache::class])) {
288
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
289
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
290
291
            $classMetadata->setCache($cache);
292
        }
293
294
        // Evaluate annotations on properties/fields
295
        /* @var $reflProperty \ReflectionProperty */
296
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
297
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
298
                continue;
299
            }
300
301
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
302
            $property            = $this->convertPropertyAnnotationsToProperty(
303
                $propertyAnnotations,
304
                $reflectionProperty,
305
                $classMetadata,
306
                $metadataBuildingContext
307
            );
308
309
            if (! $property) {
310
                continue;
311
            }
312
313
            $metadata->addProperty($property);
314
        }
315
316
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
317
318
        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...
319
    }
320
321
    /**
322
     * @param Annotation\Annotation[] $classAnnotations
323
     *
324
     * @throws \UnexpectedValueException
325
     * @throws Mapping\MappingException
326
     */
327
    private function convertClassAnnotationsToClassMetadata(
328
        \ReflectionClass $reflectionClass,
329
        array $classAnnotations,
330
        Mapping\ClassMetadata $metadata,
331
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
332
    ) : Mapping\ClassMetadata {
333
        switch (true) {
334
            case isset($classAnnotations[Annotation\Entity::class]):
335
                $binder = new Binder\EntityClassMetadataBinder(
336
                    $reflectionClass,
337
                    $classAnnotations,
338
                    $metadata,
339
                    $metadataBuildingContext
340
                );
341
342
                break;
343
344
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
345
                $binder = new Binder\MappedSuperClassMetadataBinder(
346
                    $reflectionClass,
347
                    $classAnnotations,
348
                    $metadata,
349
                    $metadataBuildingContext
350
                );
351
                break;
352
353
            case isset($classAnnotations[Annotation\Embeddable::class]):
354
                $binder = new Binder\EmbeddableClassMetadataBinder(
355
                    $reflectionClass,
356
                    $classAnnotations,
357
                    $metadata,
358
                    $metadataBuildingContext
359
                );
360
                break;
361
362
            default:
363
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
364
        }
365
366
        return $binder->bind();
367
    }
368
369
    /**
370
     * @param Annotation\Annotation[] $classAnnotations
371
     *
372
     * @return Mapping\ClassMetadata
373
     *
374
     * @throws Mapping\MappingException
375
     * @throws \UnexpectedValueException
376
     */
377
    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...
378
        array $classAnnotations,
379
        \ReflectionClass $reflectionClass,
380
        Mapping\ClassMetadata $metadata,
381
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
382
    ) {
383
        /** @var Annotation\Entity $entityAnnot */
384
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
385
386
        if ($entityAnnot->repositoryClass !== null) {
387
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
388
        }
389
390
        if ($entityAnnot->readOnly) {
391
            $metadata->asReadOnly();
392
        }
393
394
        $metadata->isMappedSuperclass = false;
395
        $metadata->isEmbeddedClass    = false;
396
397
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
398
399
        // Evaluate @ChangeTrackingPolicy annotation
400
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
401
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
402
403
            $metadata->setChangeTrackingPolicy(
404
                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...
405
            );
406
        }
407
408
        // Evaluate @InheritanceType annotation
409
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
410
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
411
412
            $metadata->setInheritanceType(
413
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
414
            );
415
416
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
417
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
418
            }
419
        }
420
421
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
422
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
423
424
        return $metadata;
425
    }
426
427
    /**
428
     * @param Annotation\Annotation[] $classAnnotations
429
     */
430
    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...
431
        array $classAnnotations,
432
        \ReflectionClass $reflectionClass,
433
        Mapping\ClassMetadata $metadata,
434
        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

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

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

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

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

714
        /** @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...
715
    ) : Mapping\OneToManyAssociationMetadata {
716
        $className      = $metadata->getClassName();
717
        $fieldName      = $reflectionProperty->getName();
718
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
719
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
720
        $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...
721
722
        $assocMetadata->setTargetEntity($targetEntity);
723
        $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...
724
        $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...
725
        $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...
726
727
        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...
728
            $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
729
        }
730
731
        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...
732
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
733
        }
734
735
        // Check for OrderBy
736
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
737
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
738
739
            $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...
740
        }
741
742
        // Check for Id
743
        if (isset($propertyAnnotations[Annotation\Id::class])) {
744
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
745
        }
746
747
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
748
749
        return $assocMetadata;
750
    }
751
752
    /**
753
     * @param Annotation\Annotation[] $propertyAnnotations
754
     *
755
     * @throws Mapping\MappingException
756
     */
757
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
758
        \ReflectionProperty $reflectionProperty,
759
        array $propertyAnnotations,
760
        Mapping\ClassMetadata $metadata,
761
        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

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

981
        /** @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...
982
        Mapping\ClassMetadata $metadata,
983
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
984
    ) : void {
985
        $parent = $metadata->getParent();
986
987
        if ($parent !== null && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
988
            $metadata->setTable($parent->table);
989
990
            return;
991
        }
992
993
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
994
        $table          = new Mapping\TableMetadata();
995
996
        $table->setName($namingStrategy->classToTableName($metadata->getClassName()));
997
998
        // Evaluate @Table annotation
999
        if (isset($classAnnotations[Annotation\Table::class])) {
1000
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1001
1002
            $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

1002
            $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

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

1015
        /** @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...
1016
        Mapping\ClassMetadata $metadata,
1017
        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

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

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

1119
        /** @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...
1120
        Mapping\ClassMetadata $metadata,
1121
        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

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