Failed Conditions
Push — develop ( 7b23d3...7f775d )
by Guilherme
63:14
created

AnnotationDriver::isTransient()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
ccs 7
cts 7
cp 1
cc 3
eloc 6
nc 3
nop 1
crap 3
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
        );
269 357
270 357
        // Evaluate @Cache annotation
271 357
        if (isset($classAnnotations[Annotation\Cache::class])) {
272 357
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
273 69
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
274
275
            $classMetadata->setCache($cache);
276 357
        }
277 357
278
        // Evaluate annotations on properties/fields
279
        /* @var $reflProperty \ReflectionProperty */
280 357
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
281 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...
282 12
                continue;
283 12
            }
284
285
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
286
            $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...
287 356
                $propertyAnnotations,
288
                $reflectionProperty,
289 356
                $classMetadata
290 136
            );
291 355
292 21
            if (! $property) {
293 21
                continue;
294
            }
295
296
            $metadata->addProperty($property);
297
        }
298
299 356
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata);
300 347
301
        return $classMetadata;
302
    }
303
304 347
    /**
305
     * @param array                 $classAnnotations
306 347
     * @param \ReflectionClass      $reflectionClass
307 340
     * @param Mapping\ClassMetadata $metadata
308
     *
309
     * @return Mapping\ClassMetadata
310 347
     *
311 291
     * @throws Mapping\MappingException
312 291
     */
313
    private function convertClassAnnotationsToClassMetadata(
314
        array $classAnnotations,
315
        \ReflectionClass $reflectionClass,
316 347
        Mapping\ClassMetadata $metadata
317
    ) : Mapping\ClassMetadata
318 347
    {
319 13
        switch (true) {
320
            case isset($classAnnotations[Annotation\Entity::class]):
321
                return $this->convertClassAnnotationsToEntityClassMetadata(
322
                    $classAnnotations,
323 347
                    $reflectionClass,
324 8
                    $metadata
325 8
                );
326 8
327 8
                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...
328
329 344
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
330
                return $this->convertClassAnnotationsToMappedSuperClassMetadata(
331 344
                    $classAnnotations,
332 2
                    $reflectionClass,
333 347
                    $metadata
334
                );
335
336 255
            case isset($classAnnotations[Annotation\Embeddable::class]):
337 106
                return $this->convertClassAnnotationsToEmbeddableClassMetadata(
338 9
                    $classAnnotations,
339
                    $reflectionClass,
340
                    $metadata
341 106
                );
342 106
343 106
            default:
344 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...
345 106
        }
346 106
    }
347 106
348 106
    /**
349 206
     * @param array                 $classAnnotations
350 102
     * @param \ReflectionClass      $reflectionClass
351 102
     * @param Mapping\ClassMetadata $metadata
352 102
     *
353 102
     * @return Mapping\ClassMetadata
354 102
     *
355 102
     * @throws Mapping\MappingException
356
     * @throws \UnexpectedValueException
357 102
     */
358 15
    private function convertClassAnnotationsToEntityClassMetadata(
359
        array $classAnnotations,
360
        \ReflectionClass $reflectionClass,
361 102
        Mapping\ClassMetadata $metadata
362 203
    )
363 133
    {
364 30
        /** @var Annotation\Entity $entityAnnot */
365
        $entityAnnot  = $classAnnotations[Annotation\Entity::class];
366
367 133
        if ($entityAnnot->repositoryClass !== null) {
368 133
            $metadata->setCustomRepositoryClassName(
369 133
                $metadata->fullyQualifiedClassName($entityAnnot->repositoryClass)
370 133
            );
371 133
        }
372 133
373 113
        if ($entityAnnot->readOnly) {
374 84
            $metadata->asReadOnly();
375
        }
376 84
377
        // Evaluate Table annotation
378 65
        if (isset($classAnnotations[Annotation\Table::class])) {
379 65
            $tableAnnot = $classAnnotations[Annotation\Table::class];
380
            $table      = $this->convertTableAnnotationToTableMetadata($tableAnnot);
381
382 65
            $metadata->setTable($table);
383 64
        }
384
385
        // Evaluate @ChangeTrackingPolicy annotation
386 65
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
387 64
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
388
389
            $metadata->setChangeTrackingPolicy(
390
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
391 84
            );
392 84
        }
393 84
394 84
        // Evaluate @InheritanceType annotation
395 84
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
396 84
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
397 84
398 84
            $metadata->setInheritanceType(
399
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
400 84
            );
401 3
402
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
403
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
404 84
            }
405 44
        }
406 10
407 10
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
408
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
409 355
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
410
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
411
412
        return $metadata;
413
    }
414 357
415 4
    /**
416
     * @param array                 $classAnnotations
417 4
     * @param \ReflectionClass      $reflectionClass
418 4
     * @param Mapping\ClassMetadata $metadata
419 4
     *
420
     * @return Mapping\ClassMetadata
421
     */
422 4
    private function convertClassAnnotationsToMappedSuperClassMetadata(
423 3
        array $classAnnotations,
424
        \ReflectionClass $reflectionClass,
425 3
        Mapping\ClassMetadata $metadata
426 3
    ) : Mapping\ClassMetadata
427
    {
428
        /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */
429 3
        $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class];
430
431
        if ($mappedSuperclassAnnot->repositoryClass !== null) {
432
            $metadata->setCustomRepositoryClassName(
433 4
                $metadata->fullyQualifiedClassName($mappedSuperclassAnnot->repositoryClass)
434 2
            );
435
        }
436 2
437 2
        $metadata->isMappedSuperclass = true;
438
        $metadata->isEmbeddedClass = false;
439
440 2
        $this->attachNamedQueries($classAnnotations, $reflectionClass, $metadata);
441 2
        $this->attachNamedNativeQueries($classAnnotations, $reflectionClass, $metadata);
442
        $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata);
443
        $this->attachEntityListeners($classAnnotations, $reflectionClass, $metadata);
444 2
445 2
        return $metadata;
446
    }
447
448 2
    /**
449
     * @param array                 $classAnnotations
450
     * @param \ReflectionClass      $reflectionClass
451
     * @param Mapping\ClassMetadata $metadata
452 4
     *
453 1
     * @return Mapping\ClassMetadata
454
     */
455
    private function convertClassAnnotationsToEmbeddableClassMetadata(
456 4
        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...
457
        \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...
458
        Mapping\ClassMetadata $metadata
459
    ) : Mapping\ClassMetadata
460
    {
461 357
        $metadata->isMappedSuperclass = false;
462 3
        $metadata->isEmbeddedClass = true;
463
464 3
        return $metadata;
465 3
    }
466
467 3
    /**
468
     * @param array                 $propertyAnnotations
469
     * @param \ReflectionProperty   $reflectionProperty
470
     * @param Mapping\ClassMetadata $metadata
471
     *
472 357
     * @todo guilhermeblanco Remove nullable typehint once embeddables are back
473 10
     *
474
     * @return Mapping\Property|null
475 10
     */
476 10
    private function convertPropertyAnnotationsToProperty(
477
        array $propertyAnnotations,
478 10
        \ReflectionProperty $reflectionProperty,
479
        Mapping\ClassMetadata $metadata
480
    ) : ?Mapping\Property
481
    {
482 10
        switch (true) {
483 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...
484
                return $this->convertReflectionPropertyToFieldMetadata(
485
                    $reflectionProperty,
486 10
                    $propertyAnnotations
487
                );
488 10
489 10
            case isset($propertyAnnotations[Annotation\OneToOne::class]):
490
                return $this->convertReflectionPropertyToOneToOneAssociationMetadata(
491 10
                    $reflectionProperty,
492 10
                    $propertyAnnotations,
493
                    $metadata
494
                );
495
496
            case isset($propertyAnnotations[Annotation\ManyToOne::class]):
497 10
                return $this->convertReflectionPropertyToManyToOneAssociationMetadata(
498 10
                    $reflectionProperty,
499
                    $propertyAnnotations,
500
                    $metadata
501
                );
502
503
            case isset($propertyAnnotations[Annotation\OneToMany::class]):
504 357
                return $this->convertReflectionPropertyToOneToManyAssociationMetadata(
505
                    $reflectionProperty,
506 18
                    $propertyAnnotations,
507 17
                    $metadata
508 17
                );
509
510
            case isset($propertyAnnotations[Annotation\ManyToMany::class]):
511
                return $this->convertReflectionPropertyToManyToManyAssociationMetadata(
512 357
                    $reflectionProperty,
513
                    $propertyAnnotations,
514
                    $metadata
515
                );
516
517
            case isset($propertyAnnotations[Annotation\Embedded::class]):
518
                return null;
519
520
            default:
521
                return new Mapping\TransientMetadata($reflectionProperty->getName());
522
        }
523
    }
524 241
525
    private function convertReflectionPropertyToFieldMetadata(
526 241
        \ReflectionProperty $reflProperty,
527
        array $propertyAnnotations
528 241
    )
529
    {
530
        $className   = $reflProperty->getDeclaringClass()->getName();
0 ignored issues
show
introduced by
Consider using $reflProperty->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
531
        $fieldName   = $reflProperty->getName();
532 241
        $isVersioned = isset($propertyAnnotations[Annotation\Version::class]);
533
        $columnAnnot = $propertyAnnotations[Annotation\Column::class];
534
535
        if ($columnAnnot->type == null) {
536
            throw Mapping\MappingException::propertyTypeIsRequired($className, $fieldName);
537
        }
538
539
        $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata($columnAnnot, $fieldName, $isVersioned);
540
541
        // Check for Id
542 27
        if (isset($propertyAnnotations[Annotation\Id::class])) {
543
            $fieldMetadata->setPrimaryKey(true);
544 27
        }
545 27
546
        // Check for GeneratedValue strategy
547 27
        if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) {
548 21
            $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class];
549 14
            $strategy = strtoupper($generatedValueAnnot->strategy);
550
            $idGeneratorType = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy));
551
552 21
            if ($idGeneratorType !== Mapping\GeneratorType::NONE) {
553 10
                $idGeneratorDefinition = [];
554
555
                // Check for CustomGenerator/SequenceGenerator/TableGenerator definition
556 21
                switch (true) {
557 11
                    case isset($propertyAnnotations[Annotation\SequenceGenerator::class]):
558
                        $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class];
559
560 21
                        $idGeneratorDefinition = [
561 7
                            'sequenceName' => $seqGeneratorAnnot->sequenceName,
562
                            'allocationSize' => $seqGeneratorAnnot->allocationSize,
563
                        ];
564 21
565 8
                        break;
566
567
                    case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]):
568 21
                        $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class];
569 6
570
                        $idGeneratorDefinition = [
571
                            'class' => $customGeneratorAnnot->class,
572 21
                            'arguments' => $customGeneratorAnnot->arguments,
573 11
                        ];
574
575
                        break;
576 21
577 21
                    /* @todo If it is not supported, why does this exist? */
578
                    case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']):
579
                        throw Mapping\MappingException::tableIdGeneratorNotImplemented($className);
580
                }
581 27
582
                $fieldMetadata->setValueGenerator(new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition));
583
            }
584
        }
585
586
        return $fieldMetadata;
587
    }
588
589
    private function convertReflectionPropertyToOneToOneAssociationMetadata(
590
        \ReflectionProperty $reflProperty,
591 173
        array $propertyAnnotations,
592
        Mapping\ClassMetadata $metadata
593
    )
594 173
    {
595 173
        $className     = $reflProperty->getDeclaringClass()->getName();
0 ignored issues
show
introduced by
Consider using $reflProperty->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
596 173
        $fieldName     = $reflProperty->getName();
597 173
        $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class];
598 173
        $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName);
599 173
        $targetEntity  = $metadata->fullyQualifiedClassName($oneToOneAnnot->targetEntity);
600
601
        $assocMetadata->setTargetEntity($targetEntity);
602
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade));
603
        $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval);
604
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch));
605
606
        if (! empty($oneToOneAnnot->mappedBy)) {
607
            $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy);
608
        }
609
610
        if (! empty($oneToOneAnnot->inversedBy)) {
611 347
            $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy);
612
        }
613
614 347
        // Check for Id
615 347
        if (isset($propertyAnnotations[Annotation\Id::class])) {
616 347
            $assocMetadata->setPrimaryKey(true);
617 347
        }
618 347
619
        // Check for Cache
620
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
621 347
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
622 9
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata, $fieldName);
623
624
            $assocMetadata->setCache($cacheMetadata);
625 347
        }
626 80
627
        // Check for JoinColumn/JoinColumns annotations
628
        switch (true) {
629 347
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
630 5
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
631
632
                $assocMetadata->addJoinColumn(
633 347
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
634
                );
635
636
                break;
637
638
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
639
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
640
641
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
642
                    $assocMetadata->addJoinColumn(
643
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
644
                    );
645
                }
646
647
                break;
648
        }
649
650
        return $assocMetadata;
651
    }
652
653
    private function convertReflectionPropertyToManyToOneAssociationMetadata(
654
        \ReflectionProperty $reflProperty,
655
        array $propertyAnnotations,
656
        Mapping\ClassMetadata $metadata
657
    )
658
    {
659
        $className      = $reflProperty->getDeclaringClass()->getName();
0 ignored issues
show
introduced by
Consider using $reflProperty->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
660
        $fieldName      = $reflProperty->getName();
661
        $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class];
662
        $assocMetadata  = new Mapping\ManyToOneAssociationMetadata($fieldName);
663
        $targetEntity  = $metadata->fullyQualifiedClassName($manyToOneAnnot->targetEntity);
664
665
        $assocMetadata->setTargetEntity($targetEntity);
666
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade));
667
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch));
668
669
        if (! empty($manyToOneAnnot->inversedBy)) {
670
            $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy);
671
        }
672
673
        // Check for Id
674
        if (isset($propertyAnnotations[Annotation\Id::class])) {
675
            $assocMetadata->setPrimaryKey(true);
676
        }
677
678
        // Check for Cache
679
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
680
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
681
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata, $fieldName);
682
683
            $assocMetadata->setCache($cacheMetadata);
684
        }
685
686
        // Check for JoinColumn/JoinColumns annotations
687
        switch (true) {
688
            case isset($propertyAnnotations[Annotation\JoinColumn::class]):
689
                $joinColumnAnnot = $propertyAnnotations[Annotation\JoinColumn::class];
690
691
                $assocMetadata->addJoinColumn(
692
                    $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
693
                );
694
695
                break;
696
697
            case isset($propertyAnnotations[Annotation\JoinColumns::class]):
698
                $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class];
699
700
                foreach ($joinColumnsAnnot->value as $joinColumnAnnot) {
701
                    $assocMetadata->addJoinColumn(
702
                        $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot)
703
                    );
704
                }
705
706
                break;
707
        }
708
709
        return $assocMetadata;
710
    }
711
712
    private function convertReflectionPropertyToOneToManyAssociationMetadata(
713
        \ReflectionProperty $reflProperty,
714
        array $propertyAnnotations,
715
        Mapping\ClassMetadata $metadata
716
    )
717
    {
718
        $className      = $reflProperty->getDeclaringClass()->getName();
0 ignored issues
show
introduced by
Consider using $reflProperty->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
719
        $fieldName      = $reflProperty->getName();
720
        $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class];
721
        $assocMetadata  = new Mapping\OneToManyAssociationMetadata($fieldName);
722
        $targetEntity  = $metadata->fullyQualifiedClassName($oneToManyAnnot->targetEntity);
723
724
        $assocMetadata->setTargetEntity($targetEntity);
725
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade));
726
        $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval);
727
        $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch));
728
729
        if (! empty($oneToManyAnnot->mappedBy)) {
730
            $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy);
731
        }
732
733
        if (! empty($oneToManyAnnot->indexBy)) {
734
            $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy);
735
        }
736
737
        // Check for OrderBy
738
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
739
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
740
741
            $assocMetadata->setOrderBy($orderByAnnot->value);
742
        }
743
744
        // Check for Id
745
        if (isset($propertyAnnotations[Annotation\Id::class])) {
746
            $assocMetadata->setPrimaryKey(true);
747
        }
748
749
        // Check for Cache
750
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
751
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
752
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata, $fieldName);
753
754
            $assocMetadata->setCache($cacheMetadata);
755
        }
756
757
        return $assocMetadata;
758
    }
759
760
    private function convertReflectionPropertyToManyToManyAssociationMetadata(
761
        \ReflectionProperty $reflProperty,
762
        array $propertyAnnotations,
763
        Mapping\ClassMetadata $metadata
764
    )
765
    {
766
        $className       = $reflProperty->getDeclaringClass()->getName();
0 ignored issues
show
introduced by
Consider using $reflProperty->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
767
        $fieldName       = $reflProperty->getName();
768
        $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class];
769
        $assocMetadata   = new Mapping\ManyToManyAssociationMetadata($fieldName);
770
        $targetEntity    = $metadata->fullyQualifiedClassName($manyToManyAnnot->targetEntity);
771
772
        $assocMetadata->setTargetEntity($targetEntity);
773
        $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade));
774
        $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval);
775
        $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch));
776
777
        if (! empty($manyToManyAnnot->mappedBy)) {
778
            $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy);
779
        }
780
781
        if (! empty($manyToManyAnnot->inversedBy)) {
782
            $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy);
783
        }
784
785
        if (! empty($manyToManyAnnot->indexBy)) {
786
            $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy);
787
        }
788
789
        // Check for JoinTable
790
        if (isset($propertyAnnotations[Annotation\JoinTable::class])) {
791
            $joinTableAnnot    = $propertyAnnotations[Annotation\JoinTable::class];
792
            $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
793
794
            $assocMetadata->setJoinTable($joinTableMetadata);
795
        }
796
797
        // Check for OrderBy
798
        if (isset($propertyAnnotations[Annotation\OrderBy::class])) {
799
            $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class];
800
801
            $assocMetadata->setOrderBy($orderByAnnot->value);
802
        }
803
804
        // Check for Id
805
        if (isset($propertyAnnotations[Annotation\Id::class])) {
806
            $assocMetadata->setPrimaryKey(true);
807
        }
808
809
        // Check for Cache
810
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
811
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
812
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata, $fieldName);
813
814
            $assocMetadata->setCache($cacheMetadata);
815
        }
816
817
        return $assocMetadata;
818
    }
819
820
    /**
821
     * Parse the given Column as FieldMetadata
822
     *
823
     * @param Annotation\Column $columnAnnot
824
     * @param string            $fieldName
825
     * @param bool              $isVersioned
826
     *
827
     * @return Mapping\FieldMetadata
828
     */
829
    private function convertColumnAnnotationToFieldMetadata(
830
        Annotation\Column $columnAnnot,
831
        string $fieldName,
832
        bool $isVersioned
833
    ) : Mapping\FieldMetadata
834
    {
835
        $fieldMetadata = $isVersioned
836
            ? new Mapping\VersionFieldMetadata($fieldName)
837
            : new Mapping\FieldMetadata($fieldName)
838
        ;
839
840
        $fieldMetadata->setType(Type::getType($columnAnnot->type));
841
842
        if (! empty($columnAnnot->name)) {
843
            $fieldMetadata->setColumnName($columnAnnot->name);
844
        }
845
846
        if (! empty($columnAnnot->columnDefinition)) {
847
            $fieldMetadata->setColumnDefinition($columnAnnot->columnDefinition);
848
        }
849
850
        if (! empty($columnAnnot->length)) {
851
            $fieldMetadata->setLength($columnAnnot->length);
852
        }
853
854
        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...
855
            $fieldMetadata->setOptions($columnAnnot->options);
856
        }
857
858
        $fieldMetadata->setScale($columnAnnot->scale);
859
        $fieldMetadata->setPrecision($columnAnnot->precision);
860
        $fieldMetadata->setNullable($columnAnnot->nullable);
861
        $fieldMetadata->setUnique($columnAnnot->unique);
862
863
        return $fieldMetadata;
864
    }
865
866
    /**
867
     * Parse the given Table as TableMetadata
868
     *
869
     * @param Annotation\Table $tableAnnot
870
     *
871
     * @return Mapping\TableMetadata
872
     */
873
    private function convertTableAnnotationToTableMetadata(
874
        Annotation\Table $tableAnnot
875
    ) : Mapping\TableMetadata
876
    {
877
        $table = new Mapping\TableMetadata();
878
879
        if (! empty($tableAnnot->name)) {
880
            $table->setName($tableAnnot->name);
881
        }
882
883
        if (! empty($tableAnnot->schema)) {
884
            $table->setSchema($tableAnnot->schema);
885
        }
886
887
        foreach ($tableAnnot->options as $optionName => $optionValue) {
888
            $table->addOption($optionName, $optionValue);
889
        }
890
891
        foreach ($tableAnnot->indexes as $indexAnnot) {
892
            $table->addIndex([
893
                'name'    => $indexAnnot->name,
894
                'columns' => $indexAnnot->columns,
895
                'unique'  => $indexAnnot->unique,
896
                'options' => $indexAnnot->options,
897
                'flags'   => $indexAnnot->flags,
898
            ]);
899
        }
900
901
        foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
902
            $table->addUniqueConstraint([
903
                'name'    => $uniqueConstraintAnnot->name,
904
                'columns' => $uniqueConstraintAnnot->columns,
905
                'options' => $uniqueConstraintAnnot->options,
906
                'flags'   => $uniqueConstraintAnnot->flags,
907
            ]);
908
        }
909
910
        return $table;
911
    }
912
913
    /**
914
     * Parse the given JoinTable as JoinTableMetadata
915
     *
916
     * @param Annotation\JoinTable $joinTableAnnot
917
     *
918
     * @return Mapping\JoinTableMetadata
919
     */
920
    private function convertJoinTableAnnotationToJoinTableMetadata(
921
        Annotation\JoinTable $joinTableAnnot
922
    ) : Mapping\JoinTableMetadata
923
    {
924
        $joinTable = new Mapping\JoinTableMetadata();
925
926
        if (! empty($joinTableAnnot->name)) {
927
            $joinTable->setName($joinTableAnnot->name);
928
        }
929
930
        if (! empty($joinTableAnnot->schema)) {
931
            $joinTable->setSchema($joinTableAnnot->schema);
932
        }
933
934
        foreach ($joinTableAnnot->joinColumns as $joinColumnAnnot) {
935
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
936
937
            $joinTable->addJoinColumn($joinColumn);
938
        }
939
940
        foreach ($joinTableAnnot->inverseJoinColumns as $joinColumnAnnot) {
941
            $joinColumn = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
942
943
            $joinTable->addInverseJoinColumn($joinColumn);
944
        }
945
946
        return $joinTable;
947
    }
948
949
    /**
950
     * Parse the given JoinColumn as JoinColumnMetadata
951
     *
952
     * @param Annotation\JoinColumn $joinColumnAnnot
953
     *
954
     * @return Mapping\JoinColumnMetadata
955
     */
956
    private function convertJoinColumnAnnotationToJoinColumnMetadata(
957
        Annotation\JoinColumn $joinColumnAnnot
958
    ) : Mapping\JoinColumnMetadata
959
    {
960
        $joinColumn = new Mapping\JoinColumnMetadata();
961
962
        // @todo Remove conditionals for name and referencedColumnName once naming strategy is brought into drivers
963
        if (! empty($joinColumnAnnot->name)) {
964
            $joinColumn->setColumnName($joinColumnAnnot->name);
965
        }
966
967
        if (! empty($joinColumnAnnot->referencedColumnName)) {
968
            $joinColumn->setReferencedColumnName($joinColumnAnnot->referencedColumnName);
969
        }
970
971
        $joinColumn->setNullable($joinColumnAnnot->nullable);
972
        $joinColumn->setUnique($joinColumnAnnot->unique);
973
974
        if (! empty($joinColumnAnnot->fieldName)) {
975
            $joinColumn->setAliasedName($joinColumnAnnot->fieldName);
976
        }
977
978
        if (! empty($joinColumnAnnot->columnDefinition)) {
979
            $joinColumn->setColumnDefinition($joinColumnAnnot->columnDefinition);
980
        }
981
982
        if ($joinColumnAnnot->onDelete) {
983
            $joinColumn->setOnDelete(strtoupper($joinColumnAnnot->onDelete));
984
        }
985
986
        return $joinColumn;
987
    }
988
989
    /**
990
     * Parse the given Cache as CacheMetadata
991
     *
992
     * @param Annotation\Cache      $cacheAnnot
993
     * @param Mapping\ClassMetadata $metadata
994
     * @param null|string           $fieldName
995
     *
996
     * @return Mapping\CacheMetadata
997
     */
998
    private function convertCacheAnnotationToCacheMetadata(
999
        Annotation\Cache $cacheAnnot,
1000
        Mapping\ClassMetadata $metadata,
1001
        $fieldName = null
1002
    ) : Mapping\CacheMetadata
1003
    {
1004
        $baseRegion    = strtolower(str_replace('\\', '_', $metadata->getRootClassName()));
1005
        $defaultRegion = $baseRegion . ($fieldName ? '__' . $fieldName : '');
1006
1007
        $usage = constant(sprintf('%s::%s', Mapping\CacheUsage::class, $cacheAnnot->usage));
1008
        $region = $cacheAnnot->region ?: $defaultRegion;
1009
1010
        return new Mapping\CacheMetadata($usage, $region);
1011
    }
1012
1013
    /**
1014
     * @param Annotation\SqlResultSetMapping $resultSetMapping
1015
     *
1016
     * @return array
1017
     */
1018
    private function convertSqlResultSetMapping(Annotation\SqlResultSetMapping $resultSetMapping)
1019
    {
1020
        $entities = [];
1021
1022
        foreach ($resultSetMapping->entities as $entityResultAnnot) {
1023
            $entityResult = [
1024
                'fields'                => [],
1025
                'entityClass'           => $entityResultAnnot->entityClass,
1026
                'discriminatorColumn'   => $entityResultAnnot->discriminatorColumn,
1027
            ];
1028
1029
            foreach ($entityResultAnnot->fields as $fieldResultAnnot) {
1030
                $entityResult['fields'][] = [
1031
                    'name'      => $fieldResultAnnot->name,
1032
                    'column'    => $fieldResultAnnot->column
1033
                ];
1034
            }
1035
1036
            $entities[] = $entityResult;
1037
        }
1038
1039
        $columns = [];
1040
1041
        foreach ($resultSetMapping->columns as $columnResultAnnot) {
1042
            $columns[] = [
1043
                'name' => $columnResultAnnot->name,
1044
            ];
1045
        }
1046
1047
        return [
1048
            'name'     => $resultSetMapping->name,
1049
            'entities' => $entities,
1050
            'columns'  => $columns
1051
        ];
1052
    }
1053
1054
    /**
1055
     * @param array                 $classAnnotations
1056
     * @param \ReflectionClass      $reflectionClass
1057
     * @param Mapping\ClassMetadata $metadata
1058
     *
1059
     * @return void
1060
     *
1061
     * @throws Mapping\MappingException
1062
     */
1063
    private function attachDiscriminatorColumn(
1064
        array $classAnnotations,
1065
        \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...
1066
        Mapping\ClassMetadata $metadata
1067
    ) : void
1068
    {
1069
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1070
1071
        $discriminatorColumn->setTableName($metadata->getTableName());
1072
        $discriminatorColumn->setColumnName('dtype');
1073
        $discriminatorColumn->setType(Type::getType('string'));
1074
        $discriminatorColumn->setLength(255);
1075
1076
        // Evaluate DiscriminatorColumn annotation
1077
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1078
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1079
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1080
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1081
                ? $discriminatorColumnAnnotation->type
1082
                : 'string';
1083
1084
            $discriminatorColumn->setType(Type::getType($typeName));
1085
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1086
1087
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1088
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1089
            }
1090
1091
            if (! empty($discriminatorColumnAnnotation->length)) {
1092
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1093
            }
1094
        }
1095
1096
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1097
1098
        // Evaluate DiscriminatorMap annotation
1099
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1100
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1101
            $discriminatorMap           = array_map(
1102
                function ($className) use ($metadata) {
1103
                    return $metadata->fullyQualifiedClassName($className);
1104
                },
1105
                $discriminatorMapAnnotation->value
1106
            );
1107
1108
            $metadata->setDiscriminatorMap($discriminatorMap);
1109
        }
1110
    }
1111
1112
    /**
1113
     * @param array                 $classAnnotations
1114
     * @param \ReflectionClass      $reflectionClass
1115
     * @param Mapping\ClassMetadata $metadata
1116
     *
1117
     * @return void
1118
     *
1119
     * @throws \UnexpectedValueException
1120
     * @throws Mapping\MappingException
1121
     */
1122
    private function attachNamedQueries(
1123
        array $classAnnotations,
1124
        \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...
1125
        Mapping\ClassMetadata $metadata
1126
    ) : void
1127
    {
1128
        // Evaluate @NamedQueries annotation
1129
        if (isset($classAnnotations[Annotation\NamedQueries::class])) {
1130
            $namedQueriesAnnot = $classAnnotations[Annotation\NamedQueries::class];
1131
1132
            if (! is_array($namedQueriesAnnot->value)) {
1133
                throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1134
            }
1135
1136
            foreach ($namedQueriesAnnot->value as $namedQuery) {
1137
                if (! ($namedQuery instanceof Annotation\NamedQuery)) {
1138
                    throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1139
                }
1140
1141
                $metadata->addNamedQuery($namedQuery->name, $namedQuery->query);
1142
            }
1143
        }
1144
    }
1145
1146
    /**
1147
     * @param array                 $classAnnotations
1148
     * @param \ReflectionClass      $reflectionClass
1149
     * @param Mapping\ClassMetadata $metadata
1150
     *
1151
     * @return void
1152
     */
1153
    private function attachNamedNativeQueries(
1154
        array $classAnnotations,
1155
        \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...
1156
        Mapping\ClassMetadata $metadata
1157
    ) : void
1158
    {
1159
        // Evaluate @NamedNativeQueries annotation
1160
        if (isset($classAnnotations[Annotation\NamedNativeQueries::class])) {
1161
            $namedNativeQueriesAnnot = $classAnnotations[Annotation\NamedNativeQueries::class];
1162
1163
            foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
1164
                $metadata->addNamedNativeQuery(
1165
                    $namedNativeQuery->name,
1166
                    $namedNativeQuery->query,
1167
                    [
1168
                        'resultClass'      => $namedNativeQuery->resultClass,
1169
                        'resultSetMapping' => $namedNativeQuery->resultSetMapping,
1170
                    ]
1171
                );
1172
            }
1173
        }
1174
1175
        // Evaluate @SqlResultSetMappings annotation
1176
        if (isset($classAnnotations[Annotation\SqlResultSetMappings::class])) {
1177
            $sqlResultSetMappingsAnnot = $classAnnotations[Annotation\SqlResultSetMappings::class];
1178
1179
            foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
1180
                $sqlResultSetMapping = $this->convertSqlResultSetMapping($resultSetMapping);
1181
1182
                $metadata->addSqlResultSetMapping($sqlResultSetMapping);
1183
            }
1184
        }
1185
    }
1186
1187
    /**
1188
     * @param array                 $classAnnotations
1189
     * @param \ReflectionClass      $reflectionClass
1190
     * @param Mapping\ClassMetadata $metadata
1191
     *
1192
     * @return void
1193
     */
1194
    private function attachLifecycleCallbacks(
1195
        array $classAnnotations,
1196
        \ReflectionClass $reflectionClass,
1197
        Mapping\ClassMetadata $metadata
1198
    ) : void
1199
    {
1200
        // Evaluate @HasLifecycleCallbacks annotation
1201
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1202
            /* @var $method \ReflectionMethod */
1203
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1204
                foreach ($this->getMethodCallbacks($method) as $callback) {
1205
                    $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...
1206
                }
1207
            }
1208
        }
1209
    }
1210
1211
    /**
1212
     * @param array                 $classAnnotations
1213
     * @param \ReflectionClass      $reflectionClass
1214
     * @param Mapping\ClassMetadata $metadata
1215
     *
1216
     * @return void
1217
     *
1218
     * @throws \ReflectionException
1219
     * @throws Mapping\MappingException
1220
     */
1221
    private function attachEntityListeners(
1222
        array $classAnnotations,
1223
        \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...
1224
        Mapping\ClassMetadata $metadata
1225
    ) : void
1226
    {
1227
        // Evaluate @EntityListeners annotation
1228
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1229
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1230
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1231
1232
            foreach ($entityListenersAnnot->value as $item) {
1233
                $listenerClassName = $metadata->fullyQualifiedClassName($item);
1234
1235
                if (! class_exists($listenerClassName)) {
1236
                    throw Mapping\MappingException::entityListenerClassNotFound(
1237
                        $listenerClassName,
1238
                        $metadata->getClassName()
1239
                    );
1240
                }
1241
1242
                $listenerClass = new \ReflectionClass($listenerClassName);
1243
1244
                /* @var $method \ReflectionMethod */
1245
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1246
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1247
                        $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...
1248
                    }
1249
                }
1250
            }
1251
        }
1252
    }
1253
1254
    /**
1255
     * @param array                 $classAnnotations
1256
     * @param \ReflectionClass      $reflectionClass
1257
     * @param Mapping\ClassMetadata $metadata
1258
     *
1259
     * @return void
1260
     *
1261
     * @throws Mapping\MappingException
1262
     */
1263
    private function attachPropertyOverrides(
1264
        array $classAnnotations,
1265
        \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...
1266
        Mapping\ClassMetadata $metadata
1267
    ) : void
1268
    {
1269
        // Evaluate AssociationOverrides annotation
1270
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1271
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1272
1273
            foreach ($associationOverridesAnnot->value as $associationOverride) {
1274
                $fieldName = $associationOverride->name;
1275
                $property  = $metadata->getProperty($fieldName);
1276
1277
                if (! $property) {
1278
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1279
                }
1280
1281
                $existingClass = get_class($property);
1282
                $override      = new $existingClass($fieldName);
1283
1284
                // Check for JoinColumn/JoinColumns annotations
1285
                if ($associationOverride->joinColumns) {
1286
                    $joinColumns = [];
1287
1288
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1289
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1290
                    }
1291
1292
                    $override->setJoinColumns($joinColumns);
1293
                }
1294
1295
                // Check for JoinTable annotations
1296
                if ($associationOverride->joinTable) {
1297
                    $joinTableAnnot    = $associationOverride->joinTable;
1298
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1299
1300
                    $override->setJoinTable($joinTableMetadata);
1301
                }
1302
1303
                // Check for inversedBy
1304
                if ($associationOverride->inversedBy) {
1305
                    $override->setInversedBy($associationOverride->inversedBy);
1306
                }
1307
1308
                // Check for fetch
1309
                if ($associationOverride->fetch) {
1310
                    $override->setFetchMode(
1311
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1312
                    );
1313
                }
1314
1315
                $metadata->setPropertyOverride($override);
1316
            }
1317
        }
1318
1319
        // Evaluate AttributeOverrides annotation
1320
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1321
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1322
1323
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1324
                $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1325
                    $attributeOverrideAnnot->column,
1326
                    $attributeOverrideAnnot->name,
1327
                    false
1328
                );
1329
1330
                $metadata->setPropertyOverride($fieldMetadata);
1331
            }
1332
        }
1333
    }
1334
1335
    /**
1336
     * Attempts to resolve the cascade modes.
1337
     *
1338
     * @param string $className        The class name.
1339
     * @param string $fieldName        The field name.
1340
     * @param array  $originalCascades The original unprocessed field cascades.
1341
     *
1342
     * @return array The processed field cascades.
1343
     *
1344
     * @throws Mapping\MappingException If a cascade option is not valid.
1345
     */
1346
    private function getCascade(string $className, string $fieldName, array $originalCascades)
1347
    {
1348
        $cascadeTypes = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1349
        $cascades     = array_map('strtolower', $originalCascades);
1350
1351
        if (in_array('all', $cascades)) {
1352
            $cascades = $cascadeTypes;
1353
        }
1354
1355
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1356
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1357
1358
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1359
        }
1360
1361
        return $cascades;
1362
    }
1363
1364
    /**
1365
     * Attempts to resolve the fetch mode.
1366
     *
1367
     * @param string $className The class name.
1368
     * @param string $fetchMode The fetch mode.
1369
     *
1370
     * @return string The fetch mode as defined in ClassMetadata.
1371
     *
1372
     * @throws Mapping\MappingException If the fetch mode is not valid.
1373
     */
1374
    private function getFetchMode($className, $fetchMode) : string
1375
    {
1376
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1377
1378
        if ( ! defined($fetchModeConstant)) {
1379
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1380
        }
1381
1382
        return constant($fetchModeConstant);
1383
    }
1384
1385
    /**
1386
     * Parses the given method.
1387
     *
1388
     * @param \ReflectionMethod $method
1389
     *
1390
     * @return array
1391
     */
1392
    private function getMethodCallbacks(\ReflectionMethod $method) : array
1393
    {
1394
        $annotations = $this->getMethodAnnotations($method);
1395
        $events      = [
1396
            Events::prePersist  => Annotation\PrePersist::class,
1397
            Events::postPersist => Annotation\PostPersist::class,
1398
            Events::preUpdate   => Annotation\PreUpdate::class,
1399
            Events::postUpdate  => Annotation\PostUpdate::class,
1400
            Events::preRemove   => Annotation\PreRemove::class,
1401
            Events::postRemove  => Annotation\PostRemove::class,
1402
            Events::postLoad    => Annotation\PostLoad::class,
1403
            Events::preFlush    => Annotation\PreFlush::class,
1404
        ];
1405
1406
        // Check for callbacks
1407
        $callbacks = [];
1408
1409
        foreach ($events as $eventName => $annotationClassName) {
1410
            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...
1411
                $callbacks[] = $eventName;
1412
            }
1413
        }
1414
1415
        return $callbacks;
1416
    }
1417
1418
    /**
1419
     * @param \ReflectionClass $reflectionClass
1420
     *
1421
     * @return array
1422
     */
1423
    private function getClassAnnotations(\ReflectionClass $reflectionClass) : array
1424
    {
1425
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1426
1427
        foreach ($classAnnotations as $key => $annot) {
1428
            if (! is_numeric($key)) {
1429
                continue;
1430
            }
1431
1432
            $classAnnotations[get_class($annot)] = $annot;
1433
        }
1434
1435
        return $classAnnotations;
1436
    }
1437
1438
    /**
1439
     * @param \ReflectionProperty $reflectionProperty
1440
     *
1441
     * @return array
1442
     */
1443
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty) : array
1444
    {
1445
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1446
1447
        foreach ($propertyAnnotations as $key => $annot) {
1448
            if (! is_numeric($key)) {
1449
                continue;
1450
            }
1451
1452
            $propertyAnnotations[get_class($annot)] = $annot;
1453
        }
1454
1455
        return $propertyAnnotations;
1456
    }
1457
1458
    /**
1459
     * @param \ReflectionMethod $reflectionMethod
1460
     *
1461
     * @return array
1462
     */
1463
    private function getMethodAnnotations(\ReflectionMethod $reflectionMethod) : array
1464
    {
1465
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1466
1467
        foreach ($methodAnnotations as $key => $annot) {
1468
            if (! is_numeric($key)) {
1469
                continue;
1470
            }
1471
1472
            $methodAnnotations[get_class($annot)] = $annot;
1473
        }
1474
1475
        return $methodAnnotations;
1476
    }
1477
1478
    /**
1479
     * Factory method for the Annotation Driver.
1480
     *
1481
     * @param array|string          $paths
1482
     * @param AnnotationReader|null $reader
1483
     *
1484
     * @return AnnotationDriver
1485
     */
1486
    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...
1487
    {
1488
        if ($reader == null) {
1489
            $reader = new AnnotationReader();
1490
        }
1491
1492
        return new self($reader, $paths);
1493
    }
1494
}
1495