Failed Conditions
Pull Request — develop (#6936)
by Michael
589:45 queued 491:04
created

AnnotationDriver::getPropertyAnnotations()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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

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

418
                $this->/** @scrutinizer ignore-call */ 
419
                       attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
419
            }
420
        }
421
422
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
423
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
424
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
425
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
426
427
        return $metadata;
428
    }
429
430
    /**
431
     * @param array                                $classAnnotations
432
     * @param \ReflectionClass                     $reflectionClass
433
     * @param Mapping\ClassMetadata                $metadata
434
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
435
     *
436
     * @return Mapping\ClassMetadata
437
     */
438
    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...
439
        array $classAnnotations,
440
        \ReflectionClass $reflectionClass,
441
        Mapping\ClassMetadata $metadata,
442
        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

442
        /** @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...
443
    ) : Mapping\ClassMetadata
444
    {
445
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
446
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
447
448
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
449
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
450
        }
451
452
        $metadata->isMappedSuperclass = true;
453
        $metadata->isEmbeddedClass = false;
454
455
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
456
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
457
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
458
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
459
460
        return $metadata;
461
    }
462
463
    /**
464
     * @param array                 $propertyAnnotations
465
     * @param \ReflectionProperty   $reflectionProperty
466
     * @param Mapping\ClassMetadata $metadata
467
     *
468
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
469
     *
470
     * @return Mapping\Property|null
471
     *
472
     * @throws Mapping\MappingException
473
     */
474
    private function convertPropertyAnnotationsToProperty(
475
        array $propertyAnnotations,
476
        \ReflectionProperty $reflectionProperty,
477
        Mapping\ClassMetadata $metadata,
478
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
479
    ) : ?Mapping\Property
480
    {
481
        switch (true) {
482
            case isset($propertyAnnotations[Annotation\Column::class]):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
483
                return $this->convertReflectionPropertyToFieldMetadata(
484
                    $reflectionProperty,
485
                    $propertyAnnotations,
486
                    $metadata,
487
                    $metadataBuildingContext
488
                );
489
490
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
491
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
492
                    $reflectionProperty,
493
                    $propertyAnnotations,
494
                    $metadata,
495
                    $metadataBuildingContext
496
                );
497
498
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
499
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
500
                    $reflectionProperty,
501
                    $propertyAnnotations,
502
                    $metadata,
503
                    $metadataBuildingContext
504
                );
505
506
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
507
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
508
                    $reflectionProperty,
509
                    $propertyAnnotations,
510
                    $metadata,
511
                    $metadataBuildingContext
512
                );
513
514
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
515
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
516
                    $reflectionProperty,
517
                    $propertyAnnotations,
518
                    $metadata,
519
                    $metadataBuildingContext
520
                );
521
522
            case isset($propertyAnnotations[Annotation\Embedded::class]):
523
                return null;
524
525
            default:
526
                return new Mapping\TransientMetadata($reflectionProperty->getName());
527
        }
528
    }
529
530
    /**
531
     * @param \ReflectionProperty                  $reflProperty
532
     * @param array                                $propertyAnnotations
533
     * @param Mapping\ClassMetadata                $metadata
534
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
535
     *
536
     * @return Mapping\FieldMetadata
537
     *
538
     * @throws Mapping\MappingException
539
     */
540
    private function convertReflectionPropertyToFieldMetadata(
541
        \ReflectionProperty $reflProperty,
542
        array $propertyAnnotations,
543
        Mapping\ClassMetadata $metadata,
544
        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

544
        /** @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...
545
    ) : Mapping\FieldMetadata
546
    {
547
        $className   = $metadata->getClassName();
548
        $fieldName   = $reflProperty->getName();
549
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
550
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
551
552
        if ($columnAnnot->type == null) {
553
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
554
        }
555
556
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
557
558
        // Check for Id
559
        if (isset($propertyAnnotations[Annotation\Id::class])) {
560
            $fieldMetadata->setPrimaryKey(true);
561
        }
562
563
        // Check for GeneratedValue strategy
564
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
565
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
566
            $strategy = strtoupper($generatedValueAnnot->strategy);
567
            $idGeneratorType = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
568
569
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
570
                $idGeneratorDefinition = [];
571
572
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
573
                switch (true) {
574
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
575
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
576
577
                        $idGeneratorDefinition = [
578
                            'sequenceName' => $seqGeneratorAnnot->sequenceName,
579
                            'allocationSize' => $seqGeneratorAnnot->allocationSize,
580
                        ];
581
582
                        break;
583
584
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
585
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
586
587
                        $idGeneratorDefinition = [
588
                            'class' => $customGeneratorAnnot->class,
589
                            'arguments' => $customGeneratorAnnot->arguments,
590
                        ];
591
592
                        break;
593
594
                    /* @todo If it is not supported, why does this exist? */
595
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
596
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
597
                }
598
599
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
600
            }
601
        }
602
603
        return $fieldMetadata;
604
    }
605
606
    /**
607
     * @param \ReflectionProperty                  $reflectionProperty
608
     * @param array                                $propertyAnnotations
609
     * @param Mapping\ClassMetadata                $metadata
610
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
611
     *
612
     * @return Mapping\OneToOneAssociationMetadata
613
     */
614
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
615
        \ReflectionProperty $reflectionProperty,
616
        array $propertyAnnotations,
617
        Mapping\ClassMetadata $metadata,
618
        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

618
        /** @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...
619
    )
620
    {
621
        $className     = $metadata->getClassName();
622
        $fieldName     = $reflectionProperty->getName();
623
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
624
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
625
        $targetEntity  = $oneToOneAnnot->targetEntity;
626
627
        $assocMetadata->setTargetEntity($targetEntity);
628
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade));
629
        $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval);
630
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch));
631
632
        if (! empty($oneToOneAnnot->mappedBy)) {
633
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
634
        }
635
636
        if (! empty($oneToOneAnnot->inversedBy)) {
637
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
638
        }
639
640
        // Check for Id
641
        if (isset($propertyAnnotations[Annotation\Id::class])) {
642
            $assocMetadata->setPrimaryKey(true);
643
        }
644
645
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
646
647
        // Check for JoinColumn/JoinColumns annotations
648
        switch (true) {
649
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
650
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
651
652
                $assocMetadata->addJoinColumn(
653
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
654
                );
655
656
                break;
657
658
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
659
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
660
661
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
662
                    $assocMetadata->addJoinColumn(
663
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
664
                    );
665
                }
666
667
                break;
668
        }
669
670
        return $assocMetadata;
671
    }
672
673
    /**
674
     * @param \ReflectionProperty                  $reflectionProperty
675
     * @param array                                $propertyAnnotations
676
     * @param Mapping\ClassMetadata                $metadata
677
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
678
     *
679
     * @return Mapping\ManyToOneAssociationMetadata
680
     */
681
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
682
        \ReflectionProperty $reflectionProperty,
683
        array $propertyAnnotations,
684
        Mapping\ClassMetadata $metadata,
685
        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

685
        /** @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...
686
    )
687
    {
688
        $className      = $metadata->getClassName();
689
        $fieldName      = $reflectionProperty->getName();
690
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
691
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
692
        $targetEntity   = $manyToOneAnnot->targetEntity;
693
694
        $assocMetadata->setTargetEntity($targetEntity);
695
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade));
696
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch));
697
698
        if (! empty($manyToOneAnnot->inversedBy)) {
699
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
700
        }
701
702
        // Check for Id
703
        if (isset($propertyAnnotations[Annotation\Id::class])) {
704
            $assocMetadata->setPrimaryKey(true);
705
        }
706
707
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
708
709
        // Check for JoinColumn/JoinColumns annotations
710
        switch (true) {
711
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
712
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
713
714
                $assocMetadata->addJoinColumn(
715
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
716
                );
717
718
                break;
719
720
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
721
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
722
723
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
724
                    $assocMetadata->addJoinColumn(
725
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
726
                    );
727
                }
728
729
                break;
730
        }
731
732
        return $assocMetadata;
733
    }
734
735
    /**
736
     * @param \ReflectionProperty                  $reflectionProperty
737
     * @param array                                $propertyAnnotations
738
     * @param Mapping\ClassMetadata                $metadata
739
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
740
     *
741
     * @return Mapping\OneToManyAssociationMetadata
742
     *
743
     * @throws Mapping\MappingException
744
     */
745
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
746
        \ReflectionProperty $reflectionProperty,
747
        array $propertyAnnotations,
748
        Mapping\ClassMetadata $metadata,
749
        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

749
        /** @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...
750
    ) : Mapping\OneToManyAssociationMetadata
751
    {
752
        $className      = $metadata->getClassName();
753
        $fieldName      = $reflectionProperty->getName();
754
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
755
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
756
        $targetEntity   = $oneToManyAnnot->targetEntity;
757
758
        $assocMetadata->setTargetEntity($targetEntity);
759
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
760
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
761
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));
762
763
        if (! empty($oneToManyAnnot->mappedBy)) {
764
            $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
765
        }
766
767
        if (! empty($oneToManyAnnot->indexBy)) {
768
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
769
        }
770
771
        // Check for OrderBy
772
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
773
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
774
775
            $assocMetadata->setOrderBy($orderByAnnot->value);
776
        }
777
778
        // Check for Id
779
        if (isset($propertyAnnotations[Annotation\Id::class])) {
780
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
781
        }
782
783
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
784
785
        return $assocMetadata;
786
    }
787
788
    /**
789
     * @param \ReflectionProperty                  $reflectionProperty
790
     * @param array                                $propertyAnnotations
791
     * @param Mapping\ClassMetadata                $metadata
792
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
793
     *
794
     * @return Mapping\ManyToManyAssociationMetadata
795
     *
796
     * @throws Mapping\MappingException
797
     */
798
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
799
        \ReflectionProperty $reflectionProperty,
800
        array $propertyAnnotations,
801
        Mapping\ClassMetadata $metadata,
802
        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

802
        /** @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...
803
    ) : Mapping\ManyToManyAssociationMetadata
804
    {
805
        $className       = $metadata->getClassName();
806
        $fieldName       = $reflectionProperty->getName();
807
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
808
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
809
        $targetEntity    = $manyToManyAnnot->targetEntity;
810
811
        $assocMetadata->setTargetEntity($targetEntity);
812
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade));
813
        $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval);
814
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch));
815
816
        if (! empty($manyToManyAnnot->mappedBy)) {
817
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
818
        }
819
820
        if (! empty($manyToManyAnnot->inversedBy)) {
821
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
822
        }
823
824
        if (! empty($manyToManyAnnot->indexBy)) {
825
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
826
        }
827
828
        // Check for JoinTable
829
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
830
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
831
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
832
833
            $assocMetadata->setJoinTable($joinTableMetadata);
834
        }
835
836
        // Check for OrderBy
837
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
838
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
839
840
            $assocMetadata->setOrderBy($orderByAnnot->value);
841
        }
842
843
        // Check for Id
844
        if (isset($propertyAnnotations[Annotation\Id::class])) {
845
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
846
        }
847
848
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
849
850
        return $assocMetadata;
851
    }
852
853
    /**
854
     * Parse the given Column as FieldMetadata
855
     *
856
     * @param Annotation\Column $columnAnnot
857
     * @param string            $fieldName
858
     * @param bool              $isVersioned
859
     *
860
     * @return Mapping\FieldMetadata
861
     */
862
    private function convertColumnAnnotationToFieldMetadata(
863
        Annotation\Column $columnAnnot,
864
        string $fieldName,
865
        bool $isVersioned
866
    ) : Mapping\FieldMetadata
867
    {
868
        $fieldMetadata = $isVersioned
869
            ? new Mapping\VersionFieldMetadata($fieldName)
870
            : new Mapping\FieldMetadata($fieldName)
871
        ;
872
873
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
874
875
        if (! empty($columnAnnot->name)) {
876
            $fieldMetadata->setColumnName($columnAnnot->name);
877
        }
878
879
        if (! empty($columnAnnot->columnDefinition)) {
880
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
881
        }
882
883
        if (! empty($columnAnnot->length)) {
884
            $fieldMetadata->setLength($columnAnnot->length);
885
        }
886
887
        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...
888
            $fieldMetadata->setOptions($columnAnnot->options);
889
        }
890
891
        $fieldMetadata->setScale($columnAnnot->scale);
892
        $fieldMetadata->setPrecision($columnAnnot->precision);
893
        $fieldMetadata->setNullable($columnAnnot->nullable);
894
        $fieldMetadata->setUnique($columnAnnot->unique);
895
896
        return $fieldMetadata;
897
    }
898
899
    /**
900
     * Parse the given Table as TableMetadata
901
     *
902
     * @param Annotation\Table      $tableAnnot
903
     * @param Mapping\TableMetadata $table
904
     *
905
     * @return void
906
     */
907
    private function convertTableAnnotationToTableMetadata(
908
        Annotation\Table $tableAnnot,
909
        Mapping\TableMetadata $table
910
    ) : void
911
    {
912
        if (! empty($tableAnnot->name)) {
913
            $table->setName($tableAnnot->name);
914
        }
915
916
        if (! empty($tableAnnot->schema)) {
917
            $table->setSchema($tableAnnot->schema);
918
        }
919
920
        foreach ($tableAnnot->options as $optionName => $optionValue) {
921
            $table->addOption($optionName, $optionValue);
922
        }
923
924
        foreach ($tableAnnot->indexes as $indexAnnot) {
925
            $table->addIndex([
926
                'name'    => $indexAnnot->name,
927
                'columns' => $indexAnnot->columns,
928
                'unique'  => $indexAnnot->unique,
929
                'options' => $indexAnnot->options,
930
                'flags'   => $indexAnnot->flags,
931
            ]);
932
        }
933
934
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
935
            $table->addUniqueConstraint([
936
                'name'    => $uniqueConstraintAnnot->name,
937
                'columns' => $uniqueConstraintAnnot->columns,
938
                'options' => $uniqueConstraintAnnot->options,
939
                'flags'   => $uniqueConstraintAnnot->flags,
940
            ]);
941
        }
942
943
        return $table;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $table returns the type Doctrine\ORM\Mapping\TableMetadata which is incompatible with the type-hinted return void.
Loading history...
944
    }
945
946
    /**
947
     * Parse the given JoinTable as JoinTableMetadata
948
     *
949
     * @param Annotation\JoinTable $joinTableAnnot
950
     *
951
     * @return Mapping\JoinTableMetadata
952
     */
953
    private function convertJoinTableAnnotationToJoinTableMetadata(
954
        Annotation\JoinTable $joinTableAnnot
955
    ) : Mapping\JoinTableMetadata
956
    {
957
        $joinTable = new Mapping\JoinTableMetadata();
958
959
        if (! empty($joinTableAnnot->name)) {
960
            $joinTable->setName($joinTableAnnot->name);
961
        }
962
963
        if (! empty($joinTableAnnot->schema)) {
964
            $joinTable->setSchema($joinTableAnnot->schema);
965
        }
966
967
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
968
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
969
970
            $joinTable->addJoinColumn($joinColumn);
971
        }
972
973
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
974
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
975
976
            $joinTable->addInverseJoinColumn($joinColumn);
977
        }
978
979
        return $joinTable;
980
    }
981
982
    /**
983
     * Parse the given JoinColumn as JoinColumnMetadata
984
     *
985
     * @param Annotation\JoinColumn $joinColumnAnnot
986
     *
987
     * @return Mapping\JoinColumnMetadata
988
     */
989
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
990
        Annotation\JoinColumn $joinColumnAnnot
991
    ) : Mapping\JoinColumnMetadata
992
    {
993
        $joinColumn = new Mapping\JoinColumnMetadata();
994
995
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
996
        if (! empty($joinColumnAnnot->name)) {
997
            $joinColumn->setColumnName($joinColumnAnnot->name);
998
        }
999
1000
        if (! empty($joinColumnAnnot->referencedColumnName)) {
1001
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
1002
        }
1003
1004
        $joinColumn->setNullable($joinColumnAnnot->nullable);
1005
        $joinColumn->setUnique($joinColumnAnnot->unique);
1006
1007
        if (! empty($joinColumnAnnot->fieldName)) {
1008
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
1009
        }
1010
1011
        if (! empty($joinColumnAnnot->columnDefinition)) {
1012
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
1013
        }
1014
1015
        if ($joinColumnAnnot->onDelete) {
1016
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
1017
        }
1018
1019
        return $joinColumn;
1020
    }
1021
1022
    /**
1023
     * Parse the given Cache as CacheMetadata
1024
     *
1025
     * @param Annotation\Cache      $cacheAnnot
1026
     * @param Mapping\ClassMetadata $metadata
1027
     * @param null|string           $fieldName
1028
     *
1029
     * @return Mapping\CacheMetadata
1030
     */
1031
    private function convertCacheAnnotationToCacheMetadata(
1032
        Annotation\Cache $cacheAnnot,
1033
        Mapping\ClassMetadata $metadata,
1034
        $fieldName = null
1035
    ) : Mapping\CacheMetadata
1036
    {
1037
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
1038
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
1039
1040
        $usage = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
1041
        $region = $cacheAnnot->region ?: $defaultRegion;
1042
1043
        return new Mapping\CacheMetadata($usage, $region);
1044
    }
1045
1046
    /**
1047
     * @param Annotation\SqlResultSetMapping $resultSetMapping
1048
     *
1049
     * @return array
1050
     */
1051
    private function convertSqlResultSetMapping(Annotation\SqlResultSetMapping $resultSetMapping)
1052
    {
1053
        $entities = [];
1054
1055
        foreach ($resultSetMapping->entities as $entityResultAnnot) {
1056
            $entityResult = [
1057
                'fields'                => [],
1058
                'entityClass'           => $entityResultAnnot->entityClass,
1059
                'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
1060
            ];
1061
1062
            foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
1063
                $entityResult['fields'][] = [
1064
                    'name'      => $fieldResultAnnot->name,
1065
                    'column'    => $fieldResultAnnot->column
1066
                ];
1067
            }
1068
1069
            $entities[] = $entityResult;
1070
        }
1071
1072
        $columns = [];
1073
1074
        foreach ($resultSetMapping->columns as $columnResultAnnot) {
1075
            $columns[] = [
1076
                'name' => $columnResultAnnot->name,
1077
            ];
1078
        }
1079
1080
        return [
1081
            'name'     => $resultSetMapping->name,
1082
            'entities' => $entities,
1083
            'columns'  => $columns
1084
        ];
1085
    }
1086
1087
    private function attachTable(
1088
        array $classAnnotations,
1089
        \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

1089
        /** @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...
1090
        Mapping\ClassMetadata $metadata,
1091
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
1092
    ) : void
1093
    {
1094
        $parent = $metadata->getParent();
1095
1096
        if ($parent->inheritanceType === InheritanceType::SINGLE_TABLE) {
0 ignored issues
show
Bug introduced by
The type Doctrine\ORM\Mapping\Dri...otation\InheritanceType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1097
            $metadata->setTable($parent->table);
1098
1099
            return;
1100
        }
1101
1102
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1103
        $table          = new Mapping\TableMetadata();
1104
1105
        $table->setName($namingStrategy->classToTableName($metadata->getClassName()));
1106
1107
        // Evaluate @Table annotation
1108
        if (isset($classAnnotations[Annotation\Table::class])) {
1109
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1110
1111
            $this->convertTableAnnotationToTableMetadata($table, $tableAnnot);
0 ignored issues
show
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

1111
            $this->convertTableAnnotationToTableMetadata(/** @scrutinizer ignore-type */ $table, $tableAnnot);
Loading history...
1112
        }
1113
1114
        $metadata->setTable($table);
1115
    }
1116
1117
    /**
1118
     * @param array                 $classAnnotations
1119
     * @param \ReflectionClass      $reflectionClass
1120
     * @param Mapping\ClassMetadata $metadata
1121
     *
1122
     * @return void
1123
     *
1124
     * @throws Mapping\MappingException
1125
     */
1126
    private function attachDiscriminatorColumn(
1127
        array $classAnnotations,
1128
        \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

1128
        /** @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...
1129
        Mapping\ClassMetadata $metadata,
1130
        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

1130
        /** @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...
1131
    ) : void
1132
    {
1133
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1134
1135
        $discriminatorColumn->setTableName($metadata->getTableName());
1136
        $discriminatorColumn->setColumnName('dtype');
1137
        $discriminatorColumn->setType(Type::getType('string'));
1138
        $discriminatorColumn->setLength(255);
1139
1140
        // Evaluate DiscriminatorColumn annotation
1141
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1142
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1143
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1144
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1145
                ? $discriminatorColumnAnnotation->type
1146
                : 'string';
1147
1148
            $discriminatorColumn->setType(Type::getType($typeName));
1149
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1150
1151
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1152
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1153
            }
1154
1155
            if (! empty($discriminatorColumnAnnotation->length)) {
1156
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1157
            }
1158
        }
1159
1160
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1161
1162
        // Evaluate DiscriminatorMap annotation
1163
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1164
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1165
            $discriminatorMap           = $discriminatorMapAnnotation->value;
1166
1167
            $metadata->setDiscriminatorMap($discriminatorMap);
1168
        }
1169
    }
1170
1171
    /**
1172
     * @param array                 $classAnnotations
1173
     * @param \ReflectionClass      $reflectionClass
1174
     * @param Mapping\ClassMetadata $metadata
1175
     *
1176
     * @return void
1177
     *
1178
     * @throws \UnexpectedValueException
1179
     * @throws Mapping\MappingException
1180
     */
1181
    private function attachNamedQueries(
1182
        array $classAnnotations,
1183
        \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

1183
        /** @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...
1184
        Mapping\ClassMetadata $metadata
1185
    ) : void
1186
    {
1187
        // Evaluate @NamedQueries annotation
1188
        if (isset($classAnnotations[Annotation\NamedQueries::class])) {
1189
            $namedQueriesAnnot = $classAnnotations[Annotation\NamedQueries::class];
1190
1191
            if (! is_array($namedQueriesAnnot->value)) {
1192
                throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1193
            }
1194
1195
            foreach ($namedQueriesAnnot->value as $namedQuery) {
1196
                if (! ($namedQuery instanceof Annotation\NamedQuery)) {
1197
                    throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1198
                }
1199
1200
                $metadata->addNamedQuery($namedQuery->name, $namedQuery->query);
1201
            }
1202
        }
1203
    }
1204
1205
    /**
1206
     * @param array                 $classAnnotations
1207
     * @param \ReflectionClass      $reflectionClass
1208
     * @param Mapping\ClassMetadata $metadata
1209
     *
1210
     * @return void
1211
     */
1212
    private function attachNamedNativeQueries(
1213
        array $classAnnotations,
1214
        \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

1214
        /** @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...
1215
        Mapping\ClassMetadata $metadata
1216
    ) : void
1217
    {
1218
        // Evaluate @NamedNativeQueries annotation
1219
        if (isset($classAnnotations[Annotation\NamedNativeQueries::class])) {
1220
            $namedNativeQueriesAnnot = $classAnnotations[Annotation\NamedNativeQueries::class];
1221
1222
            foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
1223
                $metadata->addNamedNativeQuery(
1224
                    $namedNativeQuery->name,
1225
                    $namedNativeQuery->query,
1226
                    [
1227
                        'resultClass'      => $namedNativeQuery->resultClass,
1228
                        'resultSetMapping' => $namedNativeQuery->resultSetMapping,
1229
                    ]
1230
                );
1231
            }
1232
        }
1233
1234
        // Evaluate @SqlResultSetMappings annotation
1235
        if (isset($classAnnotations[Annotation\SqlResultSetMappings::class])) {
1236
            $sqlResultSetMappingsAnnot = $classAnnotations[Annotation\SqlResultSetMappings::class];
1237
1238
            foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
1239
                $sqlResultSetMapping = $this->convertSqlResultSetMapping($resultSetMapping);
1240
1241
                $metadata->addSqlResultSetMapping($sqlResultSetMapping);
1242
            }
1243
        }
1244
    }
1245
1246
    /**
1247
     * @param array                 $classAnnotations
1248
     * @param \ReflectionClass      $reflectionClass
1249
     * @param Mapping\ClassMetadata $metadata
1250
     *
1251
     * @return void
1252
     */
1253
    private function attachLifecycleCallbacks(
1254
        array $classAnnotations,
1255
        \ReflectionClass $reflectionClass,
1256
        Mapping\ClassMetadata $metadata
1257
    ) : void
1258
    {
1259
        // Evaluate @HasLifecycleCallbacks annotation
1260
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1261
            /* @var $method \ReflectionMethod */
1262
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1263
                foreach ($this->getMethodCallbacks($method) as $callback) {
1264
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1265
                }
1266
            }
1267
        }
1268
    }
1269
1270
    /**
1271
     * @param array                 $classAnnotations
1272
     * @param \ReflectionClass      $reflectionClass
1273
     * @param Mapping\ClassMetadata $metadata
1274
     *
1275
     * @return void
1276
     *
1277
     * @throws \ReflectionException
1278
     * @throws Mapping\MappingException
1279
     */
1280
    private function attachEntityListeners(
1281
        array $classAnnotations,
1282
        \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

1282
        /** @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...
1283
        Mapping\ClassMetadata $metadata
1284
    ) : void
1285
    {
1286
        // Evaluate @EntityListeners annotation
1287
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1288
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1289
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1290
1291
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1292
                if (! class_exists($listenerClassName)) {
1293
                    throw Mapping\MappingException::entityListenerClassNotFound(
1294
                        $listenerClassName,
1295
                        $metadata->getClassName()
1296
                    );
1297
                }
1298
1299
                $listenerClass = new \ReflectionClass($listenerClassName);
1300
1301
                /* @var $method \ReflectionMethod */
1302
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1303
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1304
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1305
                    }
1306
                }
1307
            }
1308
        }
1309
    }
1310
1311
    /**
1312
     * @param array                 $classAnnotations
1313
     * @param \ReflectionClass      $reflectionClass
1314
     * @param Mapping\ClassMetadata $metadata
1315
     *
1316
     * @return void
1317
     *
1318
     * @throws Mapping\MappingException
1319
     */
1320
    private function attachPropertyOverrides(
1321
        array $classAnnotations,
1322
        \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

1322
        /** @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...
1323
        Mapping\ClassMetadata $metadata,
1324
        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

1324
        /** @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...
1325
    ) : void
1326
    {
1327
        // Evaluate AssociationOverrides annotation
1328
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1329
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1330
1331
            foreach ($associationOverridesAnnot->value as $associationOverride) {
1332
                $fieldName = $associationOverride->name;
1333
                $property  = $metadata->getProperty($fieldName);
1334
1335
                if (! $property) {
1336
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1337
                }
1338
1339
                $existingClass = get_class($property);
1340
                $override      = new $existingClass($fieldName);
1341
1342
                // Check for JoinColumn/JoinColumns annotations
1343
                if ($associationOverride->joinColumns) {
1344
                    $joinColumns = [];
1345
1346
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1347
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1348
                    }
1349
1350
                    $override->setJoinColumns($joinColumns);
1351
                }
1352
1353
                // Check for JoinTable annotations
1354
                if ($associationOverride->joinTable) {
1355
                    $joinTableAnnot    = $associationOverride->joinTable;
1356
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1357
1358
                    $override->setJoinTable($joinTableMetadata);
1359
                }
1360
1361
                // Check for inversedBy
1362
                if ($associationOverride->inversedBy) {
1363
                    $override->setInversedBy($associationOverride->inversedBy);
1364
                }
1365
1366
                // Check for fetch
1367
                if ($associationOverride->fetch) {
1368
                    $override->setFetchMode(
1369
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1370
                    );
1371
                }
1372
1373
                $metadata->setPropertyOverride($override);
1374
            }
1375
        }
1376
1377
        // Evaluate AttributeOverrides annotation
1378
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1379
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1380
1381
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1382
                $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1383
                    $attributeOverrideAnnot->column,
1384
                    $attributeOverrideAnnot->name,
1385
                    false
1386
                );
1387
1388
                $metadata->setPropertyOverride($fieldMetadata);
1389
            }
1390
        }
1391
    }
1392
1393
    private function attachAssociationPropertyCache(
1394
        array $propertyAnnotations,
1395
        \ReflectionProperty $reflectionProperty,
1396
        Mapping\AssociationMetadata $assocMetadata,
1397
        Mapping\ClassMetadata $metadata
1398
    ) : void
1399
    {
1400
        // Check for Cache
1401
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
1402
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
1403
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata(
1404
                $cacheAnnot,
1405
                $metadata,
1406
                $reflectionProperty->getName()
1407
            );
1408
1409
            $assocMetadata->setCache($cacheMetadata);
1410
        }
1411
    }
1412
1413
    /**
1414
     * Attempts to resolve the cascade modes.
1415
     *
1416
     * @param string $className        The class name.
1417
     * @param string $fieldName        The field name.
1418
     * @param array  $originalCascades The original unprocessed field cascades.
1419
     *
1420
     * @return array The processed field cascades.
1421
     *
1422
     * @throws Mapping\MappingException If a cascade option is not valid.
1423
     */
1424
    private function getCascade(string $className, string $fieldName, array $originalCascades) : array
1425
    {
1426
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1427
        $cascades     = array_map('strtolower', $originalCascades);
1428
1429
        if (in_array('all', $cascades)) {
1430
            $cascades = $cascadeTypes;
1431
        }
1432
1433
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1434
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1435
1436
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1437
        }
1438
1439
        return $cascades;
1440
    }
1441
1442
    /**
1443
     * Attempts to resolve the fetch mode.
1444
     *
1445
     * @param string $className The class name.
1446
     * @param string $fetchMode The fetch mode.
1447
     *
1448
     * @return string The fetch mode as defined in ClassMetadata.
1449
     *
1450
     * @throws Mapping\MappingException If the fetch mode is not valid.
1451
     */
1452
    private function getFetchMode($className, $fetchMode) : string
1453
    {
1454
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1455
1456
        if (! defined($fetchModeConstant)) {
1457
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1458
        }
1459
1460
        return constant($fetchModeConstant);
1461
    }
1462
1463
    /**
1464
     * Parses the given method.
1465
     *
1466
     * @param \ReflectionMethod $method
1467
     *
1468
     * @return array
1469
     */
1470
    private function getMethodCallbacks(\ReflectionMethod $method) : array
1471
    {
1472
        $annotations = $this->getMethodAnnotations($method);
1473
        $events      = [
1474
            Events::prePersist  => Annotation\PrePersist::class,
1475
            Events::postPersist => Annotation\PostPersist::class,
1476
            Events::preUpdate   => Annotation\PreUpdate::class,
1477
            Events::postUpdate  => Annotation\PostUpdate::class,
1478
            Events::preRemove   => Annotation\PreRemove::class,
1479
            Events::postRemove  => Annotation\PostRemove::class,
1480
            Events::postLoad    => Annotation\PostLoad::class,
1481
            Events::preFlush    => Annotation\PreFlush::class,
1482
        ];
1483
1484
        // Check for callbacks
1485
        $callbacks = [];
1486
1487
        foreach ($events as $eventName => $annotationClassName) {
1488
            if (isset($annotations[$annotationClassName]) || $method->getName() === $eventName) {
1489
                $callbacks[] = $eventName;
1490
            }
1491
        }
1492
1493
        return $callbacks;
1494
    }
1495
1496
    /**
1497
     * @param \ReflectionClass $reflectionClass
1498
     *
1499
     * @return array
1500
     */
1501
    private function getClassAnnotations(\ReflectionClass $reflectionClass) : array
1502
    {
1503
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1504
1505
        foreach ($classAnnotations as $key => $annot) {
1506
            if (! is_numeric($key)) {
1507
                continue;
1508
            }
1509
1510
            $classAnnotations[get_class($annot)] = $annot;
1511
        }
1512
1513
        return $classAnnotations;
1514
    }
1515
1516
    /**
1517
     * @param \ReflectionProperty $reflectionProperty
1518
     *
1519
     * @return array
1520
     */
1521
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty) : array
1522
    {
1523
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1524
1525
        foreach ($propertyAnnotations as $key => $annot) {
1526
            if (! is_numeric($key)) {
1527
                continue;
1528
            }
1529
1530
            $propertyAnnotations[get_class($annot)] = $annot;
1531
        }
1532
1533
        return $propertyAnnotations;
1534
    }
1535
1536
    /**
1537
     * @param \ReflectionMethod $reflectionMethod
1538
     *
1539
     * @return array
1540
     */
1541
    private function getMethodAnnotations(\ReflectionMethod $reflectionMethod) : array
1542
    {
1543
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1544
1545
        foreach ($methodAnnotations as $key => $annot) {
1546
            if (! is_numeric($key)) {
1547
                continue;
1548
            }
1549
1550
            $methodAnnotations[get_class($annot)] = $annot;
1551
        }
1552
1553
        return $methodAnnotations;
1554
    }
1555
1556
    /**
1557
     * Factory method for the Annotation Driver.
1558
     *
1559
     * @param array|string          $paths
1560
     * @param AnnotationReader|null $reader
1561
     *
1562
     * @return AnnotationDriver
1563
     */
1564
    static public function create($paths = [], AnnotationReader $reader = null)
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
1565
    {
1566
        if ($reader == null) {
1567
            $reader = new AnnotationReader();
1568
        }
1569
1570
        return new self($reader, $paths);
1571
    }
1572
}
1573