Failed Conditions
Pull Request — master (#7085)
by Guilherme
11:03
created

AnnotationDriver   F

Complexity

Total Complexity 175

Size/Duplication

Total Lines 1407
Duplicated Lines 0 %

Test Coverage

Coverage 91.85%

Importance

Changes 0
Metric Value
dl 0
loc 1407
ccs 530
cts 577
cp 0.9185
rs 0.6314
c 0
b 0
f 0
wmc 175

41 Methods

Rating   Name   Duplication   Size   Complexity  
A getCascade() 0 16 3
C getAllClassNames() 0 62 12
B convertTableAnnotationToTableMetadata() 0 32 6
B convertReflectionPropertyToManyToOneAssociationMetadata() 0 50 6
A attachAssociationPropertyCache() 0 16 2
A create() 0 7 2
B convertJoinTableAnnotationToJoinTableMetadata() 0 26 5
A addPaths() 0 3 1
A setFileExtension() 0 3 1
A isTransient() 0 10 3
A getExcludePaths() 0 3 1
A getPaths() 0 3 1
A convertClassAnnotationsToEmbeddableClassMetadata() 0 9 1
B convertSqlResultSetMapping() 0 33 4
B convertReflectionPropertyToOneToManyAssociationMetadata() 0 37 4
A getReader() 0 3 1
A convertClassAnnotationsToMappedSuperClassMetadata() 0 20 2
B convertJoinColumnAnnotationToJoinColumnMetadata() 0 30 6
B convertClassAnnotationsToEntityClassMetadata() 0 49 6
C convertPropertyAnnotationsToProperty() 0 46 7
B attachEntityListeners() 0 24 6
C convertReflectionPropertyToFieldMetadata() 0 62 8
A convertCacheAnnotationToCacheMetadata() 0 12 3
B convertColumnAnnotationToFieldMetadata() 0 34 6
A __construct() 0 5 2
A getFileExtension() 0 3 1
B convertReflectionPropertyToManyToManyAssociationMetadata() 0 52 7
B convertClassAnnotationsToClassMetadata() 0 33 4
B attachTable() 0 36 6
C attachPropertyOverrides() 0 67 11
A attachLifecycleCallbacks() 0 11 4
A getPropertyAnnotations() 0 13 3
A getClassAnnotations() 0 13 3
A addExcludePaths() 0 3 1
B attachDiscriminatorColumn() 0 40 6
A getMethodAnnotations() 0 13 3
B attachNamedNativeQueries() 0 29 5
C loadMetadataForClass() 0 62 9
B convertReflectionPropertyToOneToOneAssociationMetadata() 0 56 7
A getFetchMode() 0 9 2
B getMethodCallbacks() 0 24 4

How to fix   Complexity   

Complex Class

Complex classes like AnnotationDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationDriver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Doctrine\Common\Annotations\Reader;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Annotation;
11
use Doctrine\ORM\Cache\CacheException;
12
use Doctrine\ORM\Events;
13
use Doctrine\ORM\Mapping;
14
use function array_diff;
15
use function array_intersect;
16
use function array_map;
17
use function array_merge;
18
use function array_unique;
19
use function class_exists;
20
use function constant;
21
use function count;
22
use function defined;
23
use function get_class;
24
use function get_declared_classes;
25
use function in_array;
26
use function is_dir;
27
use function is_numeric;
28
use function preg_match;
29
use function preg_quote;
30
use function realpath;
31
use function sprintf;
32
use function str_replace;
33
use function strpos;
34
use function strtolower;
35
use function strtoupper;
36
37
/**
38
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
39
 *
40
 */
41
class AnnotationDriver implements MappingDriver
42
{
43
    /**
44
     * @var int[]
45
     */
46
    protected $entityAnnotationClasses = [
47
        Annotation\Entity::class           => 1,
48
        Annotation\MappedSuperclass::class => 2,
49
    ];
50
51
    /**
52
     * The AnnotationReader.
53
     *
54
     * @var AnnotationReader
55
     */
56
    protected $reader;
57
58
    /**
59
     * The paths where to look for mapping files.
60
     *
61
     * @var string[]
62
     */
63
    protected $paths = [];
64
65
    /**
66
     * The paths excluded from path where to look for mapping files.
67
     *
68
     * @var string[]
69
     */
70
    protected $excludePaths = [];
71
72
    /**
73
     * The file extension of mapping documents.
74
     *
75
     * @var string
76
     */
77
    protected $fileExtension = '.php';
78
79
    /**
80
     * Cache for AnnotationDriver#getAllClassNames().
81
     *
82
     * @var string[]|null
83
     */
84
    protected $classNames;
85
86
    /**
87
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
88
     * docblock annotations.
89
     *
90
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
91
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
92
     */
93 2276
    public function __construct(Reader $reader, $paths = null)
94
    {
95 2276
        $this->reader = $reader;
0 ignored issues
show
Documentation Bug introduced by
$reader is of type Doctrine\Common\Annotations\Reader, but the property $reader was declared to be of type Doctrine\Common\Annotations\AnnotationReader. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
96 2276
        if ($paths) {
97 2193
            $this->addPaths((array) $paths);
98
        }
99 2276
    }
100
101
    /**
102
     * Appends lookup paths to metadata driver.
103
     *
104
     * @param string[] $paths
105
     */
106 2197
    public function addPaths(array $paths)
107
    {
108 2197
        $this->paths = array_unique(array_merge($this->paths, $paths));
109 2197
    }
110
111
    /**
112
     * Retrieves the defined metadata lookup paths.
113
     *
114
     * @return string[]
115
     */
116
    public function getPaths()
117
    {
118
        return $this->paths;
119
    }
120
121
    /**
122
     * Append exclude lookup paths to metadata driver.
123
     *
124
     * @param string[] $paths
125
     */
126
    public function addExcludePaths(array $paths)
127
    {
128
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
129
    }
130
131
    /**
132
     * Retrieve the defined metadata lookup exclude paths.
133
     *
134
     * @return string[]
135
     */
136
    public function getExcludePaths()
137
    {
138
        return $this->excludePaths;
139
    }
140
141
    /**
142
     * Retrieve the current annotation reader
143
     *
144
     * @return AnnotationReader
145
     */
146 1
    public function getReader()
147
    {
148 1
        return $this->reader;
149
    }
150
151
    /**
152
     * Gets the file extension used to look for mapping files under.
153
     *
154
     * @return string
155
     */
156
    public function getFileExtension()
157
    {
158
        return $this->fileExtension;
159
    }
160
161
    /**
162
     * Sets the file extension used to look for mapping files under.
163
     *
164
     * @param string $fileExtension The file extension to set.
165
     *
166
     */
167
    public function setFileExtension($fileExtension)
168
    {
169
        $this->fileExtension = $fileExtension;
170
    }
171
172
    /**
173
     * Returns whether the class with the specified name is transient. Only non-transient
174
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
175
     *
176
     * A class is non-transient if it is annotated with an annotation
177
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
178
     *
179
     * @param string $className
180
     *
181
     * @return bool
182
     */
183 193
    public function isTransient($className)
184
    {
185 193
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
186
187 193
        foreach ($classAnnotations as $annot) {
188 188
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
189 188
                return false;
190
            }
191
        }
192 12
        return true;
193
    }
194
195
    /**
196
     * {@inheritDoc}
197
     */
198 61
    public function getAllClassNames()
199
    {
200 61
        if ($this->classNames !== null) {
201 46
            return $this->classNames;
202
        }
203
204 61
        if (! $this->paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
205
            throw Mapping\MappingException::pathRequired();
206
        }
207
208 61
        $classes       = [];
209 61
        $includedFiles = [];
210
211 61
        foreach ($this->paths as $path) {
212 61
            if (! is_dir($path)) {
213
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
214
            }
215
216 61
            $iterator = new \RegexIterator(
217 61
                new \RecursiveIteratorIterator(
218 61
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
219 61
                    \RecursiveIteratorIterator::LEAVES_ONLY
220
                ),
221 61
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
222 61
                \RecursiveRegexIterator::GET_MATCH
223
            );
224
225 61
            foreach ($iterator as $file) {
226 61
                $sourceFile = $file[0];
227
228 61
                if (! preg_match('(^phar:)i', $sourceFile)) {
229 61
                    $sourceFile = realpath($sourceFile);
230
                }
231
232 61
                foreach ($this->excludePaths as $excludePath) {
233
                    $exclude = str_replace('\\', '/', realpath($excludePath));
234
                    $current = str_replace('\\', '/', $sourceFile);
235
236
                    if (strpos($current, $exclude) !== false) {
237
                        continue 2;
238
                    }
239
                }
240
241 61
                require_once $sourceFile;
242
243 61
                $includedFiles[] = $sourceFile;
244
            }
245
        }
246
247 61
        $declared = get_declared_classes();
248
249 61
        foreach ($declared as $className) {
250 61
            $rc         = new \ReflectionClass($className);
251 61
            $sourceFile = $rc->getFileName();
252 61
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
253 61
                $classes[] = $className;
254
            }
255
        }
256
257 61
        $this->classNames = $classes;
258
259 61
        return $classes;
260
    }
261
262
    /**
263
     * {@inheritDoc}
264
     *
265
     * @throws CacheException
266
     * @throws Mapping\MappingException
267
     * @throws \ReflectionException
268
     * @throws \RuntimeException
269
     */
270 364
    public function loadMetadataForClass(
271
        string $className,
272
        Mapping\ClassMetadata $metadata,
273
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
274
    ) : Mapping\ClassMetadata {
275 364
        $reflectionClass = $metadata->getReflectionClass();
276
277 364
        if (! $reflectionClass) {
278
            // this happens when running annotation driver in combination with
279
            // static reflection services. This is not the nicest fix
280
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
281
        }
282
283 364
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
284 364
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
285 364
            $classAnnotations,
286 364
            $reflectionClass,
287 364
            $metadata,
288 364
            $metadataBuildingContext
289
        );
290
291
        // Evaluate @Cache annotation
292 361
        if (isset($classAnnotations[Annotation\Cache::class])) {
293 18
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
294 18
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
295
296 18
            $classMetadata->setCache($cache);
297
        }
298
299
        // Evaluate annotations on properties/fields
300
        /* @var $reflProperty \ReflectionProperty */
301 361
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
302 361
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
303 75
                continue;
304
            }
305
306 361
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
307 360
            $property            = $this->convertPropertyAnnotationsToProperty(
308 360
                $propertyAnnotations,
309 360
                $reflectionProperty,
310 360
                $classMetadata
311
            );
312
313 360
            if ($classMetadata->isMappedSuperclass &&
314 360
                $property instanceof Mapping\ToManyAssociationMetadata &&
315 360
                ! $property->isOwningSide()) {
316 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
317 1
                    $classMetadata->getClassName(),
318 1
                    $property->getName()
319
                );
320
            }
321
322 359
            if (! $property) {
323 1
                continue;
324
            }
325
326 359
            $metadata->addProperty($property);
327
        }
328
329 358
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
330
331 358
        return $classMetadata;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $classMetadata returns the type Doctrine\ORM\Mapping\ClassMetadata which is incompatible with the return type mandated by Doctrine\ORM\Mapping\Dri...:loadMetadataForClass() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
332
    }
333
334
    /**
335
     * @param Annotation\Annotation[] $classAnnotations
336
     *
337
     * @throws Mapping\MappingException
338
     */
339 364
    private function convertClassAnnotationsToClassMetadata(
340
        array $classAnnotations,
341
        \ReflectionClass $reflectionClass,
342
        Mapping\ClassMetadata $metadata,
343
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
344
    ) : Mapping\ClassMetadata {
345
        switch (true) {
346 364
            case isset($classAnnotations[Annotation\Entity::class]):
347 360
                return $this->convertClassAnnotationsToEntityClassMetadata(
348 360
                    $classAnnotations,
349 360
                    $reflectionClass,
350 360
                    $metadata,
351 360
                    $metadataBuildingContext
352
                );
353
354
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

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

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

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

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

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

463
        /** @scrutinizer ignore-unused */ array $classAnnotations,

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

Loading history...
464
        \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

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

1026
        /** @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...
1027
        Mapping\ClassMetadata $metadata,
1028
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
1029
    ) : void {
1030 360
        $parent = $metadata->getParent();
1031
1032 360
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
1033
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
1034
            do {
1035 29
                if (! $parent->isMappedSuperclass) {
1036 29
                    $metadata->setTable($parent->table);
1037
1038 29
                    break;
1039
                }
1040
1041 4
                $parent = $parent->getParent();
1042 4
            } while ($parent !== null);
1043
1044 29
            return;
1045
        }
1046
1047 360
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1048 360
        $tableMetadata  = new Mapping\TableMetadata();
1049
1050 360
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1051
1052
        // Evaluate @Table annotation
1053 360
        if (isset($classAnnotations[Annotation\Table::class])) {
1054 188
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1055
1056 188
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1057
        }
1058
1059 360
        $metadata->setTable($tableMetadata);
1060 360
    }
1061
1062
    /**
1063
     * @param Annotation\Annotation[] $classAnnotations
1064
     *
1065
     * @throws Mapping\MappingException
1066
     */
1067 79
    private function attachDiscriminatorColumn(
1068
        array $classAnnotations,
1069
        \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

1069
        /** @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...
1070
        Mapping\ClassMetadata $metadata
1071
    ) : void {
1072 79
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1073
1074 79
        $discriminatorColumn->setTableName($metadata->getTableName());
1075 79
        $discriminatorColumn->setColumnName('dtype');
1076 79
        $discriminatorColumn->setType(Type::getType('string'));
1077 79
        $discriminatorColumn->setLength(255);
1078
1079
        // Evaluate DiscriminatorColumn annotation
1080 79
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1081
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1082 63
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1083 63
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1084 59
                ? $discriminatorColumnAnnotation->type
1085 63
                : 'string';
1086
1087 63
            $discriminatorColumn->setType(Type::getType($typeName));
1088 63
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1089
1090 63
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1091 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1092
            }
1093
1094 63
            if (! empty($discriminatorColumnAnnotation->length)) {
1095 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1096
            }
1097
        }
1098
1099 79
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1100
1101
        // Evaluate DiscriminatorMap annotation
1102 79
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1103 78
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1104 78
            $discriminatorMap           = $discriminatorMapAnnotation->value;
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1105
1106 78
            $metadata->setDiscriminatorMap($discriminatorMap);
1107
        }
1108 79
    }
1109
1110
    /**
1111
     * @param Annotation\Annotation[] $classAnnotations
1112
     */
1113 361
    private function attachNamedNativeQueries(
1114
        array $classAnnotations,
1115
        \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

1115
        /** @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...
1116
        Mapping\ClassMetadata $metadata
1117
    ) : void {
1118
        // Evaluate @NamedNativeQueries annotation
1119 361
        if (isset($classAnnotations[Annotation\NamedNativeQueries::class])) {
1120 15
            $namedNativeQueriesAnnot = $classAnnotations[Annotation\NamedNativeQueries::class];
1121
1122 15
            foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
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...
1123 15
                $metadata->addNamedNativeQuery(
1124 15
                    $namedNativeQuery->name,
1125 15
                    $namedNativeQuery->query,
1126
                    [
1127 15
                        'resultClass'      => $namedNativeQuery->resultClass,
1128 15
                        'resultSetMapping' => $namedNativeQuery->resultSetMapping,
1129
                    ]
1130
                );
1131
            }
1132
        }
1133
1134
        // Evaluate @SqlResultSetMappings annotation
1135 361
        if (isset($classAnnotations[Annotation\SqlResultSetMappings::class])) {
1136 15
            $sqlResultSetMappingsAnnot = $classAnnotations[Annotation\SqlResultSetMappings::class];
1137
1138 15
            foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
1139 15
                $sqlResultSetMapping = $this->convertSqlResultSetMapping($resultSetMapping);
1140
1141 15
                $metadata->addSqlResultSetMapping($sqlResultSetMapping);
1142
            }
1143
        }
1144 361
    }
1145
1146
    /**
1147
     * @param Annotation\Annotation[] $classAnnotations
1148
     */
1149 361
    private function attachLifecycleCallbacks(
1150
        array $classAnnotations,
1151
        \ReflectionClass $reflectionClass,
1152
        Mapping\ClassMetadata $metadata
1153
    ) : void {
1154
        // Evaluate @HasLifecycleCallbacks annotation
1155 361
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1156
            /* @var $method \ReflectionMethod */
1157 15
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1158 14
                foreach ($this->getMethodCallbacks($method) as $callback) {
1159 14
                    $metadata->addLifecycleCallback($method->getName(), $callback);
1160
                }
1161
            }
1162
        }
1163 361
    }
1164
1165
    /**
1166
     * @param Annotation\Annotation[] $classAnnotations
1167
     *
1168
     * @throws \ReflectionException
1169
     * @throws Mapping\MappingException
1170
     */
1171 361
    private function attachEntityListeners(
1172
        array $classAnnotations,
1173
        \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

1173
        /** @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...
1174
        Mapping\ClassMetadata $metadata
1175
    ) : void {
1176
        // Evaluate @EntityListeners annotation
1177 361
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1178
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1179 10
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1180
1181 10
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1182 10
                if (! class_exists($listenerClassName)) {
1183
                    throw Mapping\MappingException::entityListenerClassNotFound(
1184
                        $listenerClassName,
1185
                        $metadata->getClassName()
1186
                    );
1187
                }
1188
1189 10
                $listenerClass = new \ReflectionClass($listenerClassName);
1190
1191
                /* @var $method \ReflectionMethod */
1192 10
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1193 10
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1194 10
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
1195
                    }
1196
                }
1197
            }
1198
        }
1199 361
    }
1200
1201
    /**
1202
     * @param Annotation\Annotation[] $classAnnotations
1203
     *
1204
     * @throws Mapping\MappingException
1205
     */
1206 358
    private function attachPropertyOverrides(
1207
        array $classAnnotations,
1208
        \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

1208
        /** @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...
1209
        Mapping\ClassMetadata $metadata
1210
    ) : void {
1211
        // Evaluate AssociationOverrides annotation
1212 358
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1213 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1214
1215 5
            foreach ($associationOverridesAnnot->value as $associationOverride) {
0 ignored issues
show
Bug introduced by
Accessing value on the interface Doctrine\ORM\Annotation\Annotation suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1216 5
                $fieldName = $associationOverride->name;
1217 5
                $property  = $metadata->getProperty($fieldName);
1218
1219 5
                if (! $property) {
1220
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1221
                }
1222
1223 5
                $existingClass = get_class($property);
1224 5
                $override      = new $existingClass($fieldName);
1225
1226
                // Check for JoinColumn/JoinColumns annotations
1227 5
                if ($associationOverride->joinColumns) {
1228 3
                    $joinColumns = [];
1229
1230 3
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1231 3
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1232
                    }
1233
1234 3
                    $override->setJoinColumns($joinColumns);
1235
                }
1236
1237
                // Check for JoinTable annotations
1238 5
                if ($associationOverride->joinTable) {
1239 2
                    $joinTableAnnot    = $associationOverride->joinTable;
1240 2
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1241
1242 2
                    $override->setJoinTable($joinTableMetadata);
1243
                }
1244
1245
                // Check for inversedBy
1246 5
                if ($associationOverride->inversedBy) {
1247 1
                    $override->setInversedBy($associationOverride->inversedBy);
1248
                }
1249
1250
                // Check for fetch
1251 5
                if ($associationOverride->fetch) {
1252 1
                    $override->setFetchMode(
1253 1
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1254
                    );
1255
                }
1256
1257 5
                $metadata->setPropertyOverride($override);
1258
            }
1259
        }
1260
1261
        // Evaluate AttributeOverrides annotation
1262 358
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1263 3
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1264
1265 3
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1266 3
                $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1267 3
                    $attributeOverrideAnnot->column,
1268 3
                    $attributeOverrideAnnot->name,
1269 3
                    false
1270
                );
1271
1272 3
                $metadata->setPropertyOverride($fieldMetadata);
1273
            }
1274
        }
1275 358
    }
1276
1277
    /**
1278
     * @param Annotation\Annotation[] $propertyAnnotations
1279
     */
1280 244
    private function attachAssociationPropertyCache(
1281
        array $propertyAnnotations,
1282
        \ReflectionProperty $reflectionProperty,
1283
        Mapping\AssociationMetadata $assocMetadata,
1284
        Mapping\ClassMetadata $metadata
1285
    ) : void {
1286
        // Check for Cache
1287 244
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
1288 14
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
1289 14
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata(
1290 14
                $cacheAnnot,
1291 14
                $metadata,
1292 14
                $reflectionProperty->getName()
1293
            );
1294
1295 14
            $assocMetadata->setCache($cacheMetadata);
1296
        }
1297 244
    }
1298
1299
    /**
1300
     * Attempts to resolve the cascade modes.
1301
     *
1302
     * @param string   $className        The class name.
1303
     * @param string   $fieldName        The field name.
1304
     * @param string[] $originalCascades The original unprocessed field cascades.
1305
     *
1306
     * @return string[] The processed field cascades.
1307
     *
1308
     * @throws Mapping\MappingException If a cascade option is not valid.
1309
     */
1310 244
    private function getCascade(string $className, string $fieldName, array $originalCascades)
1311
    {
1312 244
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1313 244
        $cascades     = array_map('strtolower', $originalCascades);
1314
1315 244
        if (in_array('all', $cascades, true)) {
1316 19
            $cascades = $cascadeTypes;
1317
        }
1318
1319 244
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1320
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1321
1322
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1323
        }
1324
1325 244
        return $cascades;
1326
    }
1327
1328
    /**
1329
     * Attempts to resolve the fetch mode.
1330
     *
1331
     * @param string $className The class name.
1332
     * @param string $fetchMode The fetch mode.
1333
     *
1334
     * @return string The fetch mode as defined in ClassMetadata.
1335
     *
1336
     * @throws Mapping\MappingException If the fetch mode is not valid.
1337
     */
1338 244
    private function getFetchMode($className, $fetchMode) : string
1339
    {
1340 244
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1341
1342 244
        if (! defined($fetchModeConstant)) {
1343
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1344
        }
1345
1346 244
        return constant($fetchModeConstant);
1347
    }
1348
1349
    /**
1350
     * Parses the given method.
1351
     *
1352
     * @return string[]
1353
     */
1354 24
    private function getMethodCallbacks(\ReflectionMethod $method) : array
1355
    {
1356 24
        $annotations = $this->getMethodAnnotations($method);
1357
        $events      = [
1358 24
            Events::prePersist  => Annotation\PrePersist::class,
1359 24
            Events::postPersist => Annotation\PostPersist::class,
1360 24
            Events::preUpdate   => Annotation\PreUpdate::class,
1361 24
            Events::postUpdate  => Annotation\PostUpdate::class,
1362 24
            Events::preRemove   => Annotation\PreRemove::class,
1363 24
            Events::postRemove  => Annotation\PostRemove::class,
1364 24
            Events::postLoad    => Annotation\PostLoad::class,
1365 24
            Events::preFlush    => Annotation\PreFlush::class,
1366
        ];
1367
1368
        // Check for callbacks
1369 24
        $callbacks = [];
1370
1371 24
        foreach ($events as $eventName => $annotationClassName) {
1372 24
            if (isset($annotations[$annotationClassName]) || $method->getName() === $eventName) {
1373 24
                $callbacks[] = $eventName;
1374
            }
1375
        }
1376
1377 24
        return $callbacks;
1378
    }
1379
1380
    /**
1381
     * @return Annotation\Annotation[]
1382
     */
1383 364
    private function getClassAnnotations(\ReflectionClass $reflectionClass) : array
1384
    {
1385 364
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1386
1387 364
        foreach ($classAnnotations as $key => $annot) {
1388 361
            if (! is_numeric($key)) {
1389
                continue;
1390
            }
1391
1392 361
            $classAnnotations[get_class($annot)] = $annot;
1393
        }
1394
1395 364
        return $classAnnotations;
1396
    }
1397
1398
    /**
1399
     * @return Annotation\Annotation[]
1400
     */
1401 361
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty) : array
1402
    {
1403 361
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1404
1405 360
        foreach ($propertyAnnotations as $key => $annot) {
1406 360
            if (! is_numeric($key)) {
1407
                continue;
1408
            }
1409
1410 360
            $propertyAnnotations[get_class($annot)] = $annot;
1411
        }
1412
1413 360
        return $propertyAnnotations;
1414
    }
1415
1416
    /**
1417
     * @return Annotation\Annotation[]
1418
     */
1419 24
    private function getMethodAnnotations(\ReflectionMethod $reflectionMethod) : array
1420
    {
1421 24
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1422
1423 24
        foreach ($methodAnnotations as $key => $annot) {
1424 18
            if (! is_numeric($key)) {
1425
                continue;
1426
            }
1427
1428 18
            $methodAnnotations[get_class($annot)] = $annot;
1429
        }
1430
1431 24
        return $methodAnnotations;
1432
    }
1433
1434
    /**
1435
     * Factory method for the Annotation Driver.
1436
     *
1437
     * @param string|string[] $paths
1438
     *
1439
     * @return AnnotationDriver
1440
     */
1441
    public static function create($paths = [], ?AnnotationReader $reader = null)
1442
    {
1443
        if ($reader === null) {
1444
            $reader = new AnnotationReader();
1445
        }
1446
1447
        return new self($reader, $paths);
1448
    }
1449
}
1450