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

AnnotationDriver   F

Complexity

Total Complexity 174

Size/Duplication

Total Lines 1562
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 39

Importance

Changes 0
Metric Value
wmc 174
lcom 1
cbo 39
dl 0
loc 1562
rs 0.5217
c 0
b 0
f 0

41 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A addPaths() 0 4 1
A getPaths() 0 4 1
A addExcludePaths() 0 4 1
A getExcludePaths() 0 4 1
A getReader() 0 4 1
A getFileExtension() 0 4 1
A setFileExtension() 0 4 1
A isTransient() 0 11 3
C getAllClassNames() 0 63 12
B loadMetadataForClass() 0 56 6
B convertClassAnnotationsToClassMetadata() 0 42 4
B convertClassAnnotationsToEntityClassMetadata() 0 54 6
B convertClassAnnotationsToMappedSuperClassMetadata() 0 26 2
B convertPropertyAnnotationsToProperty() 0 55 7
C convertReflectionPropertyToFieldMetadata() 0 65 8
B convertReflectionPropertyToOneToOneAssociationMetadata() 0 58 7
B convertReflectionPropertyToManyToOneAssociationMetadata() 0 53 6
B convertReflectionPropertyToOneToManyAssociationMetadata() 0 42 5
B convertReflectionPropertyToManyToManyAssociationMetadata() 0 54 7
B convertColumnAnnotationToFieldMetadata() 0 36 6
B convertTableAnnotationToTableMetadata() 0 38 6
B convertJoinTableAnnotationToJoinTableMetadata() 0 28 5
B convertJoinColumnAnnotationToJoinColumnMetadata() 0 32 6
A convertCacheAnnotationToCacheMetadata() 0 14 3
B convertSqlResultSetMapping() 0 35 4
B attachTable() 0 29 3
B attachDiscriminatorColumn() 0 49 6
B attachNamedQueries() 0 23 5
B attachNamedNativeQueries() 0 33 5
A attachLifecycleCallbacks() 0 16 4
B attachEntityListeners() 0 32 6
C attachPropertyOverrides() 0 72 11
A attachAssociationPropertyCache() 0 19 2
A getCascade() 0 17 3
A getFetchMode() 0 10 2
B getMethodCallbacks() 0 25 4
A getClassAnnotations() 0 14 3
A getPropertyAnnotations() 0 14 3
A getMethodAnnotations() 0 14 3
A create() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like AnnotationDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationDriver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Mapping\Driver\Annotation;
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 Mapping\Driver\MappingDriver
23
{
24
    /**
25
     * {@inheritdoc}
26
     */
27
    protected $entityAnnotationClasses = [
28
        Annotation\Entity::class           => 1,
29
        Annotation\MappedSuperclass::class => 2,
30
        Annotation\Embeddable::class       => 3,
31
    ];
32
33
    /**
34
     * The AnnotationReader.
35
     *
36
     * @var AnnotationReader
37
     */
38
    protected $reader;
39
40
    /**
41
     * The paths where to look for mapping files.
42
     *
43
     * @var array
44
     */
45
    protected $paths = [];
46
47
    /**
48
     * The paths excluded from path where to look for mapping files.
49
     *
50
     * @var array
51
     */
52
    protected $excludePaths = [];
53
54
    /**
55
     * The file extension of mapping documents.
56
     *
57
     * @var string
58
     */
59
    protected $fileExtension = '.php';
60
61
    /**
62
     * Cache for AnnotationDriver#getAllClassNames().
63
     *
64
     * @var array|null
65
     */
66
    protected $classNames;
67
68
    /**
69
     * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
70
     * docblock annotations.
71
     *
72
     * @param AnnotationReader  $reader The AnnotationReader to use, duck-typed.
73
     * @param string|array|null $paths  One or multiple paths where mapping classes can be found.
74
     */
75
    public function __construct($reader, $paths = null)
76
    {
77
        $this->reader = $reader;
78
79
        if ($paths) {
80
            $this->addPaths((array) $paths);
81
        }
82
    }
83
84
    /**
85
     * Appends lookup paths to metadata driver.
86
     *
87
     * @param array $paths
88
     *
89
     * @return void
90
     */
91
    public function addPaths(array $paths)
92
    {
93
        $this->paths = array_unique(array_merge($this->paths, $paths));
94
    }
95
96
    /**
97
     * Retrieves the defined metadata lookup paths.
98
     *
99
     * @return array
100
     */
101
    public function getPaths()
102
    {
103
        return $this->paths;
104
    }
105
106
    /**
107
     * Append exclude lookup paths to metadata driver.
108
     *
109
     * @param array $paths
110
     */
111
    public function addExcludePaths(array $paths)
112
    {
113
        $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths));
114
    }
115
116
    /**
117
     * Retrieve the defined metadata lookup exclude paths.
118
     *
119
     * @return array
120
     */
121
    public function getExcludePaths()
122
    {
123
        return $this->excludePaths;
124
    }
125
126
    /**
127
     * Retrieve the current annotation reader
128
     *
129
     * @return AnnotationReader
130
     */
131
    public function getReader()
132
    {
133
        return $this->reader;
134
    }
135
136
    /**
137
     * Gets the file extension used to look for mapping files under.
138
     *
139
     * @return string
140
     */
141
    public function getFileExtension()
142
    {
143
        return $this->fileExtension;
144
    }
145
146
    /**
147
     * Sets the file extension used to look for mapping files under.
148
     *
149
     * @param string $fileExtension The file extension to set.
150
     *
151
     * @return void
152
     */
153
    public function setFileExtension($fileExtension)
154
    {
155
        $this->fileExtension = $fileExtension;
156
    }
157
158
    /**
159
     * Returns whether the class with the specified name is transient. Only non-transient
160
     * classes, that is entities and mapped superclasses, should have their metadata loaded.
161
     *
162
     * A class is non-transient if it is annotated with an annotation
163
     * from the {@see AnnotationDriver::entityAnnotationClasses}.
164
     *
165
     * @param string $className
166
     *
167
     * @return boolean
168
     */
169
    public function isTransient($className)
170
    {
171
        $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className));
172
173
        foreach ($classAnnotations as $annot) {
174
            if (isset($this->entityAnnotationClasses[get_class($annot)])) {
175
                return false;
176
            }
177
        }
178
        return true;
179
    }
180
181
    /**
182
     * {@inheritDoc}
183
     */
184
    public function getAllClassNames()
185
    {
186
        if ($this->classNames !== null) {
187
            return $this->classNames;
188
        }
189
190
        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...
191
            throw Mapping\MappingException::pathRequired();
192
        }
193
194
        $classes = [];
195
        $includedFiles = [];
196
197
        foreach ($this->paths as $path) {
198
            if ( ! is_dir($path)) {
199
                throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
200
            }
201
202
            $iterator = new \RegexIterator(
203
                new \RecursiveIteratorIterator(
204
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
205
                    \RecursiveIteratorIterator::LEAVES_ONLY
206
                ),
207
                '/^.+' . preg_quote($this->fileExtension) . '$/i',
208
                \RecursiveRegexIterator::GET_MATCH
209
            );
210
211
            foreach ($iterator as $file) {
212
                $sourceFile = $file[0];
213
214
                if ( ! preg_match('(^phar:)i', $sourceFile)) {
215
                    $sourceFile = realpath($sourceFile);
216
                }
217
218
                foreach ($this->excludePaths as $excludePath) {
219
                    $exclude = str_replace('\\', '/', realpath($excludePath));
220
                    $current = str_replace('\\', '/', $sourceFile);
221
222
                    if (strpos($current, $exclude) !== false) {
223
                        continue 2;
224
                    }
225
                }
226
227
                require_once $sourceFile;
228
229
                $includedFiles[] = $sourceFile;
230
            }
231
        }
232
233
        $declared = get_declared_classes();
234
235
        foreach ($declared as $className) {
236
            $rc = new \ReflectionClass($className);
237
            $sourceFile = $rc->getFileName();
238
            if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
239
                $classes[] = $className;
240
            }
241
        }
242
243
        $this->classNames = $classes;
244
245
        return $classes;
246
    }
247
248
    /**
249
     * {@inheritdoc}
250
     *
251
     * @throws \UnexpectedValueException
252
     * @throws \ReflectionException
253
     * @throws Mapping\MappingException
254
     */
255
    public function loadMetadataForClass(
256
        string $className,
257
        Mapping\ClassMetadata $metadata,
258
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
259
    ) : Mapping\ClassMetadata
260
    {
261
        $reflectionClass = $metadata->getReflectionClass();
262
263
        if (! $reflectionClass) {
264
            // this happens when running annotation driver in combination with
265
            // static reflection services. This is not the nicest fix
266
            $reflectionClass = new \ReflectionClass($metadata->getClassName());
267
        }
268
269
        $classAnnotations = $this->getClassAnnotations($reflectionClass);
270
        $classMetadata    = $this->convertClassAnnotationsToClassMetadata(
271
            $reflectionClass,
272
            $classAnnotations,
273
            $metadata,
274
            $metadataBuildingContext
275
        );
276
277
        // Evaluate @Cache annotation
278
        if (isset($classAnnotations[Annotation\Cache::class])) {
279
            $cacheAnnot = $classAnnotations[Annotation\Cache::class];
280
            $cache      = $this->convertCacheAnnotationToCacheMetadata($cacheAnnot, $metadata);
281
282
            $classMetadata->setCache($cache);
283
        }
284
285
        // Evaluate annotations on properties/fields
286
        /* @var $reflProperty \ReflectionProperty */
287
        foreach ($reflectionClass->getProperties() as $reflectionProperty) {
288
            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...
289
                continue;
290
            }
291
292
            $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty);
293
            $property            = $this->convertPropertyAnnotationsToProperty(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $property is correct as $this->convertPropertyAn...etadataBuildingContext) (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...
294
                $propertyAnnotations,
295
                $reflectionProperty,
296
                $classMetadata,
297
                $metadataBuildingContext
298
            );
299
300
            if (! $property) {
301
                continue;
302
            }
303
304
            $metadata->addProperty($property);
305
        }
306
307
        $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
308
309
        return $classMetadata;
310
    }
311
312
    /**
313
     * @param \ReflectionClass                     $reflectionClass
314
     * @param array                                $classAnnotations
315
     * @param Mapping\ClassMetadata                $metadata
316
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
317
     *
318
     * @return Mapping\ClassMetadata
319
     *
320
     * @throws \UnexpectedValueException
321
     * @throws Mapping\MappingException
322
     */
323
    private function convertClassAnnotationsToClassMetadata(
324
        \ReflectionClass $reflectionClass,
325
        array $classAnnotations,
326
        Mapping\ClassMetadata $metadata,
327
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
328
    ) : Mapping\ClassMetadata
329
    {
330
        switch (true) {
331
            case isset($classAnnotations[Annotation\Entity::class]):
332
                $binder = new Binder\EntityClassMetadataBinder(
333
                    $reflectionClass,
334
                    $classAnnotations,
335
                    $metadata,
336
                    $metadataBuildingContext
337
                );
338
339
                break;
340
341
            case isset($classAnnotations[Annotation\MappedSuperclass::class]):
342
                $binder = new Binder\MappedSuperClassMetadataBinder(
343
                    $reflectionClass,
344
                    $classAnnotations,
345
                    $metadata,
346
                    $metadataBuildingContext
347
                );
348
                break;
349
350
            case isset($classAnnotations[Annotation\Embeddable::class]):
351
                $binder = new Binder\EmbeddableClassMetadataBinder(
352
                    $reflectionClass,
353
                    $classAnnotations,
354
                    $metadata,
355
                    $metadataBuildingContext
356
                );
357
                break;
358
359
            default:
360
                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...
361
        }
362
363
        return $binder->bind();
364
    }
365
366
    /**
367
     * @param array                                $classAnnotations
368
     * @param \ReflectionClass                     $reflectionClass
369
     * @param Mapping\ClassMetadata                $metadata
370
     * @param Mapping\ClassMetadataBuildingContext $metadataBuildingContext
371
     *
372
     * @return Mapping\ClassMetadata
373
     *
374
     * @throws Mapping\MappingException
375
     * @throws \UnexpectedValueException
376
     */
377
    private function convertClassAnnotationsToEntityClassMetadata(
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
378
        array $classAnnotations,
379
        \ReflectionClass $reflectionClass,
380
        Mapping\ClassMetadata $metadata,
381
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
382
    )
383
    {
384
        /** @var Annotation\Entity $entityAnnot */
385
        $entityAnnot  = $classAnnotations[Annotation\Entity::class];
386
387
        if ($entityAnnot->repositoryClass !== null) {
388
            $metadata->setCustomRepositoryClassName(
389
                $metadata->fullyQualifiedClassName($entityAnnot->repositoryClass)
390
            );
391
        }
392
393
        if ($entityAnnot->readOnly) {
394
            $metadata->asReadOnly();
395
        }
396
397
        $metadata->isMappedSuperclass = false;
398
        $metadata->isEmbeddedClass = false;
399
400
        $this->attachTable($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext);
401
402
        // Evaluate @ChangeTrackingPolicy annotation
403
        if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) {
404
            $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class];
405
406
            $metadata->setChangeTrackingPolicy(
407
                constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value))
408
            );
409
        }
410
411
        // Evaluate @InheritanceType annotation
412
        if (isset($classAnnotations[Annotation\InheritanceType::class])) {
413
            $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class];
414
415
            $metadata->setInheritanceType(
416
                constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value))
417
            );
418
419
            if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) {
420
                $this->attachDiscriminatorColumn($classAnnotations, $reflectionClass, $metadata);
0 ignored issues
show
Bug introduced by
The call to attachDiscriminatorColumn() misses a required argument $metadataBuildingContext.

This check looks for function calls that miss required arguments.

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1116
        }
1117
1118
        $metadata->setTable($table);
1119
    }
1120
1121
    /**
1122
     * @param array                 $classAnnotations
1123
     * @param \ReflectionClass      $reflectionClass
1124
     * @param Mapping\ClassMetadata $metadata
1125
     *
1126
     * @return void
1127
     *
1128
     * @throws Mapping\MappingException
1129
     */
1130
    private function attachDiscriminatorColumn(
1131
        array $classAnnotations,
1132
        \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...
1133
        Mapping\ClassMetadata $metadata,
1134
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext 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...
1135
    ) : void
1136
    {
1137
        $discriminatorColumn = new Mapping\DiscriminatorColumnMetadata();
1138
1139
        $discriminatorColumn->setTableName($metadata->getTableName());
1140
        $discriminatorColumn->setColumnName('dtype');
1141
        $discriminatorColumn->setType(Type::getType('string'));
1142
        $discriminatorColumn->setLength(255);
1143
1144
        // Evaluate DiscriminatorColumn annotation
1145
        if (isset($classAnnotations[Annotation\DiscriminatorColumn::class])) {
1146
            /** @var Annotation\DiscriminatorColumn $discriminatorColumnAnnotation */
1147
            $discriminatorColumnAnnotation = $classAnnotations[Annotation\DiscriminatorColumn::class];
1148
            $typeName                      = ! empty($discriminatorColumnAnnotation->type)
1149
                ? $discriminatorColumnAnnotation->type
1150
                : 'string';
1151
1152
            $discriminatorColumn->setType(Type::getType($typeName));
1153
            $discriminatorColumn->setColumnName($discriminatorColumnAnnotation->name);
1154
1155
            if (! empty($discriminatorColumnAnnotation->columnDefinition)) {
1156
                $discriminatorColumn->setColumnDefinition($discriminatorColumnAnnotation->columnDefinition);
1157
            }
1158
1159
            if (! empty($discriminatorColumnAnnotation->length)) {
1160
                $discriminatorColumn->setLength($discriminatorColumnAnnotation->length);
1161
            }
1162
        }
1163
1164
        $metadata->setDiscriminatorColumn($discriminatorColumn);
1165
1166
        // Evaluate DiscriminatorMap annotation
1167
        if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) {
1168
            $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class];
1169
            $discriminatorMap           = array_map(
1170
                function ($className) use ($metadata) {
1171
                    return $metadata->fullyQualifiedClassName($className);
1172
                },
1173
                $discriminatorMapAnnotation->value
1174
            );
1175
1176
            $metadata->setDiscriminatorMap($discriminatorMap);
1177
        }
1178
    }
1179
1180
    /**
1181
     * @param array                 $classAnnotations
1182
     * @param \ReflectionClass      $reflectionClass
1183
     * @param Mapping\ClassMetadata $metadata
1184
     *
1185
     * @return void
1186
     *
1187
     * @throws \UnexpectedValueException
1188
     * @throws Mapping\MappingException
1189
     */
1190
    private function attachNamedQueries(
1191
        array $classAnnotations,
1192
        \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...
1193
        Mapping\ClassMetadata $metadata
1194
    ) : void
1195
    {
1196
        // Evaluate @NamedQueries annotation
1197
        if (isset($classAnnotations[Annotation\NamedQueries::class])) {
1198
            $namedQueriesAnnot = $classAnnotations[Annotation\NamedQueries::class];
1199
1200
            if (! is_array($namedQueriesAnnot->value)) {
1201
                throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1202
            }
1203
1204
            foreach ($namedQueriesAnnot->value as $namedQuery) {
1205
                if (! ($namedQuery instanceof Annotation\NamedQuery)) {
1206
                    throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations.");
1207
                }
1208
1209
                $metadata->addNamedQuery($namedQuery->name, $namedQuery->query);
1210
            }
1211
        }
1212
    }
1213
1214
    /**
1215
     * @param array                 $classAnnotations
1216
     * @param \ReflectionClass      $reflectionClass
1217
     * @param Mapping\ClassMetadata $metadata
1218
     *
1219
     * @return void
1220
     */
1221
    private function attachNamedNativeQueries(
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 @NamedNativeQueries annotation
1228
        if (isset($classAnnotations[Annotation\NamedNativeQueries::class])) {
1229
            $namedNativeQueriesAnnot = $classAnnotations[Annotation\NamedNativeQueries::class];
1230
1231
            foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) {
1232
                $metadata->addNamedNativeQuery(
1233
                    $namedNativeQuery->name,
1234
                    $namedNativeQuery->query,
1235
                    [
1236
                        'resultClass'      => $namedNativeQuery->resultClass,
1237
                        'resultSetMapping' => $namedNativeQuery->resultSetMapping,
1238
                    ]
1239
                );
1240
            }
1241
        }
1242
1243
        // Evaluate @SqlResultSetMappings annotation
1244
        if (isset($classAnnotations[Annotation\SqlResultSetMappings::class])) {
1245
            $sqlResultSetMappingsAnnot = $classAnnotations[Annotation\SqlResultSetMappings::class];
1246
1247
            foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) {
1248
                $sqlResultSetMapping = $this->convertSqlResultSetMapping($resultSetMapping);
1249
1250
                $metadata->addSqlResultSetMapping($sqlResultSetMapping);
1251
            }
1252
        }
1253
    }
1254
1255
    /**
1256
     * @param array                 $classAnnotations
1257
     * @param \ReflectionClass      $reflectionClass
1258
     * @param Mapping\ClassMetadata $metadata
1259
     *
1260
     * @return void
1261
     */
1262
    private function attachLifecycleCallbacks(
1263
        array $classAnnotations,
1264
        \ReflectionClass $reflectionClass,
1265
        Mapping\ClassMetadata $metadata
1266
    ) : void
1267
    {
1268
        // Evaluate @HasLifecycleCallbacks annotation
1269
        if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) {
1270
            /* @var $method \ReflectionMethod */
1271
            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1272
                foreach ($this->getMethodCallbacks($method) as $callback) {
1273
                    $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...
1274
                }
1275
            }
1276
        }
1277
    }
1278
1279
    /**
1280
     * @param array                 $classAnnotations
1281
     * @param \ReflectionClass      $reflectionClass
1282
     * @param Mapping\ClassMetadata $metadata
1283
     *
1284
     * @return void
1285
     *
1286
     * @throws \ReflectionException
1287
     * @throws Mapping\MappingException
1288
     */
1289
    private function attachEntityListeners(
1290
        array $classAnnotations,
1291
        \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...
1292
        Mapping\ClassMetadata $metadata
1293
    ) : void
1294
    {
1295
        // Evaluate @EntityListeners annotation
1296
        if (isset($classAnnotations[Annotation\EntityListeners::class])) {
1297
            /** @var Annotation\EntityListeners $entityListenersAnnot */
1298
            $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class];
1299
1300
            foreach ($entityListenersAnnot->value as $item) {
1301
                $listenerClassName = $metadata->fullyQualifiedClassName($item);
1302
1303
                if (! class_exists($listenerClassName)) {
1304
                    throw Mapping\MappingException::entityListenerClassNotFound(
1305
                        $listenerClassName,
1306
                        $metadata->getClassName()
1307
                    );
1308
                }
1309
1310
                $listenerClass = new \ReflectionClass($listenerClassName);
1311
1312
                /* @var $method \ReflectionMethod */
1313
                foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
1314
                    foreach ($this->getMethodCallbacks($method) as $callback) {
1315
                        $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...
1316
                    }
1317
                }
1318
            }
1319
        }
1320
    }
1321
1322
    /**
1323
     * @param array                 $classAnnotations
1324
     * @param \ReflectionClass      $reflectionClass
1325
     * @param Mapping\ClassMetadata $metadata
1326
     *
1327
     * @return void
1328
     *
1329
     * @throws Mapping\MappingException
1330
     */
1331
    private function attachPropertyOverrides(
1332
        array $classAnnotations,
1333
        \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...
1334
        Mapping\ClassMetadata $metadata,
1335
        Mapping\ClassMetadataBuildingContext $metadataBuildingContext
0 ignored issues
show
Unused Code introduced by
The parameter $metadataBuildingContext 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...
1336
    ) : void
1337
    {
1338
        // Evaluate AssociationOverrides annotation
1339
        if (isset($classAnnotations[Annotation\AssociationOverrides::class])) {
1340
            $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class];
1341
1342
            foreach ($associationOverridesAnnot->value as $associationOverride) {
1343
                $fieldName = $associationOverride->name;
1344
                $property  = $metadata->getProperty($fieldName);
1345
1346
                if (! $property) {
1347
                    throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName);
1348
                }
1349
1350
                $existingClass = get_class($property);
1351
                $override      = new $existingClass($fieldName);
1352
1353
                // Check for JoinColumn/JoinColumns annotations
1354
                if ($associationOverride->joinColumns) {
1355
                    $joinColumns = [];
1356
1357
                    foreach ($associationOverride->joinColumns as $joinColumnAnnot) {
1358
                        $joinColumns[] = $this->convertJoinColumnAnnotationToJoinColumnMetadata($joinColumnAnnot);
1359
                    }
1360
1361
                    $override->setJoinColumns($joinColumns);
1362
                }
1363
1364
                // Check for JoinTable annotations
1365
                if ($associationOverride->joinTable) {
1366
                    $joinTableAnnot    = $associationOverride->joinTable;
1367
                    $joinTableMetadata = $this->convertJoinTableAnnotationToJoinTableMetadata($joinTableAnnot);
1368
1369
                    $override->setJoinTable($joinTableMetadata);
1370
                }
1371
1372
                // Check for inversedBy
1373
                if ($associationOverride->inversedBy) {
1374
                    $override->setInversedBy($associationOverride->inversedBy);
1375
                }
1376
1377
                // Check for fetch
1378
                if ($associationOverride->fetch) {
1379
                    $override->setFetchMode(
1380
                        constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch)
1381
                    );
1382
                }
1383
1384
                $metadata->setPropertyOverride($override);
1385
            }
1386
        }
1387
1388
        // Evaluate AttributeOverrides annotation
1389
        if (isset($classAnnotations[Annotation\AttributeOverrides::class])) {
1390
            $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class];
1391
1392
            foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
1393
                $fieldMetadata = $this->convertColumnAnnotationToFieldMetadata(
1394
                    $attributeOverrideAnnot->column,
1395
                    $attributeOverrideAnnot->name,
1396
                    false
1397
                );
1398
1399
                $metadata->setPropertyOverride($fieldMetadata);
1400
            }
1401
        }
1402
    }
1403
1404
    private function attachAssociationPropertyCache(
1405
        array $propertyAnnotations,
1406
        \ReflectionProperty $reflectionProperty,
1407
        Mapping\AssociationMetadata $assocMetadata,
1408
        Mapping\ClassMetadata $metadata
1409
    ) : void
1410
    {
1411
        // Check for Cache
1412
        if (isset($propertyAnnotations[Annotation\Cache::class])) {
1413
            $cacheAnnot    = $propertyAnnotations[Annotation\Cache::class];
1414
            $cacheMetadata = $this->convertCacheAnnotationToCacheMetadata(
1415
                $cacheAnnot,
1416
                $metadata,
1417
                $reflectionProperty->getName()
1418
            );
1419
1420
            $assocMetadata->setCache($cacheMetadata);
1421
        }
1422
    }
1423
1424
    /**
1425
     * Attempts to resolve the cascade modes.
1426
     *
1427
     * @param string $className        The class name.
1428
     * @param string $fieldName        The field name.
1429
     * @param array  $originalCascades The original unprocessed field cascades.
1430
     *
1431
     * @return array The processed field cascades.
1432
     *
1433
     * @throws Mapping\MappingException If a cascade option is not valid.
1434
     */
1435
    private function getCascade(string $className, string $fieldName, array $originalCascades) : array
1436
    {
1437
        $cascadeTypes = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1438
        $cascades     = array_map('strtolower', $originalCascades);
1439
1440
        if (in_array('all', $cascades)) {
1441
            $cascades = $cascadeTypes;
1442
        }
1443
1444
        if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) {
1445
            $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes));
1446
1447
            throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName);
1448
        }
1449
1450
        return $cascades;
1451
    }
1452
1453
    /**
1454
     * Attempts to resolve the fetch mode.
1455
     *
1456
     * @param string $className The class name.
1457
     * @param string $fetchMode The fetch mode.
1458
     *
1459
     * @return string The fetch mode as defined in ClassMetadata.
1460
     *
1461
     * @throws Mapping\MappingException If the fetch mode is not valid.
1462
     */
1463
    private function getFetchMode($className, $fetchMode) : string
1464
    {
1465
        $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode);
1466
1467
        if (! defined($fetchModeConstant)) {
1468
            throw Mapping\MappingException::invalidFetchMode($className, $fetchMode);
1469
        }
1470
1471
        return constant($fetchModeConstant);
1472
    }
1473
1474
    /**
1475
     * Parses the given method.
1476
     *
1477
     * @param \ReflectionMethod $method
1478
     *
1479
     * @return array
1480
     */
1481
    private function getMethodCallbacks(\ReflectionMethod $method) : array
1482
    {
1483
        $annotations = $this->getMethodAnnotations($method);
1484
        $events      = [
1485
            Events::prePersist  => Annotation\PrePersist::class,
1486
            Events::postPersist => Annotation\PostPersist::class,
1487
            Events::preUpdate   => Annotation\PreUpdate::class,
1488
            Events::postUpdate  => Annotation\PostUpdate::class,
1489
            Events::preRemove   => Annotation\PreRemove::class,
1490
            Events::postRemove  => Annotation\PostRemove::class,
1491
            Events::postLoad    => Annotation\PostLoad::class,
1492
            Events::preFlush    => Annotation\PreFlush::class,
1493
        ];
1494
1495
        // Check for callbacks
1496
        $callbacks = [];
1497
1498
        foreach ($events as $eventName => $annotationClassName) {
1499
            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...
1500
                $callbacks[] = $eventName;
1501
            }
1502
        }
1503
1504
        return $callbacks;
1505
    }
1506
1507
    /**
1508
     * @param \ReflectionClass $reflectionClass
1509
     *
1510
     * @return array
1511
     */
1512
    private function getClassAnnotations(\ReflectionClass $reflectionClass) : array
1513
    {
1514
        $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
1515
1516
        foreach ($classAnnotations as $key => $annot) {
1517
            if (! is_numeric($key)) {
1518
                continue;
1519
            }
1520
1521
            $classAnnotations[get_class($annot)] = $annot;
1522
        }
1523
1524
        return $classAnnotations;
1525
    }
1526
1527
    /**
1528
     * @param \ReflectionProperty $reflectionProperty
1529
     *
1530
     * @return array
1531
     */
1532
    private function getPropertyAnnotations(\ReflectionProperty $reflectionProperty) : array
1533
    {
1534
        $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty);
1535
1536
        foreach ($propertyAnnotations as $key => $annot) {
1537
            if (! is_numeric($key)) {
1538
                continue;
1539
            }
1540
1541
            $propertyAnnotations[get_class($annot)] = $annot;
1542
        }
1543
1544
        return $propertyAnnotations;
1545
    }
1546
1547
    /**
1548
     * @param \ReflectionMethod $reflectionMethod
1549
     *
1550
     * @return array
1551
     */
1552
    private function getMethodAnnotations(\ReflectionMethod $reflectionMethod) : array
1553
    {
1554
        $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
1555
1556
        foreach ($methodAnnotations as $key => $annot) {
1557
            if (! is_numeric($key)) {
1558
                continue;
1559
            }
1560
1561
            $methodAnnotations[get_class($annot)] = $annot;
1562
        }
1563
1564
        return $methodAnnotations;
1565
    }
1566
1567
    /**
1568
     * Factory method for the Annotation Driver.
1569
     *
1570
     * @param array|string          $paths
1571
     * @param AnnotationReader|null $reader
1572
     *
1573
     * @return AnnotationDriver
1574
     */
1575
    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...
1576
    {
1577
        if ($reader == null) {
1578
            $reader = new AnnotationReader();
1579
        }
1580
1581
        return new self($reader, $paths);
1582
    }
1583
}
1584