Failed Conditions
Push — develop ( 856053...a7d1bd )
by Guilherme
61:28
created

convertReflectionPropertyToOneToOneAssociationMetadata()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 57
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7.6383

Importance

Changes 0
Metric Value
dl 0
loc 57
ccs 13
cts 17
cp 0.7647
rs 7.6759
c 0
b 0
f 0
cc 7
eloc 33
nc 24
nop 3
crap 7.6383

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\DBAL\Types\Type;
9
use Doctrine\ORM\Annotation;
10
use Doctrine\ORM\Events;
11
use Doctrine\ORM\Mapping;
12
13
/**
14
 * The AnnotationDriver reads the mapping metadata from docblock annotations.
15
 *
16
 * @since 2.0
17
 * @author Benjamin Eberlei <[email protected]>
18
 * @author Guilherme Blanco <[email protected]>
19
 * @author Jonathan H. Wage <[email protected]>
20
 * @author Roman Borschel <[email protected]>
21
 */
22
class AnnotationDriver implements MappingDriver
23
{
24
    /**
25
     * {@inheritDoc}
26
     */
27
    protected $entityAnnotationClasses = [
28
        Annotation\Entity::class           => 1,
29
        Annotation\MappedSuperclass::class => 2,
30
    ];
31
32
    /**
33
     * The AnnotationReader.
34
     *
35
     * @var AnnotationReader
36
     */
37
    protected $reader;
38
39
    /**
40
     * The paths where to look for mapping files.
41
     *
42
     * @var array
43
     */
44
    protected $paths = [];
45
46
    /**
47
     * The paths excluded from path where to look for mapping files.
48
     *
49
     * @var array
50
     */
51
    protected $excludePaths = [];
52
53
    /**
54
     * The file extension of mapping documents.
55
     *
56 362
     * @var string
57
     */
58
    protected $fileExtension = '.php';
59 362
60 362
    /**
61
     * Cache for AnnotationDriver#getAllClassNames().
62 362
     *
63
     * @var array|null
64
     */
65 1
    protected $classNames;
66
67
    /**
68 362
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
69
     * docblock annotations.
70 362
     *
71 359
     * @param AnnotationReader  $reader The AnnotationReader to use, duck-typed.
72 359
     * @param string|array|null $paths  One or multiple paths where mapping classes can be found.
73
     */
74
    public function __construct($reader, $paths = null)
75
    {
76 359
        $this->reader = $reader;
77
        if ($paths) {
78
            $this->addPaths((array) $paths);
79
        }
80
    }
81
82 362
    /**
83 354
     * Appends lookup paths to metadata driver.
84
     *
85 354
     * @param array $paths
86 8
     *
87
     * @return void
88
     */
89 354
    public function addPaths(array $paths)
90 1
    {
91
        $this->paths = array_unique(array_merge($this->paths, $paths));
92
    }
93 354
94
    /**
95 38
     * Retrieves the defined metadata lookup paths.
96 32
     *
97
     * @return array
98 32
     */
99 32
    public function getPaths()
100 32
    {
101
        return $this->paths;
102 7
    }
103 4
104 4
    /**
105
     * Append exclude lookup paths to metadata driver.
106
     *
107 3
     * @param array $paths
108
     */
109
    public function addExcludePaths(array $paths)
110
    {
111 359
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
112 187
    }
113
114 187
    /**
115
     * Retrieve the defined metadata lookup exclude paths.
116 187
     *
117 13
     * @return array
118 13
     */
119 13
    public function getExcludePaths()
120 13
    {
121 13
        return $this->excludePaths;
122
    }
123
124
    /**
125 187
     * Retrieve the current annotation reader
126 8
     *
127 8
     * @return AnnotationReader
128 8
     */
129 8
    public function getReader()
130
    {
131
        return $this->reader;
132
    }
133
134
    /**
135 359
     * Gets the file extension used to look for mapping files under.
136 14
     *
137
     * @return string
138 14
     */
139 14
    public function getFileExtension()
140 14
    {
141
        return $this->fileExtension;
142
    }
143
144
    /**
145 359
     * Sets the file extension used to look for mapping files under.
146 14
     *
147
     * @param string $fileExtension The file extension to set.
148 14
     *
149 14
     * @return void
150 14
     */
151 14
    public function setFileExtension($fileExtension)
152 14
    {
153 14
        $this->fileExtension = $fileExtension;
154
    }
155
156
    /**
157
     * Returns whether the class with the specified name is transient. Only non-transient
158
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
159 359
     *
160 14
     * A class is non-transient if it is annotated with an annotation
161
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
162 14
     *
163 14
     * @param string $className
164 14
     *
165
     * @return boolean
166 14
     */
167
    public function isTransient($className)
168 14
    {
169 14
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
170 14
171
        foreach ($classAnnotations as $annot) {
172
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
173 14
                return false;
174 14
            }
175 14
        }
176 14
        return true;
177
    }
178
179
    /**
180 14
     * {@inheritDoc}
181
     */
182
    public function getAllClassNames()
183 14
    {
184 9
        if ($this->classNames !== null) {
185 9
            return $this->classNames;
186
        }
187
188
        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...
189 14
            throw Mapping\MappingException::pathRequired();
190 14
        }
191 14
192 14
        $classes = [];
193
        $includedFiles = [];
194
195
        foreach ($this->paths as $path) {
196
            if ( ! is_dir($path)) {
197
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
198 359
            }
199 9
200
            $iterator = new \RegexIterator(
201 9
                new \RecursiveIteratorIterator(
202
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
203
                    \RecursiveIteratorIterator::LEAVES_ONLY
204
                ),
205 9
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
206 9
                \RecursiveRegexIterator::GET_MATCH
207
            );
208
209
            foreach ($iterator as $file) {
210 9
                $sourceFile = $file[0];
211
212
                if ( ! preg_match('(^phar:)i', $sourceFile)) {
213
                    $sourceFile = realpath($sourceFile);
214
                }
215 359
216 65
                foreach ($this->excludePaths as $excludePath) {
217
                    $exclude = str_replace('\\', '/', realpath($excludePath));
218 65
                    $current = str_replace('\\', '/', $sourceFile);
219 65
220
                    if (strpos($current, $exclude) !== false) {
221
                        continue 2;
222 65
                    }
223 65
                }
224
225 65
                require_once $sourceFile;
226
227
                $includedFiles[] = $sourceFile;
228 65
            }
229 48
        }
230
231 48
        $declared = get_declared_classes();
232 48
233 48
        foreach ($declared as $className) {
234 48
            $rc = new \ReflectionClass($className);
235
            $sourceFile = $rc->getFileName();
236 20
            if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
237 20
                $classes[] = $className;
238 20
            }
239
        }
240
241 65
        $this->classNames = $classes;
242
243
        return $classes;
244 65
    }
245 64
246
    /**
247 64
     * {@inheritDoc}
248
     */
249
    public function loadMetadataForClass(
250
        string $className,
251
        Mapping\ClassMetadata $metadata,
252
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
253
    ) : Mapping\ClassMetadata
254 359
    {
255 5
        $reflectionClass = $metadata->getReflectionClass();
256
257 5
        if (! $reflectionClass) {
258 5
            // this happens when running annotation driver in combination with
259
            // static reflection services. This is not the nicest fix
260
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
261
        }
262
263
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
264 359
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
265
            $classAnnotations,
266
            $reflectionClass,
267
            $metadata,
268
            $metadataBuildingContext
269 357
        );
270 357
271 357
        // Evaluate @Cache annotation
272 357
        if (isset($classAnnotations[Annotation\Cache::class])) {
273 69
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
274
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
275
276 357
            $classMetadata->setCache($cache);
277 357
        }
278
279
        // Evaluate annotations on properties/fields
280 357
        /* @var $reflProperty \ReflectionProperty */
281 12
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
282 12
            if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) {
0 ignored issues
show
introduced by
Consider using $reflectionProperty->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
283 12
                continue;
284
            }
285
286
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
287 356
            $property            = $this->convertPropertyAnnotationsToProperty(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $property is correct as $this->convertPropertyAn...operty, $classMetadata) (which targets Doctrine\ORM\Mapping\Dri...AnnotationsToProperty()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
288
                $propertyAnnotations,
289 356
                $reflectionProperty,
290 136
                $classMetadata
291 355
            );
292 21
293 21
            if (! $property) {
294
                continue;
295
            }
296
297
            $metadata->addProperty($property);
298
        }
299 356
300 347
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
301
302
        return $classMetadata;
303
    }
304 347
305
    /**
306 347
     * @param array                                $classAnnotations
307 340
     * @param \ReflectionClass                     $reflectionClass
308
     * @param Mapping\ClassMetadata                $metadata
309
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
310 347
     *
311 291
     * @return Mapping\ClassMetadata
312 291
     *
313
     * @throws Mapping\MappingException
314
     */
315
    private function convertClassAnnotationsToClassMetadata(
316 347
        array $classAnnotations,
317
        \ReflectionClass $reflectionClass,
318 347
        Mapping\ClassMetadata $metadata,
319 13
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
320
    ) : Mapping\ClassMetadata
321
    {
322
        switch (true) {
323 347
            case isset($classAnnotations[Annotation\Entity::class]):
324 8
                return $this->convertClassAnnotationsToEntityClassMetadata(
325 8
                    $classAnnotations,
326 8
                    $reflectionClass,
327 8
                    $metadata,
328
                    $metadataBuildingContext
329 344
                );
330
331 344
                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...
332 2
333 347
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
334
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
335
                    $classAnnotations,
336 255
                    $reflectionClass,
337 106
                    $metadata
338 9
                );
339
340
            case isset($classAnnotations[Annotation\Embeddable::class]):
341 106
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
342 106
                    $classAnnotations,
343 106
                    $reflectionClass,
344 106
                    $metadata
345 106
                );
346 106
347 106
            default:
348 106
                throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName());
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
349 206
        }
350 102
    }
351 102
352 102
    /**
353 102
     * @param array                                $classAnnotations
354 102
     * @param \ReflectionClass                     $reflectionClass
355 102
     * @param Mapping\ClassMetadata                $metadata
356
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
357 102
     *
358 15
     * @return Mapping\ClassMetadata
359
     *
360
     * @throws Mapping\MappingException
361 102
     * @throws \UnexpectedValueException
362 203
     */
363 133
    private function convertClassAnnotationsToEntityClassMetadata(
364 30
        array $classAnnotations,
365
        \ReflectionClass $reflectionClass,
366
        Mapping\ClassMetadata $metadata,
367 133
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
368 133
    )
369 133
    {
370 133
        /** @var Annotation\Entity $entityAnnot */
371 133
        $entityAnnot  = $classAnnotations[Annotation\Entity::class];
372 133
373 113
        if ($entityAnnot->repositoryClass !== null) {
374 84
            $metadata->setCustomRepositoryClassName(
375
                $metadata->fullyQualifiedClassName($entityAnnot->repositoryClass)
376 84
            );
377
        }
378 65
379 65
        if ($entityAnnot->readOnly) {
380
            $metadata->asReadOnly();
381
        }
382 65
383 64
        $metadata->isMappedSuperclass = false;
384
        $metadata->isEmbeddedClass = false;
385
386 65
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
387 64
388
        // Evaluate @ChangeTrackingPolicy annotation
389
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
390
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
391 84
392 84
            $metadata->setChangeTrackingPolicy(
393 84
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
394 84
            );
395 84
        }
396 84
397 84
        // Evaluate @InheritanceType annotation
398 84
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
399
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
400 84
401 3
            $metadata->setInheritanceType(
402
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
403
            );
404 84
405 44
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
406 10
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
407 10
            }
408
        }
409 355
410
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
411
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
412
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
413
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
414 357
415 4
        return $metadata;
416
    }
417 4
418 4
    /**
419 4
     * @param array                 $classAnnotations
420
     * @param \ReflectionClass      $reflectionClass
421
     * @param Mapping\ClassMetadata $metadata
422 4
     *
423 3
     * @return Mapping\ClassMetadata
424
     */
425 3
    private function convertClassAnnotationsToMappedSuperClassMetadata(
426 3
        array $classAnnotations,
427
        \ReflectionClass $reflectionClass,
428
        Mapping\ClassMetadata $metadata
429 3
    ) : Mapping\ClassMetadata
430
    {
431
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
432
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
433 4
434 2
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
435
            $metadata->setCustomRepositoryClassName(
436 2
                $metadata->fullyQualifiedClassName($mappedSuperclassAnnot->repositoryClass)
437 2
            );
438
        }
439
440 2
        $metadata->isMappedSuperclass = true;
441 2
        $metadata->isEmbeddedClass = false;
442
443
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
444 2
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
445 2
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
446
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
447
448 2
        return $metadata;
449
    }
450
451
    /**
452 4
     * @param array                 $classAnnotations
453 1
     * @param \ReflectionClass      $reflectionClass
454
     * @param Mapping\ClassMetadata $metadata
455
     *
456 4
     * @return Mapping\ClassMetadata
457
     */
458
    private function convertClassAnnotationsToEmbeddableClassMetadata(
459
        array $classAnnotations,
0 ignored issues
show
Unused Code introduced by
The parameter $classAnnotations is not used and could be removed.

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

Loading history...
460
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed.

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

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

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

Loading history...
1077
        Mapping\ClassMetadata $metadata,
1078
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
1079
    ) : void
1080
    {
1081
        $parent = $metadata->getParent();
1082
1083
        if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) {
0 ignored issues
show
Bug introduced by
The property inheritanceType does not seem to exist in Doctrine\ORM\Mapping\ComponentMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1084
            // Handle the case where a middle mapped super class inherits from a single table inheritance tree.
1085
            do {
1086
                if (! $parent->isMappedSuperclass) {
0 ignored issues
show
Bug introduced by
The property isMappedSuperclass does not seem to exist in Doctrine\ORM\Mapping\ComponentMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1087
                    $metadata->setTable($parent->table);
0 ignored issues
show
Bug introduced by
The property table does not seem to exist in Doctrine\ORM\Mapping\ComponentMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
1088
1089
                    break;
1090
                }
1091
            } while (($parent = $parent->getParent()) !== null);
1092
1093
            return;
1094
        }
1095
1096
        $namingStrategy = $metadataBuildingContext->getNamingStrategy();
1097
        $tableMetadata  = new Mapping\TableMetadata();
1098
1099
        $tableMetadata->setName($namingStrategy->classToTableName($metadata->getClassName()));
1100
1101
        // Evaluate @Table annotation
1102
        if (isset($classAnnotations[Annotation\Table::class])) {
1103
            $tableAnnot = $classAnnotations[Annotation\Table::class];
1104
1105
            $this->convertTableAnnotationToTableMetadata($tableAnnot, $tableMetadata);
1106
        }
1107
1108
        $metadata->setTable($tableMetadata);
1109
    }
1110
1111
    /**
1112
     * @param array                 $classAnnotations
1113
     * @param \ReflectionClass      $reflectionClass
1114
     * @param Mapping\ClassMetadata $metadata
1115
     *
1116
     * @return void
1117
     *
1118
     * @throws Mapping\MappingException
1119
     */
1120
    private function attachDiscriminatorColumn(
1121
        array $classAnnotations,
1122
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed.

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

Loading history...
1123
        Mapping\ClassMetadata $metadata
1124
    ) : void
1125
    {
1126
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1127
1128
        $discriminatorColumn->setTableName($metadata->getTableName());
1129
        $discriminatorColumn->setColumnName('dtype');
1130
        $discriminatorColumn->setType(Type::getType('string'));
1131
        $discriminatorColumn->setLength(255);
1132
1133
        // Evaluate DiscriminatorColumn annotation
1134
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1135
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1136
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1137
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1138
                ? $discriminatorColumnAnnotation->type
1139
                : 'string';
1140
1141
            $discriminatorColumn->setType(Type::getType($typeName));
1142
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1143
1144
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1145
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1146
            }
1147
1148
            if (! empty($discriminatorColumnAnnotation->length)) {
1149
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1150
            }
1151
        }
1152
1153
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1154
1155
        // Evaluate DiscriminatorMap annotation
1156
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1157
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1158
            $discriminatorMap           = array_map(
1159
                function ($className) use ($metadata) {
1160
                    return $metadata->fullyQualifiedClassName($className);
1161
                },
1162
                $discriminatorMapAnnotation->value
1163
            );
1164
1165
            $metadata->setDiscriminatorMap($discriminatorMap);
1166
        }
1167
    }
1168
1169
    /**
1170
     * @param array                 $classAnnotations
1171
     * @param \ReflectionClass      $reflectionClass
1172
     * @param Mapping\ClassMetadata $metadata
1173
     *
1174
     * @return void
1175
     *
1176
     * @throws \UnexpectedValueException
1177
     * @throws Mapping\MappingException
1178
     */
1179
    private function attachNamedQueries(
1180
        array $classAnnotations,
1181
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed.

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

Loading history...
1182
        Mapping\ClassMetadata $metadata
1183
    ) : void
1184
    {
1185
        // Evaluate @NamedQueries annotation
1186
        if (isset($classAnnotations[Annotation\NamedQueries::class])) {
1187
            $namedQueriesAnnot = $classAnnotations[Annotation\NamedQueries::class];
1188
1189
            if (! is_array($namedQueriesAnnot->value)) {
1190
                throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1191
            }
1192
1193
            foreach ($namedQueriesAnnot->value as $namedQuery) {
1194
                if (! ($namedQuery instanceof Annotation\NamedQuery)) {
1195
                    throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1196
                }
1197
1198
                $metadata->addNamedQuery($namedQuery->name, $namedQuery->query);
1199
            }
1200
        }
1201
    }
1202
1203
    /**
1204
     * @param array                 $classAnnotations
1205
     * @param \ReflectionClass      $reflectionClass
1206
     * @param Mapping\ClassMetadata $metadata
1207
     *
1208
     * @return void
1209
     */
1210
    private function attachNamedNativeQueries(
1211
        array $classAnnotations,
1212
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed.

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

Loading history...
1213
        Mapping\ClassMetadata $metadata
1214
    ) : void
1215
    {
1216
        // Evaluate @NamedNativeQueries annotation
1217
        if (isset($classAnnotations[Annotation\NamedNativeQueries::class])) {
1218
            $namedNativeQueriesAnnot = $classAnnotations[Annotation\NamedNativeQueries::class];
1219
1220
            foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
1221
                $metadata->addNamedNativeQuery(
1222
                    $namedNativeQuery->name,
1223
                    $namedNativeQuery->query,
1224
                    [
1225
                        'resultClass'      => $namedNativeQuery->resultClass,
1226
                        'resultSetMapping' => $namedNativeQuery->resultSetMapping,
1227
                    ]
1228
                );
1229
            }
1230
        }
1231
1232
        // Evaluate @SqlResultSetMappings annotation
1233
        if (isset($classAnnotations[Annotation\SqlResultSetMappings::class])) {
1234
            $sqlResultSetMappingsAnnot = $classAnnotations[Annotation\SqlResultSetMappings::class];
1235
1236
            foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
1237
                $sqlResultSetMapping = $this->convertSqlResultSetMapping($resultSetMapping);
1238
1239
                $metadata->addSqlResultSetMapping($sqlResultSetMapping);
1240
            }
1241
        }
1242
    }
1243
1244
    /**
1245
     * @param array                 $classAnnotations
1246
     * @param \ReflectionClass      $reflectionClass
1247
     * @param Mapping\ClassMetadata $metadata
1248
     *
1249
     * @return void
1250
     */
1251
    private function attachLifecycleCallbacks(
1252
        array $classAnnotations,
1253
        \ReflectionClass $reflectionClass,
1254
        Mapping\ClassMetadata $metadata
1255
    ) : void
1256
    {
1257
        // Evaluate @HasLifecycleCallbacks annotation
1258
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1259
            /* @var $method \ReflectionMethod */
1260
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1261
                foreach ($this->getMethodCallbacks($method) as $callback) {
1262
                    $metadata->addLifecycleCallback($method->getName(), $callback);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1263
                }
1264
            }
1265
        }
1266
    }
1267
1268
    /**
1269
     * @param array                 $classAnnotations
1270
     * @param \ReflectionClass      $reflectionClass
1271
     * @param Mapping\ClassMetadata $metadata
1272
     *
1273
     * @return void
1274
     *
1275
     * @throws \ReflectionException
1276
     * @throws Mapping\MappingException
1277
     */
1278
    private function attachEntityListeners(
1279
        array $classAnnotations,
1280
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed.

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

Loading history...
1281
        Mapping\ClassMetadata $metadata
1282
    ) : void
1283
    {
1284
        // Evaluate @EntityListeners annotation
1285
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1286
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1287
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1288
1289
            foreach ($entityListenersAnnot->value as $item) {
1290
                $listenerClassName = $metadata->fullyQualifiedClassName($item);
1291
1292
                if (! class_exists($listenerClassName)) {
1293
                    throw Mapping\MappingException::entityListenerClassNotFound(
1294
                        $listenerClassName,
1295
                        $metadata->getClassName()
1296
                    );
1297
                }
1298
1299
                $listenerClass = new \ReflectionClass($listenerClassName);
1300
1301
                /* @var $method \ReflectionMethod */
1302
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1303
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1304
                        $metadata->addEntityListener($callback, $listenerClassName, $method->getName());
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1305
                    }
1306
                }
1307
            }
1308
        }
1309
    }
1310
1311
    /**
1312
     * @param array                 $classAnnotations
1313
     * @param \ReflectionClass      $reflectionClass
1314
     * @param Mapping\ClassMetadata $metadata
1315
     *
1316
     * @return void
1317
     *
1318
     * @throws Mapping\MappingException
1319
     */
1320
    private function attachPropertyOverrides(
1321
        array $classAnnotations,
1322
        \ReflectionClass $reflectionClass,
0 ignored issues
show
Unused Code introduced by
The parameter $reflectionClass is not used and could be removed.

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

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