Failed Conditions
CANCELLED  
Pull Request — master (#7095)
by Benjamin
10:13
created

convertReflectionPropertyToFieldMetadata()   C

Complexity

Conditions 8
Paths 13

Size

Total Lines 63
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 32
nc 13
nop 4
dl 0
loc 63
ccs 0
cts 45
cp 0
crap 72
rs 6.8825
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     * @return mixed[]
980
     */
981
    private function convertSqlResultSetMapping(Annotation\SqlResultSetMapping $resultSetMapping)
0 ignored issues
show
Unused Code introduced by
The method convertSqlResultSetMapping() 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...
982
    {
983
        $entities = [];
984
985
        foreach ($resultSetMapping->entities as $entityResultAnnot) {
986
            $entityResult = [
987
                'fields'                => [],
988
                'entityClass'           => $entityResultAnnot->entityClass,
989
                'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
990
            ];
991
992
            foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
993
                $entityResult['fields'][] = [
994
                    'name'      => $fieldResultAnnot->name,
995
                    'column'    => $fieldResultAnnot->column,
996
                ];
997
            }
998
999
            $entities[] = $entityResult;
1000
        }
1001
1002
        $columns = [];
1003
1004
        foreach ($resultSetMapping->columns as $columnResultAnnot) {
1005
            $columns[] = [
1006
                'name' => $columnResultAnnot->name,
1007
            ];
1008
        }
1009
1010
        return [
1011
            'name'     => $resultSetMapping->name,
1012
            'entities' => $entities,
1013
            'columns'  => $columns,
1014
        ];
1015
    }
1016
1017
    /**
1018
     * @param Annotation\Annotation[] $classAnnotations
1019
     */
1020
    private function attachTable(
1021
        array $classAnnotations,
1022
        \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

1022
        /** @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...
1023
        Mapping\ClassMetadata $metadata,
1024
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
1025
    ) : void {
1026
        $parent = $metadata->getParent();
1027
1028
        if ($parent !== null && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
1029
            $metadata->setTable($parent->table);
1030
1031
            return;
1032
        }
1033
1034
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1035
        $table          = new Mapping\TableMetadata();
1036
1037
        $table->setName($namingStrategy->classToTableName($metadata->getClassName()));
1038
1039
        // Evaluate @Table annotation
1040
        if (isset($classAnnotations[Annotation\Table::class])) {
1041
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1042
1043
            $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

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

1043
            $this->convertTableAnnotationToTableMetadata(/** @scrutinizer ignore-type */ $table, $tableAnnot);
Loading history...
1044
        }
1045
1046
        $metadata->setTable($table);
1047
    }
1048
1049
    /**
1050
     * @param Annotation\Annotation[] $classAnnotations
1051
     *
1052
     * @throws Mapping\MappingException
1053
     */
1054
    private function attachDiscriminatorColumn(
1055
        array $classAnnotations,
1056
        \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

1056
        /** @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...
1057
        Mapping\ClassMetadata $metadata,
1058
        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

1058
        /** @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...
1059
    ) : void {
1060
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1061
1062
        $discriminatorColumn->setTableName($metadata->getTableName());
1063
        $discriminatorColumn->setColumnName('dtype');
1064
        $discriminatorColumn->setType(Type::getType('string'));
1065
        $discriminatorColumn->setLength(255);
1066
1067
        // Evaluate DiscriminatorColumn annotation
1068
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1069
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1070
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1071
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1072
                ? $discriminatorColumnAnnotation->type
1073
                : 'string';
1074
1075
            $discriminatorColumn->setType(Type::getType($typeName));
1076
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1077
1078
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1079
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1080
            }
1081
1082
            if (! empty($discriminatorColumnAnnotation->length)) {
1083
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1084
            }
1085
        }
1086
1087
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1088
1089
        // Evaluate DiscriminatorMap annotation
1090
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1091
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1092
            $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...
1093
1094
            $metadata->setDiscriminatorMap($discriminatorMap);
1095
        }
1096
    }
1097
1098
    /**
1099
     * @param Annotation\Annotation[] $classAnnotations
1100
     */
1101
    private function attachLifecycleCallbacks(
1102
        array $classAnnotations,
1103
        \ReflectionClass $reflectionClass,
1104
        Mapping\ClassMetadata $metadata
1105
    ) : void {
1106
        // Evaluate @HasLifecycleCallbacks annotation
1107
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1108
            /* @var $method \ReflectionMethod */
1109
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1110
                foreach ($this->getMethodCallbacks($method) as $callback) {
1111
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1112
                }
1113
            }
1114
        }
1115
    }
1116
1117
    /**
1118
     * @param Annotation\Annotation[] $classAnnotations
1119
     *
1120
     * @throws \ReflectionException
1121
     * @throws Mapping\MappingException
1122
     */
1123
    private function attachEntityListeners(
1124
        array $classAnnotations,
1125
        \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

1125
        /** @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...
1126
        Mapping\ClassMetadata $metadata
1127
    ) : void {
1128
        // Evaluate @EntityListeners annotation
1129
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1130
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1131
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1132
1133
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1134
                if (! class_exists($listenerClassName)) {
1135
                    throw Mapping\MappingException::entityListenerClassNotFound(
1136
                        $listenerClassName,
1137
                        $metadata->getClassName()
1138
                    );
1139
                }
1140
1141
                $listenerClass = new \ReflectionClass($listenerClassName);
1142
1143
                /* @var $method \ReflectionMethod */
1144
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1145
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1146
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1147
                    }
1148
                }
1149
            }
1150
        }
1151
    }
1152
1153
    /**
1154
     * @param Annotation\Annotation[] $classAnnotations
1155
     *
1156
     * @throws Mapping\MappingException
1157
     */
1158
    private function attachPropertyOverrides(
1159
        array $classAnnotations,
1160
        \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

1160
        /** @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...
1161
        Mapping\ClassMetadata $metadata,
1162
        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

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