Failed Conditions
Push — develop ( 9800ca...d1cf9a )
by Marco
272:19 queued 264:21
created

convertReflectionPropertyToOneToManyAssociationMetadata()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 40
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5.0026

Importance

Changes 0
Metric Value
cc 5
eloc 20
nc 16
nop 3
dl 0
loc 40
ccs 20
cts 21
cp 0.9524
crap 5.0026
rs 8.439
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\Events;
12
use Doctrine\ORM\Mapping;
13
14
/**
15
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
16
 *
17
 * @since 2.0
18
 * @author Benjamin Eberlei <[email protected]>
19
 * @author Guilherme Blanco <[email protected]>
20
 * @author Jonathan H. Wage <[email protected]>
21
 * @author Roman Borschel <[email protected]>
22
 */
23
class AnnotationDriver implements MappingDriver
24
{
25
    /**
26
     * {@inheritDoc}
27
     */
28
    protected $entityAnnotationClasses = [
29
        Annotation\Entity::class           => 1,
30
        Annotation\MappedSuperclass::class => 2,
31
    ];
32
33
    /**
34
     * The AnnotationReader.
35
     *
36
     * @var AnnotationReader
37
     */
38
    protected $reader;
39
40
    /**
41
     * The paths where to look for mapping files.
42
     *
43
     * @var array
44
     */
45
    protected $paths = [];
46
47
    /**
48
     * The paths excluded from path where to look for mapping files.
49
     *
50
     * @var array
51
     */
52
    protected $excludePaths = [];
53
54
    /**
55
     * The file extension of mapping documents.
56
     *
57
     * @var string
58
     */
59
    protected $fileExtension = '.php';
60
61
    /**
62
     * Cache for AnnotationDriver#getAllClassNames().
63
     *
64
     * @var array|null
65
     */
66
    protected $classNames;
67
68
    /**
69
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
70
     * docblock annotations.
71
     *
72
     * @param Reader            $reader The AnnotationReader to use, duck-typed.
73
     * @param string|array|null $paths  One or multiple paths where mapping classes can be found.
74
     */
75 2291
    public function __construct(Reader $reader, $paths = null)
76
    {
77 2291
        $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...
78 2291
        if ($paths) {
79 2206
            $this->addPaths((array) $paths);
80
        }
81 2291
    }
82
83
    /**
84
     * Appends lookup paths to metadata driver.
85
     *
86
     * @param array $paths
87
     *
88
     * @return void
89
     */
90 2210
    public function addPaths(array $paths)
91
    {
92 2210
        $this->paths = array_unique(array_merge($this->paths, $paths));
93 2210
    }
94
95
    /**
96
     * Retrieves the defined metadata lookup paths.
97
     *
98
     * @return array
99
     */
100
    public function getPaths()
101
    {
102
        return $this->paths;
103
    }
104
105
    /**
106
     * Append exclude lookup paths to metadata driver.
107
     *
108
     * @param array $paths
109
     */
110
    public function addExcludePaths(array $paths)
111
    {
112
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
113
    }
114
115
    /**
116
     * Retrieve the defined metadata lookup exclude paths.
117
     *
118
     * @return array
119
     */
120
    public function getExcludePaths()
121
    {
122
        return $this->excludePaths;
123
    }
124
125
    /**
126
     * Retrieve the current annotation reader
127
     *
128
     * @return AnnotationReader
129
     */
130 1
    public function getReader()
131
    {
132 1
        return $this->reader;
133
    }
134
135
    /**
136
     * Gets the file extension used to look for mapping files under.
137
     *
138
     * @return string
139
     */
140
    public function getFileExtension()
141
    {
142
        return $this->fileExtension;
143
    }
144
145
    /**
146
     * Sets the file extension used to look for mapping files under.
147
     *
148
     * @param string $fileExtension The file extension to set.
149
     *
150
     * @return void
151
     */
152
    public function setFileExtension($fileExtension)
153
    {
154
        $this->fileExtension = $fileExtension;
155
    }
156
157
    /**
158
     * Returns whether the class with the specified name is transient. Only non-transient
159
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
160
     *
161
     * A class is non-transient if it is annotated with an annotation
162
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
163
     *
164
     * @param string $className
165
     *
166
     * @return boolean
167
     */
168 193
    public function isTransient($className)
169
    {
170 193
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
171
172 193
        foreach ($classAnnotations as $annot) {
173 188
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
174 188
                return false;
175
            }
176
        }
177 12
        return true;
178
    }
179
180
    /**
181
     * {@inheritDoc}
182
     */
183 61
    public function getAllClassNames()
184
    {
185 61
        if ($this->classNames !== null) {
186 46
            return $this->classNames;
187
        }
188
189 61
        if (!$this->paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
190
            throw Mapping\MappingException::pathRequired();
191
        }
192
193 61
        $classes = [];
194 61
        $includedFiles = [];
195
196 61
        foreach ($this->paths as $path) {
197 61
            if ( ! is_dir($path)) {
198
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
199
            }
200
201 61
            $iterator = new \RegexIterator(
202 61
                new \RecursiveIteratorIterator(
203 61
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
204 61
                    \RecursiveIteratorIterator::LEAVES_ONLY
205
                ),
206 61
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
207 61
                \RecursiveRegexIterator::GET_MATCH
208
            );
209
210 61
            foreach ($iterator as $file) {
211 61
                $sourceFile = $file[0];
212
213 61
                if ( ! preg_match('(^phar:)i', $sourceFile)) {
214 61
                    $sourceFile = realpath($sourceFile);
215
                }
216
217 61
                foreach ($this->excludePaths as $excludePath) {
218
                    $exclude = str_replace('\\', '/', realpath($excludePath));
219
                    $current = str_replace('\\', '/', $sourceFile);
220
221
                    if (strpos($current, $exclude) !== false) {
222
                        continue 2;
223
                    }
224
                }
225
226 61
                require_once $sourceFile;
227
228 61
                $includedFiles[] = $sourceFile;
229
            }
230
        }
231
232 61
        $declared = get_declared_classes();
233
234 61
        foreach ($declared as $className) {
235 61
            $rc = new \ReflectionClass($className);
236 61
            $sourceFile = $rc->getFileName();
237 61
            if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
238 61
                $classes[] = $className;
239
            }
240
        }
241
242 61
        $this->classNames = $classes;
243
244 61
        return $classes;
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250 367
    public function loadMetadataForClass(
251
        string $className,
252
        Mapping\ClassMetadata $metadata,
253
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
254
    ) : Mapping\ClassMetadata
255
    {
256 367
        $reflectionClass = $metadata->getReflectionClass();
257
258 367
        if (! $reflectionClass) {
259
            // this happens when running annotation driver in combination with
260
            // static reflection services. This is not the nicest fix
261
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
262
        }
263
264 367
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
265 367
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
266 367
            $classAnnotations,
267 367
            $reflectionClass,
268 367
            $metadata,
269 367
            $metadataBuildingContext
270
        );
271
272
        // Evaluate @Cache annotation
273 364
        if (isset($classAnnotations[Annotation\Cache::class])) {
274 17
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
275 17
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
276
277 17
            $classMetadata->setCache($cache);
278
        }
279
280
        // Evaluate annotations on properties/fields
281
        /* @var $reflProperty \ReflectionProperty */
282 364
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
283 364
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
284 75
                continue;
285
            }
286
287 364
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
288 363
            $property            = $this->convertPropertyAnnotationsToProperty(
289 363
                $propertyAnnotations,
290 363
                $reflectionProperty,
291 363
                $classMetadata
292
            );
293
294 363
            if (! $property) {
295 4
                continue;
296
            }
297
298 363
            $metadata->addProperty($property);
299
        }
300
301 362
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
302
303 362
        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...
304
    }
305
306
    /**
307
     * @param array                                $classAnnotations
308
     * @param \ReflectionClass                     $reflectionClass
309
     * @param Mapping\ClassMetadata                $metadata
310
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
311
     *
312
     * @return Mapping\ClassMetadata
313
     *
314
     * @throws Mapping\MappingException
315
     */
316 367
    private function convertClassAnnotationsToClassMetadata(
317
        array $classAnnotations,
318
        \ReflectionClass $reflectionClass,
319
        Mapping\ClassMetadata $metadata,
320
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
321
    ) : Mapping\ClassMetadata
322
    {
323
        switch (true) {
324 367
            case isset($classAnnotations[Annotation\Entity::class]):
325 363
                return $this->convertClassAnnotationsToEntityClassMetadata(
326 363
                    $classAnnotations,
327 363
                    $reflectionClass,
328 363
                    $metadata,
329 363
                    $metadataBuildingContext
330
                );
331
332
                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...
333
334 28
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
335 25
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
336 25
                    $classAnnotations,
337 25
                    $reflectionClass,
338 25
                    $metadata
339
                );
340
341 3
            case isset($classAnnotations[Annotation\Embeddable::class]):
342
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
343
                    $classAnnotations,
344
                    $reflectionClass,
345
                    $metadata
346
                );
347
348
            default:
349 3
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
350
        }
351
    }
352
353
    /**
354
     * @param array                                $classAnnotations
355
     * @param \ReflectionClass                     $reflectionClass
356
     * @param Mapping\ClassMetadata                $metadata
357
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
358
     *
359
     * @return Mapping\ClassMetadata
360
     *
361
     * @throws Mapping\MappingException
362
     * @throws \UnexpectedValueException
363
     */
364 363
    private function convertClassAnnotationsToEntityClassMetadata(
365
        array $classAnnotations,
366
        \ReflectionClass $reflectionClass,
367
        Mapping\ClassMetadata $metadata,
368
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
369
    )
370
    {
371
        /** @var Annotation\Entity $entityAnnot */
372 363
        $entityAnnot  = $classAnnotations[Annotation\Entity::class];
373
374 363
        if ($entityAnnot->repositoryClass !== null) {
375 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
376
        }
377
378 363
        if ($entityAnnot->readOnly) {
379 1
            $metadata->asReadOnly();
380
        }
381
382 363
        $metadata->isMappedSuperclass = false;
383 363
        $metadata->isEmbeddedClass = false;
384
385 363
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
386
387
        // Evaluate @ChangeTrackingPolicy annotation
388 363
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
389 5
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
390
391 5
            $metadata->setChangeTrackingPolicy(
392 5
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
393
            );
394
        }
395
396
        // Evaluate @InheritanceType annotation
397 363
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
398 78
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
399
400 78
            $metadata->setInheritanceType(
401 78
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
402
            );
403
404 78
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
405 78
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
406
            }
407
        }
408
409 363
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
410 363
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
411 363
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
412 363
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
413
414 363
        return $metadata;
415
    }
416
417
    /**
418
     * @param array                 $classAnnotations
419
     * @param \ReflectionClass      $reflectionClass
420
     * @param Mapping\ClassMetadata $metadata
421
     *
422
     * @return Mapping\ClassMetadata
423
     */
424 25
    private function convertClassAnnotationsToMappedSuperClassMetadata(
425
        array $classAnnotations,
426
        \ReflectionClass $reflectionClass,
427
        Mapping\ClassMetadata $metadata
428
    ) : Mapping\ClassMetadata
429
    {
430
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
431 25
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
432
433 25
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
434 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
435
        }
436
437 25
        $metadata->isMappedSuperclass = true;
438 25
        $metadata->isEmbeddedClass = false;
439
440 25
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
441 25
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
442 25
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
443 25
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
444
445 25
        return $metadata;
446
    }
447
448
    /**
449
     * @param array                 $classAnnotations
450
     * @param \ReflectionClass      $reflectionClass
451
     * @param Mapping\ClassMetadata $metadata
452
     *
453
     * @return Mapping\ClassMetadata
454
     */
455
    private function convertClassAnnotationsToEmbeddableClassMetadata(
456
        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

456
        /** @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...
457
        \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

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

1073
        /** @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...
1074
        Mapping\ClassMetadata $metadata,
1075
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
1076
    ) : void
1077
    {
1078 363
        $parent = $metadata->getParent();
1079
1080 363
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
1081
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
1082
            do {
1083 29
                if (! $parent->isMappedSuperclass) {
1084 29
                    $metadata->setTable($parent->table);
1085
1086 29
                    break;
1087
                }
1088 4
            } while (($parent = $parent->getParent()) !== null);
1089
1090 29
            return;
1091
        }
1092
1093 363
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1094 363
        $tableMetadata  = new Mapping\TableMetadata();
1095
1096 363
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1097
1098
        // Evaluate @Table annotation
1099 363
        if (isset($classAnnotations[Annotation\Table::class])) {
1100 191
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1101
1102 191
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1103
        }
1104
1105 363
        $metadata->setTable($tableMetadata);
1106 363
    }
1107
1108
    /**
1109
     * @param array                 $classAnnotations
1110
     * @param \ReflectionClass      $reflectionClass
1111
     * @param Mapping\ClassMetadata $metadata
1112
     *
1113
     * @return void
1114
     *
1115
     * @throws Mapping\MappingException
1116
     */
1117 78
    private function attachDiscriminatorColumn(
1118
        array $classAnnotations,
1119
        \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

1119
        /** @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...
1120
        Mapping\ClassMetadata $metadata
1121
    ) : void
1122
    {
1123 78
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1124
1125 78
        $discriminatorColumn->setTableName($metadata->getTableName());
1126 78
        $discriminatorColumn->setColumnName('dtype');
1127 78
        $discriminatorColumn->setType(Type::getType('string'));
1128 78
        $discriminatorColumn->setLength(255);
1129
1130
        // Evaluate DiscriminatorColumn annotation
1131 78
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1132
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1133 61
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1134 61
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1135 57
                ? $discriminatorColumnAnnotation->type
1136 61
                : 'string';
1137
1138 61
            $discriminatorColumn->setType(Type::getType($typeName));
1139 61
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1140
1141 61
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1142 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1143
            }
1144
1145 61
            if (! empty($discriminatorColumnAnnotation->length)) {
1146 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1147
            }
1148
        }
1149
1150 78
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1151
1152
        // Evaluate DiscriminatorMap annotation
1153 78
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1154 77
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1155 77
            $discriminatorMap           = $discriminatorMapAnnotation->value;
1156
1157 77
            $metadata->setDiscriminatorMap($discriminatorMap);
1158
        }
1159 78
    }
1160
1161
    /**
1162
     * @param array                 $classAnnotations
1163
     * @param \ReflectionClass      $reflectionClass
1164
     * @param Mapping\ClassMetadata $metadata
1165
     *
1166
     * @return void
1167
     *
1168
     * @throws \UnexpectedValueException
1169
     * @throws Mapping\MappingException
1170
     */
1171 364
    private function attachNamedQueries(
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
    {
1177
        // Evaluate @NamedQueries annotation
1178 364
        if (isset($classAnnotations[Annotation\NamedQueries::class])) {
1179 10
            $namedQueriesAnnot = $classAnnotations[Annotation\NamedQueries::class];
1180
1181 10
            if (! is_array($namedQueriesAnnot->value)) {
1182
                throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1183
            }
1184
1185 10
            foreach ($namedQueriesAnnot->value as $namedQuery) {
1186 10
                if (! ($namedQuery instanceof Annotation\NamedQuery)) {
1187
                    throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1188
                }
1189
1190 10
                $metadata->addNamedQuery($namedQuery->name, $namedQuery->query);
1191
            }
1192
        }
1193 364
    }
1194
1195
    /**
1196
     * @param array                 $classAnnotations
1197
     * @param \ReflectionClass      $reflectionClass
1198
     * @param Mapping\ClassMetadata $metadata
1199
     *
1200
     * @return void
1201
     */
1202 364
    private function attachNamedNativeQueries(
1203
        array $classAnnotations,
1204
        \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

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

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

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