Passed
Push — master ( b9880b...e1bb9e )
by Guilherme
09:04
created

AnnotationDriver::isTransient()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php /** @noinspection ALL */
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\Exception\CacheException;
12
use Doctrine\ORM\Events;
13
use Doctrine\ORM\Mapping;
14
use Doctrine\ORM\Mapping\Builder;
15
use FilesystemIterator;
16
use RecursiveDirectoryIterator;
17
use RecursiveIteratorIterator;
18
use RecursiveRegexIterator;
19
use ReflectionClass;
20
use ReflectionException;
21
use ReflectionMethod;
22
use ReflectionProperty;
23
use RegexIterator;
24
use RuntimeException;
25
use UnexpectedValueException;
26
use function array_diff;
27
use function array_intersect;
28
use function array_map;
29
use function array_merge;
30
use function array_unique;
31
use function class_exists;
32
use function constant;
33
use function count;
34
use function defined;
35
use function get_class;
36
use function get_declared_classes;
37
use function in_array;
38
use function is_dir;
39
use function is_numeric;
40
use function preg_match;
41
use function preg_quote;
42
use function realpath;
43
use function sprintf;
44
use function str_replace;
45
use function strpos;
46
use function strtoupper;
47
use function var_export;
48
49
/**
50
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
51
 */
52
class AnnotationDriver implements MappingDriver
53
{
54
    /** @var int[] */
55
    protected $entityAnnotationClasses = [
56
        Annotation\Entity::class           => 1,
57
        Annotation\MappedSuperclass::class => 2,
58
    ];
59
60
    /**
61
     * The AnnotationReader.
62
     *
63
     * @var AnnotationReader
64
     */
65
    protected $reader;
66
67
    /**
68
     * The paths where to look for mapping files.
69
     *
70
     * @var string[]
71
     */
72
    protected $paths = [];
73
74
    /**
75
     * The paths excluded from path where to look for mapping files.
76
     *
77
     * @var string[]
78
     */
79
    protected $excludePaths = [];
80
81
    /**
82
     * The file extension of mapping documents.
83
     *
84
     * @var string
85
     */
86
    protected $fileExtension = '.php';
87
88
    /**
89
     * Cache for AnnotationDriver#getAllClassNames().
90
     *
91
     * @var string[]|null
92
     */
93
    protected $classNames;
94
95
    /**
96
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
97
     * docblock annotations.
98
     *
99
     * @param Reader               $reader The AnnotationReader to use, duck-typed.
100
     * @param string|string[]|null $paths  One or multiple paths where mapping classes can be found.
101
     */
102 2297
    public function __construct(Reader $reader, $paths = null)
103
    {
104 2297
        $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...
105
106 2297
        if ($paths) {
107 2211
            $this->addPaths((array) $paths);
108
        }
109 2297
    }
110
111
    /**
112
     * Appends lookup paths to metadata driver.
113
     *
114
     * @param string[] $paths
115
     */
116 2215
    public function addPaths(array $paths)
117
    {
118 2215
        $this->paths = array_unique(array_merge($this->paths, $paths));
119 2215
    }
120
121
    /**
122
     * Retrieves the defined metadata lookup paths.
123
     *
124
     * @return string[]
125
     */
126
    public function getPaths()
127
    {
128
        return $this->paths;
129
    }
130
131
    /**
132
     * Append exclude lookup paths to metadata driver.
133
     *
134
     * @param string[] $paths
135
     */
136
    public function addExcludePaths(array $paths)
137
    {
138
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
139
    }
140
141
    /**
142
     * Retrieve the defined metadata lookup exclude paths.
143
     *
144
     * @return string[]
145
     */
146
    public function getExcludePaths()
147
    {
148
        return $this->excludePaths;
149
    }
150
151
    /**
152
     * Retrieve the current annotation reader
153
     *
154
     * @return Reader
155
     */
156 1
    public function getReader()
157
    {
158 1
        return $this->reader;
159
    }
160
161
    /**
162
     * Gets the file extension used to look for mapping files under.
163
     *
164
     * @return string
165
     */
166
    public function getFileExtension()
167
    {
168
        return $this->fileExtension;
169
    }
170
171
    /**
172
     * Sets the file extension used to look for mapping files under.
173
     *
174
     * @param string $fileExtension The file extension to set.
175
     */
176
    public function setFileExtension($fileExtension)
177
    {
178
        $this->fileExtension = $fileExtension;
179
    }
180
181
    /**
182
     * Returns whether the class with the specified name is transient. Only non-transient
183
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
184
     *
185
     * A class is non-transient if it is annotated with an annotation
186
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
187
     *
188
     * @param string $className
189
     *
190
     * @throws ReflectionException
191
     */
192 193
    public function isTransient($className) : bool
193
    {
194 193
        $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
195
196 193
        foreach ($classAnnotations as $annotation) {
197 188
            if (isset($this->entityAnnotationClasses[get_class($annotation)])) {
198 188
                return false;
199
            }
200
        }
201
202 12
        return true;
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     *
208
     * @throws ReflectionException
209
     */
210 60
    public function getAllClassNames() : array
211
    {
212 60
        if ($this->classNames !== null) {
213 45
            return $this->classNames;
214
        }
215
216 60
        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...
217
            throw Mapping\MappingException::pathRequired();
218
        }
219
220 60
        $classes       = [];
221 60
        $includedFiles = [];
222
223 60
        foreach ($this->paths as $path) {
224 60
            if (! is_dir($path)) {
225
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
226
            }
227
228 60
            $iterator = new RegexIterator(
229 60
                new RecursiveIteratorIterator(
230 60
                    new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
231 60
                    RecursiveIteratorIterator::LEAVES_ONLY
232
                ),
233 60
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
234 60
                RecursiveRegexIterator::GET_MATCH
235
            );
236
237 60
            foreach ($iterator as $file) {
238 60
                $sourceFile = $file[0];
239
240 60
                if (! preg_match('(^phar:)i', $sourceFile)) {
241 60
                    $sourceFile = realpath($sourceFile);
242
                }
243
244 60
                foreach ($this->excludePaths as $excludePath) {
245
                    $exclude = str_replace('\\', '/', realpath($excludePath));
246
                    $current = str_replace('\\', '/', $sourceFile);
247
248
                    if (strpos($current, $exclude) !== false) {
249
                        continue 2;
250
                    }
251
                }
252
253 60
                require_once $sourceFile;
254
255 60
                $includedFiles[] = $sourceFile;
256
            }
257
        }
258
259 60
        $declared = get_declared_classes();
260
261 60
        foreach ($declared as $className) {
262 60
            $reflectionClass = new ReflectionClass($className);
263 60
            $sourceFile      = $reflectionClass->getFileName();
264
265 60
            if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) {
266 60
                $classes[] = $className;
267
            }
268
        }
269
270 60
        $this->classNames = $classes;
271
272 60
        return $classes;
273
    }
274
275
    /**
276
     * {@inheritDoc}
277
     *
278
     * @throws CacheException
279
     * @throws Mapping\MappingException
280
     * @throws ReflectionException
281
     * @throws RuntimeException
282
     * @throws UnexpectedValueException
283
     */
284 379
    public function loadMetadataForClass(
285
        string $className,
286
        ?Mapping\ComponentMetadata $parent,
287
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
288
    ) : Mapping\ComponentMetadata {
289 379
        $reflectionClass  = new ReflectionClass($className);
290 379
        $metadata         = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext);
291 379
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
292 379
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
293 379
            $classAnnotations,
294 379
            $reflectionClass,
295 379
            $metadata,
296 379
            $metadataBuildingContext
297
        );
298
299
        // Evaluate @Cache annotation
300 373
        if (isset($classAnnotations[Annotation\Cache::class])) {
301 18
            $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext);
302
303
            $cacheBuilder
304 18
                ->withEntityClassMetadata($metadata)
305 18
                ->withCacheAnnotation($classAnnotations[Annotation\Cache::class]);
306
307 18
            $metadata->setCache($cacheBuilder->build());
308
        }
309
310
        // Evaluate annotations on properties/fields
311
        /** @var ReflectionProperty $reflProperty */
312 373
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
313 373
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
314 74
                continue;
315
            }
316
317 373
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
318 372
            $property            = $this->convertPropertyAnnotationsToProperty(
319 372
                $propertyAnnotations,
320 372
                $reflectionProperty,
321 372
                $classMetadata,
322 372
                $metadataBuildingContext
323
            );
324
325 372
            if ($classMetadata->isMappedSuperclass &&
326 372
                $property instanceof Mapping\ToManyAssociationMetadata &&
327 372
                ! $property->isOwningSide()) {
328 1
                throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass(
329 1
                    $classMetadata->getClassName(),
330 1
                    $property->getName()
331
                );
332
            }
333
334 371
            $metadata->addProperty($property);
335
        }
336
337 370
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
338
339 370
        return $classMetadata;
340
    }
341
342
    /**
343
     * @param Annotation\Annotation[] $classAnnotations
344
     *
345
     * @throws Mapping\MappingException
346
     * @throws UnexpectedValueException
347
     * @throws ReflectionException
348
     */
349 379
    private function convertClassAnnotationsToClassMetadata(
350
        array $classAnnotations,
351
        ReflectionClass $reflectionClass,
352
        Mapping\ClassMetadata $metadata,
353
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
354
    ) : Mapping\ClassMetadata {
355
        switch (true) {
356 379
            case isset($classAnnotations[Annotation\Entity::class]):
357 372
                return $this->convertClassAnnotationsToEntityClassMetadata(
358 372
                    $classAnnotations,
359 372
                    $reflectionClass,
360 372
                    $metadata,
361 372
                    $metadataBuildingContext
362
                );
363
364
                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...
365
366 29
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
367 23
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
368 23
                    $classAnnotations,
369 23
                    $reflectionClass,
370 23
                    $metadata
371
                );
372 6
            case isset($classAnnotations[Annotation\Embeddable::class]):
373
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
374
                    $classAnnotations,
375
                    $reflectionClass,
376
                    $metadata
377
                );
378
            default:
379 6
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
380
        }
381
    }
382
383
    /**
384
     * @param Annotation\Annotation[] $classAnnotations
385
     *
386
     * @return Mapping\ClassMetadata
387
     *
388
     * @throws Mapping\MappingException
389
     * @throws ReflectionException
390
     * @throws UnexpectedValueException
391
     */
392 372
    private function convertClassAnnotationsToEntityClassMetadata(
393
        array $classAnnotations,
394
        ReflectionClass $reflectionClass,
395
        Mapping\ClassMetadata $metadata,
396
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
397
    ) {
398
        /** @var Annotation\Entity $entityAnnot */
399 372
        $entityAnnot = $classAnnotations[Annotation\Entity::class];
400
401 372
        if ($entityAnnot->repositoryClass !== null) {
402 3
            $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass);
403
        }
404
405 372
        if ($entityAnnot->readOnly) {
406 1
            $metadata->asReadOnly();
407
        }
408
409 372
        $metadata->isMappedSuperclass = false;
410 372
        $metadata->isEmbeddedClass    = false;
411
412 372
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
413
414
        // Evaluate @ChangeTrackingPolicy annotation
415 372
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
416 6
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
417
418 6
            $metadata->setChangeTrackingPolicy(
419 6
                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...
420
            );
421
        }
422
423
        // Evaluate @InheritanceType annotation
424 372
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
425 80
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
426
427 80
            $metadata->setInheritanceType(
428 80
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
429
            );
430
431 80
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
432 80
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
433
            }
434
        }
435
436 372
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
437 372
        $this->attachEntityListeners($classAnnotations, $metadata);
438
439 372
        return $metadata;
440
    }
441
442
    /**
443
     * @param Annotation\Annotation[] $classAnnotations
444
     *
445
     * @throws Mapping\MappingException
446
     * @throws ReflectionException
447
     */
448 23
    private function convertClassAnnotationsToMappedSuperClassMetadata(
449
        array $classAnnotations,
450
        ReflectionClass $reflectionClass,
451
        Mapping\ClassMetadata $metadata
452
    ) : Mapping\ClassMetadata {
453
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
454 23
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
455
456 23
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
457 2
            $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass);
458
        }
459
460 23
        $metadata->isMappedSuperclass = true;
461 23
        $metadata->isEmbeddedClass    = false;
462
463 23
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
464 23
        $this->attachEntityListeners($classAnnotations, $metadata);
465
466 23
        return $metadata;
467
    }
468
469
    /**
470
     * @param Annotation\Annotation[] $classAnnotations
471
     */
472
    private function convertClassAnnotationsToEmbeddableClassMetadata(
473
        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

473
        /** @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...
474
        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

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

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

1118
        /** @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...
1119
        Mapping\ClassMetadata $metadata,
1120
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

1120
        /** @scrutinizer ignore-unused */ Mapping\ClassMetadataBuildingContext $metadataBuildingContext

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

Loading history...
1121
    ) : void {
1122 80
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1123
1124 80
        $discriminatorColumn->setTableName($metadata->getTableName());
1125 80
        $discriminatorColumn->setColumnName('dtype');
1126 80
        $discriminatorColumn->setType(Type::getType('string'));
1127 80
        $discriminatorColumn->setLength(255);
1128
1129
        // Evaluate DiscriminatorColumn annotation
1130 80
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1131
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1132 62
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1133 62
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1134 58
                ? $discriminatorColumnAnnotation->type
1135 62
                : 'string';
1136
1137 62
            $discriminatorColumn->setType(Type::getType($typeName));
1138 62
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1139
1140 62
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1141 1
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1142
            }
1143
1144 62
            if (! empty($discriminatorColumnAnnotation->length)) {
1145 5
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1146
            }
1147
        }
1148
1149 80
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1150
1151
        // Evaluate DiscriminatorMap annotation
1152 80
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1153 77
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1154 77
            $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...
1155
1156 77
            $metadata->setDiscriminatorMap($discriminatorMap);
1157
        }
1158 80
    }
1159
1160
    /**
1161
     * @param Annotation\Annotation[] $classAnnotations
1162
     */
1163 373
    private function attachLifecycleCallbacks(
1164
        array $classAnnotations,
1165
        ReflectionClass $reflectionClass,
1166
        Mapping\ClassMetadata $metadata
1167
    ) : void {
1168
        // Evaluate @HasLifecycleCallbacks annotation
1169 373
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1170
            $eventMap = [
1171 14
                Events::prePersist  => Annotation\PrePersist::class,
1172 14
                Events::postPersist => Annotation\PostPersist::class,
1173 14
                Events::preUpdate   => Annotation\PreUpdate::class,
1174 14
                Events::postUpdate  => Annotation\PostUpdate::class,
1175 14
                Events::preRemove   => Annotation\PreRemove::class,
1176 14
                Events::postRemove  => Annotation\PostRemove::class,
1177 14
                Events::postLoad    => Annotation\PostLoad::class,
1178 14
                Events::preFlush    => Annotation\PreFlush::class,
1179
            ];
1180
1181
            /** @var ReflectionMethod $reflectionMethod */
1182 14
            foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
1183 13
                $annotations = $this->getMethodAnnotations($reflectionMethod);
1184
1185 13
                foreach ($eventMap as $eventName => $annotationClassName) {
1186 13
                    if (isset($annotations[$annotationClassName])) {
1187 12
                        $metadata->addLifecycleCallback($eventName, $reflectionMethod->getName());
1188
                    }
1189
                }
1190
            }
1191
        }
1192 373
    }
1193
1194
    /**
1195
     * @param Annotation\Annotation[] $classAnnotations
1196
     *
1197
     * @throws ReflectionException
1198
     * @throws Mapping\MappingException
1199
     */
1200 373
    private function attachEntityListeners(
1201
        array $classAnnotations,
1202
        Mapping\ClassMetadata $metadata
1203
    ) : void {
1204
        // Evaluate @EntityListeners annotation
1205 373
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1206
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1207 8
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1208
            $eventMap             = [
1209 8
                Events::prePersist  => Annotation\PrePersist::class,
1210 8
                Events::postPersist => Annotation\PostPersist::class,
1211 8
                Events::preUpdate   => Annotation\PreUpdate::class,
1212 8
                Events::postUpdate  => Annotation\PostUpdate::class,
1213 8
                Events::preRemove   => Annotation\PreRemove::class,
1214 8
                Events::postRemove  => Annotation\PostRemove::class,
1215 8
                Events::postLoad    => Annotation\PostLoad::class,
1216 8
                Events::preFlush    => Annotation\PreFlush::class,
1217
            ];
1218
1219 8
            foreach ($entityListenersAnnot->value as $listenerClassName) {
1220 8
                if (! class_exists($listenerClassName)) {
1221
                    throw Mapping\MappingException::entityListenerClassNotFound(
1222
                        $listenerClassName,
1223
                        $metadata->getClassName()
1224
                    );
1225
                }
1226
1227 8
                $listenerClass = new ReflectionClass($listenerClassName);
1228
1229
                /** @var ReflectionMethod $reflectionMethod */
1230 8
                foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
1231 8
                    $annotations = $this->getMethodAnnotations($reflectionMethod);
1232
1233 8
                    foreach ($eventMap as $eventName => $annotationClassName) {
1234 8
                        if (isset($annotations[$annotationClassName])) {
1235 6
                            $metadata->addEntityListener($eventName, $listenerClassName, $reflectionMethod->getName());
1236
                        }
1237
                    }
1238
                }
1239
            }
1240
        }
1241 373
    }
1242
1243
    /**
1244
     * @param Annotation\Annotation[] $classAnnotations
1245
     *
1246
     * @throws Mapping\MappingException
1247
     */
1248 370
    private function attachPropertyOverrides(
1249
        array $classAnnotations,
1250
        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

1250
        /** @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...
1251
        Mapping\ClassMetadata $metadata,
1252
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext is not used and could be removed. ( Ignorable by Annotation )

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

1252
        /** @scrutinizer ignore-unused */ Mapping\ClassMetadataBuildingContext $metadataBuildingContext

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

Loading history...
1253
    ) : void {
1254
        // Evaluate AssociationOverrides annotation
1255 370
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1256 5
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1257
1258 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...
1259 5
                $fieldName = $associationOverride->name;
1260 5
                $property  = $metadata->getProperty($fieldName);
1261
1262 5
                if (! $property) {
1263
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1264
                }
1265
1266 5
                $existingClass = get_class($property);
1267 5
                $override      = new $existingClass($fieldName);
1268
1269
                // Check for JoinColumn/JoinColumns annotations
1270 5
                if ($associationOverride->joinColumns) {
1271 3
                    $joinColumns = [];
1272
1273 3
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1274 3
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1275
                    }
1276
1277 3
                    $override->setJoinColumns($joinColumns);
1278
                }
1279
1280
                // Check for JoinTable annotations
1281 5
                if ($associationOverride->joinTable) {
1282 2
                    $joinTableAnnot    = $associationOverride->joinTable;
1283 2
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1284
1285 2
                    $override->setJoinTable($joinTableMetadata);
1286
                }
1287
1288
                // Check for inversedBy
1289 5
                if ($associationOverride->inversedBy) {
1290 1
                    $override->setInversedBy($associationOverride->inversedBy);
1291
                }
1292
1293
                // Check for fetch
1294 5
                if ($associationOverride->fetch) {
1295 1
                    $override->setFetchMode(
1296 1
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1297
                    );
1298
                }
1299
1300 5
                $metadata->setPropertyOverride($override);
1301
            }
1302
        }
1303
1304
        // Evaluate AttributeOverrides annotation
1305 370
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1306 3
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1307
1308 3
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1309 3
                $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1310 3
                    $attributeOverrideAnnot->column,
1311 3
                    $attributeOverrideAnnot->name,
1312 3
                    false
1313
                );
1314
1315 3
                $metadata->setPropertyOverride($fieldMetadata);
1316
            }
1317
        }
1318 370
    }
1319
1320
    /**
1321
     * Attempts to resolve the cascade modes.
1322
     *
1323
     * @param string   $className        The class name.
1324
     * @param string   $fieldName        The field name.
1325
     * @param string[] $originalCascades The original unprocessed field cascades.
1326
     *
1327
     * @return string[] The processed field cascades.
1328
     *
1329
     * @throws Mapping\MappingException If a cascade option is not valid.
1330
     */
1331 251
    private function getCascade(string $className, string $fieldName, array $originalCascades) : array
1332
    {
1333 251
        $cascadeTypes = ['remove', 'persist', 'refresh'];
1334 251
        $cascades     = array_map('strtolower', $originalCascades);
1335
1336 251
        if (in_array('all', $cascades, true)) {
1337 23
            $cascades = $cascadeTypes;
1338
        }
1339
1340 251
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1341
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1342
1343
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1344
        }
1345
1346 251
        return $cascades;
1347
    }
1348
1349
    /**
1350
     * Attempts to resolve the fetch mode.
1351
     *
1352
     * @param string $className The class name.
1353
     * @param string $fetchMode The fetch mode.
1354
     *
1355
     * @return string The fetch mode as defined in ClassMetadata.
1356
     *
1357
     * @throws Mapping\MappingException If the fetch mode is not valid.
1358
     */
1359 251
    private function getFetchMode($className, $fetchMode) : string
1360
    {
1361 251
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1362
1363 251
        if (! defined($fetchModeConstant)) {
1364
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1365
        }
1366
1367 251
        return constant($fetchModeConstant);
1368
    }
1369
1370
    /**
1371
     * @return Annotation\Annotation[]
1372
     */
1373 379
    private function getClassAnnotations(ReflectionClass $reflectionClass) : array
1374
    {
1375 379
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1376
1377 379
        foreach ($classAnnotations as $key => $annot) {
1378 373
            if (! is_numeric($key)) {
1379
                continue;
1380
            }
1381
1382 373
            $classAnnotations[get_class($annot)] = $annot;
1383
        }
1384
1385 379
        return $classAnnotations;
1386
    }
1387
1388
    /**
1389
     * @return Annotation\Annotation[]
1390
     */
1391 373
    private function getPropertyAnnotations(ReflectionProperty $reflectionProperty) : array
1392
    {
1393 373
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1394
1395 372
        foreach ($propertyAnnotations as $key => $annot) {
1396 372
            if (! is_numeric($key)) {
1397
                continue;
1398
            }
1399
1400 372
            $propertyAnnotations[get_class($annot)] = $annot;
1401
        }
1402
1403 372
        return $propertyAnnotations;
1404
    }
1405
1406
    /**
1407
     * @return Annotation\Annotation[]
1408
     */
1409 21
    private function getMethodAnnotations(ReflectionMethod $reflectionMethod) : array
1410
    {
1411 21
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1412
1413 21
        foreach ($methodAnnotations as $key => $annot) {
1414 18
            if (! is_numeric($key)) {
1415
                continue;
1416
            }
1417
1418 18
            $methodAnnotations[get_class($annot)] = $annot;
1419
        }
1420
1421 21
        return $methodAnnotations;
1422
    }
1423
}
1424