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

AnnotationDriver::getCascade()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0987

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 4
nop 3
dl 0
loc 16
ccs 7
cts 9
cp 0.7778
crap 3.0987
rs 9.4285
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 2267
    public function __construct(Reader $reader, $paths = null)
94
    {
95 2267
        $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 2267
        if ($paths) {
97 2181
            $this->addPaths((array) $paths);
98
        }
99 2267
    }
100
101
    /**
102
     * Appends lookup paths to metadata driver.
103
     *
104
     * @param string[] $paths
105
     */
106 2185
    public function addPaths(array $paths)
107
    {
108 2185
        $this->paths = array_unique(array_merge($this->paths, $paths));
109 2185
    }
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 367
    public function loadMetadataForClass(
271
        string $className,
272
        Mapping\ClassMetadata $metadata,
273
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
274
    ) : Mapping\ClassMetadata {
275 367
        $reflectionClass = $metadata->getReflectionClass();
276
277 367
        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 367
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
284 367
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
285 367
            $classAnnotations,
286 367
            $reflectionClass,
287 367
            $metadata,
288 367
            $metadataBuildingContext
289
        );
290
291
        // Evaluate @Cache annotation
292 364
        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 364
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
302 364
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
303 76
                continue;
304
            }
305
306 364
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
307 363
            $property            = $this->convertPropertyAnnotationsToProperty(
308 363
                $propertyAnnotations,
309 363
                $reflectionProperty,
310 363
                $classMetadata
311
            );
312
313 363
            if ($classMetadata->isMappedSuperclass &&
314 363
                $property instanceof Mapping\ToManyAssociationMetadata &&
315 363
                ! $property->isOwningSide()) {
316 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
317 1
                    $classMetadata->getClassName(),
318 1
                    $property->getName()
319
                );
320
            }
321
322 362
            if (! $property) {
323 1
                continue;
324
            }
325
326 362
            $metadata->addProperty($property);
327
        }
328
329 361
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
330
331 361
        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 367
    private function convertClassAnnotationsToClassMetadata(
340
        array $classAnnotations,
341
        \ReflectionClass $reflectionClass,
342
        Mapping\ClassMetadata $metadata,
343
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
344
    ) : Mapping\ClassMetadata {
345
        switch (true) {
346 367
            case isset($classAnnotations[Annotation\Entity::class]):
347 363
                return $this->convertClassAnnotationsToEntityClassMetadata(
348 363
                    $classAnnotations,
349 363
                    $reflectionClass,
350 363
                    $metadata,
351 363
                    $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 363
    private function convertClassAnnotationsToEntityClassMetadata(
384
        array $classAnnotations,
385
        \ReflectionClass $reflectionClass,
386
        Mapping\ClassMetadata $metadata,
387
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
388
    ) {
389
        /** @var Annotation\Entity $entityAnnot */
390 363
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
391
392 363
        if ($entityAnnot->repositoryClass !== null) {
393 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
394
        }
395
396 363
        if ($entityAnnot->readOnly) {
397 1
            $metadata->asReadOnly();
398
        }
399
400 363
        $metadata->isMappedSuperclass = false;
401 363
        $metadata->isEmbeddedClass    = false;
402
403 363
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
404
405
        // Evaluate @ChangeTrackingPolicy annotation
406 363
        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 363
        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 363
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
428 363
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
429
430 363
        return $metadata;
431
    }
432
433
    /**
434
     * @param Annotation\Annotation[] $classAnnotations
435
     */
436 24
    private function convertClassAnnotationsToMappedSuperClassMetadata(
437
        array $classAnnotations,
438
        \ReflectionClass $reflectionClass,
439
        Mapping\ClassMetadata $metadata
440
    ) : Mapping\ClassMetadata {
441
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
442 24
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
443
444 24
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
445 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
446
        }
447
448 24
        $metadata->isMappedSuperclass = true;
449 24
        $metadata->isEmbeddedClass    = false;
450
451 24
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
452 24
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
453
454 24
        return $metadata;
455
    }
456
457
    /**
458
     * @param Annotation\Annotation[] $classAnnotations
459
     */
460
    private function convertClassAnnotationsToEmbeddableClassMetadata(
461
        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

461
        /** @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...
462
        \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

462
        /** @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...
463
        Mapping\ClassMetadata $metadata
464
    ) : Mapping\ClassMetadata {
465
        $metadata->isMappedSuperclass = false;
466
        $metadata->isEmbeddedClass    = true;
467
468
        return $metadata;
469
    }
470
471
    /**
472
     * @param Annotation\Annotation[] $propertyAnnotations
473
     *
474
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
475
     */
476 363
    private function convertPropertyAnnotationsToProperty(
477
        array $propertyAnnotations,
478
        \ReflectionProperty $reflectionProperty,
479
        Mapping\ClassMetadata $metadata
480
    ) : ?Mapping\Property {
481
        switch (true) {
482 363
            case isset($propertyAnnotations[Annotation\Column::class]):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

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

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
984
    {
985
        $entities = [];
986
987
        foreach ($resultSetMapping->entities as $entityResultAnnot) {
988
            $entityResult = [
989
                'fields'                => [],
990
                'entityClass'           => $entityResultAnnot->entityClass,
991
                'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
992
            ];
993
994
            foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
995
                $entityResult['fields'][] = [
996
                    'name'      => $fieldResultAnnot->name,
997
                    'column'    => $fieldResultAnnot->column,
998
                ];
999
            }
1000
1001
            $entities[] = $entityResult;
1002
        }
1003
1004
        $columns = [];
1005
1006
        foreach ($resultSetMapping->columns as $columnResultAnnot) {
1007
            $columns[] = [
1008
                'name' => $columnResultAnnot->name,
1009
            ];
1010
        }
1011
1012
        return [
1013
            'name'     => $resultSetMapping->name,
1014
            'entities' => $entities,
1015
            'columns'  => $columns,
1016
        ];
1017
    }
1018
1019
    /**
1020
     * @param Annotation\Annotation[] $classAnnotations
1021
     */
1022 363
    private function attachTable(
1023
        array $classAnnotations,
1024
        \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

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

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

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

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