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

convertClassAnnotationsToEmbeddableClassMetadata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 9
ccs 0
cts 4
cp 0
crap 2
rs 9.6666
c 0
b 0
f 0
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 2277
    public function __construct(Reader $reader, $paths = null)
94
    {
95 2277
        $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 2277
        if ($paths) {
97 2193
            $this->addPaths((array) $paths);
98
        }
99 2277
    }
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 365
    public function loadMetadataForClass(
271
        string $className,
272
        Mapping\ClassMetadata $metadata,
273
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
274
    ) : Mapping\ClassMetadata {
275 365
        $reflectionClass = $metadata->getReflectionClass();
276
277 365
        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 365
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
284 365
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
285 365
            $classAnnotations,
286 365
            $reflectionClass,
287 365
            $metadata,
288 365
            $metadataBuildingContext
289
        );
290
291
        // Evaluate @Cache annotation
292 362
        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 362
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
302 362
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
303 76
                continue;
304
            }
305
306 362
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
307 361
            $property            = $this->convertPropertyAnnotationsToProperty(
308 361
                $propertyAnnotations,
309 361
                $reflectionProperty,
310 361
                $classMetadata
311
            );
312
313 361
            if ($classMetadata->isMappedSuperclass &&
314 361
                $property instanceof Mapping\ToManyAssociationMetadata &&
315 361
                ! $property->isOwningSide()) {
316 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
317 1
                    $classMetadata->getClassName(),
318 1
                    $property->getName()
319
                );
320
            }
321
322 360
            if (! $property) {
323 1
                continue;
324
            }
325
326 360
            $metadata->addProperty($property);
327
        }
328
329 359
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
330
331 359
        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 365
    private function convertClassAnnotationsToClassMetadata(
340
        array $classAnnotations,
341
        \ReflectionClass $reflectionClass,
342
        Mapping\ClassMetadata $metadata,
343
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
344
    ) : Mapping\ClassMetadata {
345
        switch (true) {
346 365
            case isset($classAnnotations[Annotation\Entity::class]):
347 361
                return $this->convertClassAnnotationsToEntityClassMetadata(
348 361
                    $classAnnotations,
349 361
                    $reflectionClass,
350 361
                    $metadata,
351 361
                    $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 361
    private function convertClassAnnotationsToEntityClassMetadata(
384
        array $classAnnotations,
385
        \ReflectionClass $reflectionClass,
386
        Mapping\ClassMetadata $metadata,
387
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
388
    ) {
389
        /** @var Annotation\Entity $entityAnnot */
390 361
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
391
392 361
        if ($entityAnnot->repositoryClass !== null) {
393 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
394
        }
395
396 361
        if ($entityAnnot->readOnly) {
397 1
            $metadata->asReadOnly();
398
        }
399
400 361
        $metadata->isMappedSuperclass = false;
401 361
        $metadata->isEmbeddedClass    = false;
402
403 361
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
404
405
        // Evaluate @ChangeTrackingPolicy annotation
406 361
        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 361
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
416 80
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
417
418 80
            $metadata->setInheritanceType(
419 80
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
420
            );
421
422 80
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
423 80
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
424
            }
425
        }
426
427 361
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
428 361
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
429 361
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
430
431 361
        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 361
    private function convertPropertyAnnotationsToProperty(
479
        array $propertyAnnotations,
480
        \ReflectionProperty $reflectionProperty,
481
        Mapping\ClassMetadata $metadata
482
    ) : ?Mapping\Property {
483
        switch (true) {
484 361
            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 357
                return $this->convertReflectionPropertyToFieldMetadata(
486 357
                    $reflectionProperty,
487 357
                    $propertyAnnotations,
488 357
                    $metadata
489
                );
490
491 249
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
492 108
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
493 108
                    $reflectionProperty,
494 108
                    $propertyAnnotations,
495 108
                    $metadata
496
                );
497
498 199
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
499 135
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
500 135
                    $reflectionProperty,
501 135
                    $propertyAnnotations,
502 135
                    $metadata
503
                );
504
505 161
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
506 105
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
507 105
                    $reflectionProperty,
508 105
                    $propertyAnnotations,
509 105
                    $metadata
510
                );
511
512 105
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
513 87
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
514 87
                    $reflectionProperty,
515 87
                    $propertyAnnotations,
516 87
                    $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 357
    private function convertReflectionPropertyToFieldMetadata(
533
        \ReflectionProperty $reflProperty,
534
        array $propertyAnnotations,
535
        Mapping\ClassMetadata $metadata
536
    ) : Mapping\FieldMetadata {
537 357
        $className   = $metadata->getClassName();
538 357
        $fieldName   = $reflProperty->getName();
539 357
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
540 357
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
541
542 357
        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 357
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
547
548
        // Check for Id
549 357
        if (isset($propertyAnnotations[Annotation\Id::class])) {
550 354
            $fieldMetadata->setPrimaryKey(true);
551
        }
552
553
        // Check for GeneratedValue strategy
554 357
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
555 304
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
556 304
            $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 304
            $idGeneratorType     = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
558
559 304
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
560 284
                $idGeneratorDefinition = [];
561
562
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
563
                switch (true) {
564 284
                    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 275
                    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 272
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
586
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
587
                }
588
589 284
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
590
            }
591
        }
592
593 357
        return $fieldMetadata;
594
    }
595
596
    /**
597
     * @param Annotation\Annotation[] $propertyAnnotations
598
     *
599
     * @return Mapping\OneToOneAssociationMetadata
600
     */
601 108
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
602
        \ReflectionProperty $reflectionProperty,
603
        array $propertyAnnotations,
604
        Mapping\ClassMetadata $metadata
605
    ) {
606 108
        $className     = $metadata->getClassName();
607 108
        $fieldName     = $reflectionProperty->getName();
608 108
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
609 108
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
610 108
        $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 108
        $assocMetadata->setTargetEntity($targetEntity);
613 108
        $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 108
        $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 108
        $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 108
        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 38
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
619 38
            $assocMetadata->setOwningSide(false);
620
        }
621
622 108
        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 53
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
624
        }
625
626
        // Check for Id
627 108
        if (isset($propertyAnnotations[Annotation\Id::class])) {
628 9
            $assocMetadata->setPrimaryKey(true);
629
        }
630
631 108
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
632
633
        // Check for JoinColumn/JoinColumns annotations
634
        switch (true) {
635 108
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
636 79
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
637
638 79
                $assocMetadata->addJoinColumn(
639 79
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
640
                );
641
642 79
                break;
643
644 50
            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 108
        return $assocMetadata;
657
    }
658
659
    /**
660
     * @param Annotation\Annotation[] $propertyAnnotations
661
     *
662
     * @return Mapping\ManyToOneAssociationMetadata
663
     */
664 135
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
665
        \ReflectionProperty $reflectionProperty,
666
        array $propertyAnnotations,
667
        Mapping\ClassMetadata $metadata
668
    ) {
669 135
        $className      = $metadata->getClassName();
670 135
        $fieldName      = $reflectionProperty->getName();
671 135
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
672 135
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
673 135
        $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 135
        $assocMetadata->setTargetEntity($targetEntity);
676 135
        $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 135
        $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 135
        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 91
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
681
        }
682
683
        // Check for Id
684 135
        if (isset($propertyAnnotations[Annotation\Id::class])) {
685 32
            $assocMetadata->setPrimaryKey(true);
686
        }
687
688 135
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
689
690
        // Check for JoinColumn/JoinColumns annotations
691
        switch (true) {
692 135
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
693 76
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
694
695 76
                $assocMetadata->addJoinColumn(
696 76
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
697
                );
698
699 76
                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 135
        return $assocMetadata;
714
    }
715
716
    /**
717
     * @param Annotation\Annotation[] $propertyAnnotations
718
     *
719
     * @throws Mapping\MappingException
720
     */
721 105
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
722
        \ReflectionProperty $reflectionProperty,
723
        array $propertyAnnotations,
724
        Mapping\ClassMetadata $metadata
725
    ) : Mapping\OneToManyAssociationMetadata {
726 105
        $className      = $metadata->getClassName();
727 105
        $fieldName      = $reflectionProperty->getName();
728 105
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
729 105
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
730 105
        $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 105
        $assocMetadata->setTargetEntity($targetEntity);
733 105
        $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 105
        $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 105
        $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 105
        $assocMetadata->setOwningSide(false);
737 105
        $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 105
        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 105
        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 105
        if (isset($propertyAnnotations[Annotation\Id::class])) {
752
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
753
        }
754
755 105
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
756
757 105
        return $assocMetadata;
758
    }
759
760
    /**
761
     * @param Annotation\Annotation[] $propertyAnnotations
762
     *
763
     * @throws Mapping\MappingException
764
     */
765 87
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
766
        \ReflectionProperty $reflectionProperty,
767
        array $propertyAnnotations,
768
        Mapping\ClassMetadata $metadata
769
    ) : Mapping\ManyToManyAssociationMetadata {
770 87
        $className       = $metadata->getClassName();
771 87
        $fieldName       = $reflectionProperty->getName();
772 87
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
773 87
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
774 87
        $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 87
        $assocMetadata->setTargetEntity($targetEntity);
777 87
        $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 87
        $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 87
        $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 87
        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 35
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
783 35
            $assocMetadata->setOwningSide(false);
784
        }
785
786 87
        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 44
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
788
        }
789
790 87
        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 87
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
796 70
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
797 70
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
798
799 70
            $assocMetadata->setJoinTable($joinTableMetadata);
800
        }
801
802
        // Check for OrderBy
803 87
        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 87
        if (isset($propertyAnnotations[Annotation\Id::class])) {
811
            throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName);
812
        }
813
814 87
        $this->attachAssociationPropertyCache($propertyAnnotations, $reflectionProperty, $assocMetadata, $metadata);
815
816 87
        return $assocMetadata;
817
    }
818
819
    /**
820
     * Parse the given Column as FieldMetadata
821
     */
822 357
    private function convertColumnAnnotationToFieldMetadata(
823
        Annotation\Column $columnAnnot,
824
        string $fieldName,
825
        bool $isVersioned
826
    ) : Mapping\FieldMetadata {
827 357
        $fieldMetadata = $isVersioned
828 16
            ? new Mapping\VersionFieldMetadata($fieldName)
829 357
            : new Mapping\FieldMetadata($fieldName)
830
        ;
831
832 357
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
833
834 357
        if (! empty($columnAnnot->name)) {
835 74
            $fieldMetadata->setColumnName($columnAnnot->name);
836
        }
837
838 357
        if (! empty($columnAnnot->columnDefinition)) {
839 4
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
840
        }
841
842 357
        if (! empty($columnAnnot->length)) {
843 357
            $fieldMetadata->setLength($columnAnnot->length);
844
        }
845
846 357
        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 357
        $fieldMetadata->setScale($columnAnnot->scale);
851 357
        $fieldMetadata->setPrecision($columnAnnot->precision);
852 357
        $fieldMetadata->setNullable($columnAnnot->nullable);
853 357
        $fieldMetadata->setUnique($columnAnnot->unique);
854
855 357
        return $fieldMetadata;
856
    }
857
858
    /**
859
     * Parse the given Table as TableMetadata
860
     */
861 189
    private function convertTableAnnotationToTableMetadata(
862
        Annotation\Table $tableAnnot,
863
        Mapping\TableMetadata $tableMetadata
864
    ) : void {
865 189
        if (! empty($tableAnnot->name)) {
866 184
            $tableMetadata->setName($tableAnnot->name);
867
        }
868
869 189
        if (! empty($tableAnnot->schema)) {
870 5
            $tableMetadata->setSchema($tableAnnot->schema);
871
        }
872
873 189
        foreach ($tableAnnot->options as $optionName => $optionValue) {
874 4
            $tableMetadata->addOption($optionName, $optionValue);
875
        }
876
877 189
        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 189
        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 189
    }
896
897
    /**
898
     * Parse the given JoinTable as JoinTableMetadata
899
     */
900 70
    private function convertJoinTableAnnotationToJoinTableMetadata(
901
        Annotation\JoinTable $joinTableAnnot
902
    ) : Mapping\JoinTableMetadata {
903 70
        $joinTable = new Mapping\JoinTableMetadata();
904
905 70
        if (! empty($joinTableAnnot->name)) {
906 68
            $joinTable->setName($joinTableAnnot->name);
907
        }
908
909 70
        if (! empty($joinTableAnnot->schema)) {
910
            $joinTable->setSchema($joinTableAnnot->schema);
911
        }
912
913 70
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
914 69
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
915
916 69
            $joinTable->addJoinColumn($joinColumn);
917
        }
918
919 70
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
920 69
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
921
922 69
            $joinTable->addInverseJoinColumn($joinColumn);
923
        }
924
925 70
        return $joinTable;
926
    }
927
928
    /**
929
     * Parse the given JoinColumn as JoinColumnMetadata
930
     */
931 176
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
932
        Annotation\JoinColumn $joinColumnAnnot
933
    ) : Mapping\JoinColumnMetadata {
934 176
        $joinColumn = new Mapping\JoinColumnMetadata();
935
936
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
937 176
        if (! empty($joinColumnAnnot->name)) {
938 169
            $joinColumn->setColumnName($joinColumnAnnot->name);
939
        }
940
941 176
        if (! empty($joinColumnAnnot->referencedColumnName)) {
942 176
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
943
        }
944
945 176
        $joinColumn->setNullable($joinColumnAnnot->nullable);
946 176
        $joinColumn->setUnique($joinColumnAnnot->unique);
947
948 176
        if (! empty($joinColumnAnnot->fieldName)) {
949
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
950
        }
951
952 176
        if (! empty($joinColumnAnnot->columnDefinition)) {
953 3
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
954
        }
955
956 176
        if ($joinColumnAnnot->onDelete) {
957 16
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
958
        }
959
960 176
        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 361
    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 361
        $parent = $metadata->getParent();
1031
1032 361
        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 361
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1048 361
        $tableMetadata  = new Mapping\TableMetadata();
1049
1050 361
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1051
1052
        // Evaluate @Table annotation
1053 361
        if (isset($classAnnotations[Annotation\Table::class])) {
1054 189
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1055
1056 189
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1057
        }
1058
1059 361
        $metadata->setTable($tableMetadata);
1060 361
    }
1061
1062
    /**
1063
     * @param Annotation\Annotation[] $classAnnotations
1064
     *
1065
     * @throws Mapping\MappingException
1066
     */
1067 80
    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 80
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1073
1074 80
        $discriminatorColumn->setTableName($metadata->getTableName());
1075 80
        $discriminatorColumn->setColumnName('dtype');
1076 80
        $discriminatorColumn->setType(Type::getType('string'));
1077 80
        $discriminatorColumn->setLength(255);
1078
1079
        // Evaluate DiscriminatorColumn annotation
1080 80
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1081
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1082 64
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1083 64
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1084 60
                ? $discriminatorColumnAnnotation->type
1085 64
                : 'string';
1086
1087 64
            $discriminatorColumn->setType(Type::getType($typeName));
1088 64
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1089
1090 64
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1091 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1092
            }
1093
1094 64
            if (! empty($discriminatorColumnAnnotation->length)) {
1095 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1096
            }
1097
        }
1098
1099 80
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1100
1101
        // Evaluate DiscriminatorMap annotation
1102 80
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1103 79
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1104 79
            $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 79
            $metadata->setDiscriminatorMap($discriminatorMap);
1107
        }
1108 80
    }
1109
1110
    /**
1111
     * @param Annotation\Annotation[] $classAnnotations
1112
     */
1113 362
    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 362
        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 362
        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 362
    }
1145
1146
    /**
1147
     * @param Annotation\Annotation[] $classAnnotations
1148
     */
1149 362
    private function attachLifecycleCallbacks(
1150
        array $classAnnotations,
1151
        \ReflectionClass $reflectionClass,
1152
        Mapping\ClassMetadata $metadata
1153
    ) : void {
1154
        // Evaluate @HasLifecycleCallbacks annotation
1155 362
        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 362
    }
1164
1165
    /**
1166
     * @param Annotation\Annotation[] $classAnnotations
1167
     *
1168
     * @throws \ReflectionException
1169
     * @throws Mapping\MappingException
1170
     */
1171 362
    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 362
        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 362
    }
1200
1201
    /**
1202
     * @param Annotation\Annotation[] $classAnnotations
1203
     *
1204
     * @throws Mapping\MappingException
1205
     */
1206 359
    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 359
        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 359
        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 359
    }
1276
1277
    /**
1278
     * @param Annotation\Annotation[] $propertyAnnotations
1279
     */
1280 245
    private function attachAssociationPropertyCache(
1281
        array $propertyAnnotations,
1282
        \ReflectionProperty $reflectionProperty,
1283
        Mapping\AssociationMetadata $assocMetadata,
1284
        Mapping\ClassMetadata $metadata
1285
    ) : void {
1286
        // Check for Cache
1287 245
        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 245
    }
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 245
    private function getCascade(string $className, string $fieldName, array $originalCascades)
1311
    {
1312 245
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1313 245
        $cascades     = array_map('strtolower', $originalCascades);
1314
1315 245
        if (in_array('all', $cascades, true)) {
1316 20
            $cascades = $cascadeTypes;
1317
        }
1318
1319 245
        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 245
        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 245
    private function getFetchMode($className, $fetchMode) : string
1339
    {
1340 245
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1341
1342 245
        if (! defined($fetchModeConstant)) {
1343
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1344
        }
1345
1346 245
        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 365
    private function getClassAnnotations(\ReflectionClass $reflectionClass) : array
1384
    {
1385 365
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1386
1387 365
        foreach ($classAnnotations as $key => $annot) {
1388 362
            if (! is_numeric($key)) {
1389
                continue;
1390
            }
1391
1392 362
            $classAnnotations[get_class($annot)] = $annot;
1393
        }
1394
1395 365
        return $classAnnotations;
1396
    }
1397
1398
    /**
1399
     * @return Annotation\Annotation[]
1400
     */
1401 362
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty) : array
1402
    {
1403 362
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1404
1405 361
        foreach ($propertyAnnotations as $key => $annot) {
1406 361
            if (! is_numeric($key)) {
1407
                continue;
1408
            }
1409
1410 361
            $propertyAnnotations[get_class($annot)] = $annot;
1411
        }
1412
1413 361
        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