1 | <?php /** @noinspection ALL */ |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace Doctrine\ORM\Mapping\Driver; |
||||
6 | |||||
7 | use Doctrine\Common\Annotations\AnnotationReader; |
||||
8 | use Doctrine\Common\Annotations\Reader; |
||||
9 | use Doctrine\ORM\Annotation; |
||||
10 | use Doctrine\ORM\Cache\Exception\CacheException; |
||||
11 | use Doctrine\ORM\Events; |
||||
12 | use Doctrine\ORM\Mapping; |
||||
13 | use Doctrine\ORM\Mapping\Builder; |
||||
14 | use FilesystemIterator; |
||||
15 | use RecursiveDirectoryIterator; |
||||
16 | use RecursiveIteratorIterator; |
||||
17 | use RecursiveRegexIterator; |
||||
18 | use ReflectionClass; |
||||
19 | use ReflectionException; |
||||
20 | use ReflectionMethod; |
||||
21 | use ReflectionProperty; |
||||
22 | use RegexIterator; |
||||
23 | use RuntimeException; |
||||
24 | use UnexpectedValueException; |
||||
25 | use function array_merge; |
||||
26 | use function array_unique; |
||||
27 | use function class_exists; |
||||
28 | use function constant; |
||||
29 | use function get_class; |
||||
30 | use function get_declared_classes; |
||||
31 | use function in_array; |
||||
32 | use function is_dir; |
||||
33 | use function is_numeric; |
||||
34 | use function preg_match; |
||||
35 | use function preg_quote; |
||||
36 | use function realpath; |
||||
37 | use function sprintf; |
||||
38 | use function str_replace; |
||||
39 | use function strpos; |
||||
40 | |||||
41 | /** |
||||
42 | * The AnnotationDriver reads the mapping metadata from docblock annotations. |
||||
43 | */ |
||||
44 | class AnnotationDriver implements MappingDriver |
||||
45 | { |
||||
46 | /** @var int[] */ |
||||
47 | protected $entityAnnotationClasses = [ |
||||
48 | Annotation\Entity::class => 1, |
||||
49 | Annotation\MappedSuperclass::class => 2, |
||||
50 | ]; |
||||
51 | |||||
52 | /** |
||||
53 | * The AnnotationReader. |
||||
54 | * |
||||
55 | * @var AnnotationReader |
||||
56 | */ |
||||
57 | protected $reader; |
||||
58 | |||||
59 | /** |
||||
60 | * The paths where to look for mapping files. |
||||
61 | * |
||||
62 | * @var string[] |
||||
63 | */ |
||||
64 | protected $paths = []; |
||||
65 | |||||
66 | /** |
||||
67 | * The paths excluded from path where to look for mapping files. |
||||
68 | * |
||||
69 | * @var string[] |
||||
70 | */ |
||||
71 | protected $excludePaths = []; |
||||
72 | |||||
73 | /** |
||||
74 | * The file extension of mapping documents. |
||||
75 | * |
||||
76 | * @var string |
||||
77 | */ |
||||
78 | protected $fileExtension = '.php'; |
||||
79 | |||||
80 | /** |
||||
81 | * Cache for AnnotationDriver#getAllClassNames(). |
||||
82 | * |
||||
83 | * @var string[]|null |
||||
84 | */ |
||||
85 | protected $classNames; |
||||
86 | |||||
87 | /** |
||||
88 | * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading |
||||
89 | * docblock annotations. |
||||
90 | * |
||||
91 | * @param Reader $reader The AnnotationReader to use, duck-typed. |
||||
92 | * @param string|string[]|null $paths One or multiple paths where mapping classes can be found. |
||||
93 | */ |
||||
94 | 2297 | public function __construct(Reader $reader, $paths = null) |
|||
95 | { |
||||
96 | 2297 | $this->reader = $reader; |
|||
97 | |||||
98 | 2297 | if ($paths) { |
|||
99 | 2211 | $this->addPaths((array) $paths); |
|||
100 | } |
||||
101 | 2297 | } |
|||
102 | |||||
103 | /** |
||||
104 | * Appends lookup paths to metadata driver. |
||||
105 | * |
||||
106 | * @param string[] $paths |
||||
107 | */ |
||||
108 | 2215 | public function addPaths(array $paths) |
|||
109 | { |
||||
110 | 2215 | $this->paths = array_unique(array_merge($this->paths, $paths)); |
|||
111 | 2215 | } |
|||
112 | |||||
113 | /** |
||||
114 | * Retrieves the defined metadata lookup paths. |
||||
115 | * |
||||
116 | * @return string[] |
||||
117 | */ |
||||
118 | public function getPaths() |
||||
119 | { |
||||
120 | return $this->paths; |
||||
121 | } |
||||
122 | |||||
123 | /** |
||||
124 | * Append exclude lookup paths to metadata driver. |
||||
125 | * |
||||
126 | * @param string[] $paths |
||||
127 | */ |
||||
128 | public function addExcludePaths(array $paths) |
||||
129 | { |
||||
130 | $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); |
||||
131 | } |
||||
132 | |||||
133 | /** |
||||
134 | * Retrieve the defined metadata lookup exclude paths. |
||||
135 | * |
||||
136 | * @return string[] |
||||
137 | */ |
||||
138 | public function getExcludePaths() |
||||
139 | { |
||||
140 | return $this->excludePaths; |
||||
141 | } |
||||
142 | |||||
143 | /** |
||||
144 | * Retrieve the current annotation reader |
||||
145 | * |
||||
146 | * @return Reader |
||||
147 | */ |
||||
148 | 1 | public function getReader() |
|||
149 | { |
||||
150 | 1 | return $this->reader; |
|||
151 | } |
||||
152 | |||||
153 | /** |
||||
154 | * Gets the file extension used to look for mapping files under. |
||||
155 | * |
||||
156 | * @return string |
||||
157 | */ |
||||
158 | public function getFileExtension() |
||||
159 | { |
||||
160 | return $this->fileExtension; |
||||
161 | } |
||||
162 | |||||
163 | /** |
||||
164 | * Sets the file extension used to look for mapping files under. |
||||
165 | * |
||||
166 | * @param string $fileExtension The file extension to set. |
||||
167 | */ |
||||
168 | public function setFileExtension($fileExtension) |
||||
169 | { |
||||
170 | $this->fileExtension = $fileExtension; |
||||
171 | } |
||||
172 | |||||
173 | /** |
||||
174 | * Returns whether the class with the specified name is transient. Only non-transient |
||||
175 | * classes, that is entities and mapped superclasses, should have their metadata loaded. |
||||
176 | * |
||||
177 | * A class is non-transient if it is annotated with an annotation |
||||
178 | * from the {@see AnnotationDriver::entityAnnotationClasses}. |
||||
179 | * |
||||
180 | * @param string $className |
||||
181 | * |
||||
182 | * @throws ReflectionException |
||||
183 | */ |
||||
184 | 193 | public function isTransient($className) : bool |
|||
185 | { |
||||
186 | 193 | $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className)); |
|||
187 | |||||
188 | 193 | foreach ($classAnnotations as $annotation) { |
|||
189 | 188 | if (isset($this->entityAnnotationClasses[get_class($annotation)])) { |
|||
190 | 188 | return false; |
|||
191 | } |
||||
192 | } |
||||
193 | |||||
194 | 12 | return true; |
|||
195 | } |
||||
196 | |||||
197 | /** |
||||
198 | * {@inheritdoc} |
||||
199 | * |
||||
200 | * @throws ReflectionException |
||||
201 | */ |
||||
202 | 60 | public function getAllClassNames() : array |
|||
203 | { |
||||
204 | 60 | if ($this->classNames !== null) { |
|||
205 | 45 | return $this->classNames; |
|||
206 | } |
||||
207 | |||||
208 | 60 | if (! $this->paths) { |
|||
209 | throw Mapping\MappingException::pathRequired(); |
||||
210 | } |
||||
211 | |||||
212 | 60 | $classes = []; |
|||
213 | 60 | $includedFiles = []; |
|||
214 | |||||
215 | 60 | foreach ($this->paths as $path) { |
|||
216 | 60 | if (! is_dir($path)) { |
|||
217 | throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); |
||||
218 | } |
||||
219 | |||||
220 | 60 | $iterator = new RegexIterator( |
|||
221 | 60 | new RecursiveIteratorIterator( |
|||
222 | 60 | new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), |
|||
223 | 60 | RecursiveIteratorIterator::LEAVES_ONLY |
|||
224 | ), |
||||
225 | 60 | '/^.+' . preg_quote($this->fileExtension) . '$/i', |
|||
226 | 60 | RecursiveRegexIterator::GET_MATCH |
|||
227 | ); |
||||
228 | |||||
229 | 60 | foreach ($iterator as $file) { |
|||
230 | 60 | $sourceFile = $file[0]; |
|||
231 | |||||
232 | 60 | if (! preg_match('(^phar:)i', $sourceFile)) { |
|||
233 | 60 | $sourceFile = realpath($sourceFile); |
|||
234 | } |
||||
235 | |||||
236 | 60 | foreach ($this->excludePaths as $excludePath) { |
|||
237 | $exclude = str_replace('\\', '/', realpath($excludePath)); |
||||
238 | $current = str_replace('\\', '/', $sourceFile); |
||||
239 | |||||
240 | if (strpos($current, $exclude) !== false) { |
||||
241 | continue 2; |
||||
242 | } |
||||
243 | } |
||||
244 | |||||
245 | 60 | require_once $sourceFile; |
|||
246 | |||||
247 | 60 | $includedFiles[] = $sourceFile; |
|||
248 | } |
||||
249 | } |
||||
250 | |||||
251 | 60 | $declared = get_declared_classes(); |
|||
252 | |||||
253 | 60 | foreach ($declared as $className) { |
|||
254 | 60 | $reflectionClass = new ReflectionClass($className); |
|||
255 | 60 | $sourceFile = $reflectionClass->getFileName(); |
|||
256 | |||||
257 | 60 | if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) { |
|||
258 | 60 | $classes[] = $className; |
|||
259 | } |
||||
260 | } |
||||
261 | |||||
262 | 60 | $this->classNames = $classes; |
|||
263 | |||||
264 | 60 | return $classes; |
|||
265 | } |
||||
266 | |||||
267 | /** |
||||
268 | * {@inheritDoc} |
||||
269 | * |
||||
270 | * @throws CacheException |
||||
271 | * @throws Mapping\MappingException |
||||
272 | * @throws ReflectionException |
||||
273 | * @throws RuntimeException |
||||
274 | * @throws UnexpectedValueException |
||||
275 | */ |
||||
276 | 379 | public function loadMetadataForClass( |
|||
277 | string $className, |
||||
278 | ?Mapping\ComponentMetadata $parent, |
||||
279 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||||
280 | ) : Mapping\ComponentMetadata { |
||||
281 | 379 | $reflectionClass = new ReflectionClass($className); |
|||
282 | 379 | $metadata = new Mapping\ClassMetadata($className, $parent); |
|||
283 | 379 | $classAnnotations = $this->getClassAnnotations($reflectionClass); |
|||
284 | 379 | $classMetadata = $this->convertClassAnnotationsToClassMetadata( |
|||
285 | 379 | $classAnnotations, |
|||
286 | 379 | $reflectionClass, |
|||
287 | 379 | $metadata, |
|||
288 | 379 | $metadataBuildingContext |
|||
289 | ); |
||||
290 | |||||
291 | // Evaluate @Cache annotation |
||||
292 | 373 | if (isset($classAnnotations[Annotation\Cache::class])) { |
|||
293 | 18 | $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext); |
|||
294 | |||||
295 | $cacheBuilder |
||||
296 | 18 | ->withComponentMetadata($metadata) |
|||
297 | 18 | ->withCacheAnnotation($classAnnotations[Annotation\Cache::class]); |
|||
298 | |||||
299 | 18 | $metadata->setCache($cacheBuilder->build()); |
|||
300 | } |
||||
301 | |||||
302 | // Evaluate annotations on properties/fields |
||||
303 | /** @var ReflectionProperty $reflProperty */ |
||||
304 | 373 | foreach ($reflectionClass->getProperties() as $reflectionProperty) { |
|||
305 | 373 | if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) { |
|||
306 | 74 | continue; |
|||
307 | } |
||||
308 | |||||
309 | 373 | $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty); |
|||
310 | 372 | $property = $this->convertPropertyAnnotationsToProperty( |
|||
311 | 372 | $propertyAnnotations, |
|||
312 | 372 | $reflectionProperty, |
|||
313 | 372 | $classMetadata, |
|||
314 | 372 | $metadataBuildingContext |
|||
315 | ); |
||||
316 | |||||
317 | 371 | if ($classMetadata->isMappedSuperclass && |
|||
318 | 371 | $property instanceof Mapping\ToManyAssociationMetadata && |
|||
319 | 371 | ! $property->isOwningSide()) { |
|||
320 | 1 | throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass( |
|||
321 | 1 | $classMetadata->getClassName(), |
|||
322 | 1 | $property->getName() |
|||
323 | ); |
||||
324 | } |
||||
325 | |||||
326 | 370 | $metadata->addProperty($property); |
|||
327 | } |
||||
328 | |||||
329 | 370 | $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext); |
|||
330 | |||||
331 | 370 | return $classMetadata; |
|||
332 | } |
||||
333 | |||||
334 | /** |
||||
335 | * @param Annotation\Annotation[] $classAnnotations |
||||
336 | * |
||||
337 | * @throws Mapping\MappingException |
||||
338 | * @throws UnexpectedValueException |
||||
339 | * @throws ReflectionException |
||||
340 | */ |
||||
341 | 379 | private function convertClassAnnotationsToClassMetadata( |
|||
342 | array $classAnnotations, |
||||
343 | ReflectionClass $reflectionClass, |
||||
344 | Mapping\ClassMetadata $metadata, |
||||
345 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||||
346 | ) : Mapping\ClassMetadata { |
||||
347 | switch (true) { |
||||
348 | 379 | case isset($classAnnotations[Annotation\Entity::class]): |
|||
349 | 372 | return $this->convertClassAnnotationsToEntityClassMetadata( |
|||
350 | 372 | $classAnnotations, |
|||
351 | 372 | $reflectionClass, |
|||
352 | 372 | $metadata, |
|||
353 | 372 | $metadataBuildingContext |
|||
354 | ); |
||||
355 | |||||
356 | break; |
||||
0 ignored issues
–
show
|
|||||
357 | |||||
358 | 29 | case isset($classAnnotations[Annotation\MappedSuperclass::class]): |
|||
359 | 23 | return $this->convertClassAnnotationsToMappedSuperClassMetadata( |
|||
360 | 23 | $classAnnotations, |
|||
361 | 23 | $reflectionClass, |
|||
362 | 23 | $metadata |
|||
363 | ); |
||||
364 | 6 | case isset($classAnnotations[Annotation\Embeddable::class]): |
|||
365 | return $this->convertClassAnnotationsToEmbeddableClassMetadata( |
||||
366 | $classAnnotations, |
||||
367 | $reflectionClass, |
||||
368 | $metadata |
||||
369 | ); |
||||
370 | default: |
||||
371 | 6 | throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName()); |
|||
372 | } |
||||
373 | } |
||||
374 | |||||
375 | /** |
||||
376 | * @param Annotation\Annotation[] $classAnnotations |
||||
377 | * |
||||
378 | * @return Mapping\ClassMetadata |
||||
379 | * |
||||
380 | * @throws Mapping\MappingException |
||||
381 | * @throws ReflectionException |
||||
382 | * @throws UnexpectedValueException |
||||
383 | */ |
||||
384 | 372 | private function convertClassAnnotationsToEntityClassMetadata( |
|||
385 | array $classAnnotations, |
||||
386 | ReflectionClass $reflectionClass, |
||||
387 | Mapping\ClassMetadata $metadata, |
||||
388 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||||
389 | ) { |
||||
390 | /** @var Annotation\Entity $entityAnnot */ |
||||
391 | 372 | $entityAnnot = $classAnnotations[Annotation\Entity::class]; |
|||
392 | |||||
393 | 372 | if ($entityAnnot->repositoryClass !== null) { |
|||
394 | 3 | $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass); |
|||
395 | } |
||||
396 | |||||
397 | 372 | if ($entityAnnot->readOnly) { |
|||
398 | 1 | $metadata->asReadOnly(); |
|||
399 | } |
||||
400 | |||||
401 | 372 | $metadata->isMappedSuperclass = false; |
|||
402 | 372 | $metadata->isEmbeddedClass = false; |
|||
403 | |||||
404 | // Process table information |
||||
405 | 372 | $parent = $metadata->getParent(); |
|||
406 | |||||
407 | 372 | if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) { |
|||
408 | // Handle the case where a middle mapped super class inherits from a single table inheritance tree. |
||||
409 | do { |
||||
410 | 29 | if (! $parent->isMappedSuperclass) { |
|||
411 | 29 | $metadata->setTable($parent->table); |
|||
412 | |||||
413 | 29 | break; |
|||
414 | } |
||||
415 | |||||
416 | 4 | $parent = $parent->getParent(); |
|||
417 | 29 | } while ($parent !== null); |
|||
418 | } else { |
||||
419 | 372 | $tableBuilder = new Builder\TableMetadataBuilder($metadataBuildingContext); |
|||
420 | |||||
421 | $tableBuilder |
||||
422 | 372 | ->withEntityClassMetadata($metadata) |
|||
423 | 372 | ->withTableAnnotation($classAnnotations[Annotation\Table::class] ?? null); |
|||
424 | |||||
425 | 372 | $metadata->setTable($tableBuilder->build()); |
|||
426 | } |
||||
427 | |||||
428 | // Evaluate @ChangeTrackingPolicy annotation |
||||
429 | 372 | if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) { |
|||
430 | 6 | $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class]; |
|||
431 | |||||
432 | 6 | $metadata->setChangeTrackingPolicy( |
|||
433 | 6 | constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value)) |
|||
434 | ); |
||||
435 | } |
||||
436 | |||||
437 | // Evaluate @InheritanceType annotation |
||||
438 | 372 | if (isset($classAnnotations[Annotation\InheritanceType::class])) { |
|||
439 | 80 | $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class]; |
|||
440 | |||||
441 | 80 | $metadata->setInheritanceType( |
|||
442 | 80 | constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value)) |
|||
443 | ); |
||||
444 | |||||
445 | 80 | if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) { |
|||
446 | 80 | $discriminatorColumnBuilder = new Builder\DiscriminatorColumnMetadataBuilder($metadataBuildingContext); |
|||
447 | |||||
448 | $discriminatorColumnBuilder |
||||
449 | 80 | ->withComponentMetadata($metadata) |
|||
450 | 80 | ->withDiscriminatorColumnAnnotation($classAnnotations[Annotation\DiscriminatorColumn::class] ?? null); |
|||
451 | |||||
452 | 80 | $metadata->setDiscriminatorColumn($discriminatorColumnBuilder->build()); |
|||
453 | |||||
454 | // Evaluate DiscriminatorMap annotation |
||||
455 | 80 | if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) { |
|||
456 | 77 | $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class]; |
|||
457 | 77 | $discriminatorMap = $discriminatorMapAnnotation->value; |
|||
458 | |||||
459 | 77 | $metadata->setDiscriminatorMap($discriminatorMap); |
|||
460 | } |
||||
461 | } |
||||
462 | } |
||||
463 | |||||
464 | 372 | $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata); |
|||
465 | 372 | $this->attachEntityListeners($classAnnotations, $metadata); |
|||
466 | |||||
467 | 372 | return $metadata; |
|||
468 | } |
||||
469 | |||||
470 | /** |
||||
471 | * @param Annotation\Annotation[] $classAnnotations |
||||
472 | * |
||||
473 | * @throws Mapping\MappingException |
||||
474 | * @throws ReflectionException |
||||
475 | */ |
||||
476 | 23 | private function convertClassAnnotationsToMappedSuperClassMetadata( |
|||
477 | array $classAnnotations, |
||||
478 | ReflectionClass $reflectionClass, |
||||
479 | Mapping\ClassMetadata $metadata |
||||
480 | ) : Mapping\ClassMetadata { |
||||
481 | /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */ |
||||
482 | 23 | $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class]; |
|||
483 | |||||
484 | 23 | if ($mappedSuperclassAnnot->repositoryClass !== null) { |
|||
485 | 2 | $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass); |
|||
486 | } |
||||
487 | |||||
488 | 23 | $metadata->isMappedSuperclass = true; |
|||
489 | 23 | $metadata->isEmbeddedClass = false; |
|||
490 | |||||
491 | 23 | $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata); |
|||
492 | 23 | $this->attachEntityListeners($classAnnotations, $metadata); |
|||
493 | |||||
494 | 23 | return $metadata; |
|||
495 | } |
||||
496 | |||||
497 | /** |
||||
498 | * @param Annotation\Annotation[] $classAnnotations |
||||
499 | */ |
||||
500 | private function convertClassAnnotationsToEmbeddableClassMetadata( |
||||
501 | array $classAnnotations, |
||||
0 ignored issues
–
show
The parameter
$classAnnotations is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
502 | ReflectionClass $reflectionClass, |
||||
0 ignored issues
–
show
The parameter
$reflectionClass is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
503 | Mapping\ClassMetadata $metadata |
||||
504 | ) : Mapping\ClassMetadata { |
||||
505 | $metadata->isMappedSuperclass = false; |
||||
506 | $metadata->isEmbeddedClass = true; |
||||
507 | |||||
508 | return $metadata; |
||||
509 | } |
||||
510 | |||||
511 | /** |
||||
512 | * @param Annotation\Annotation[] $propertyAnnotations |
||||
513 | * |
||||
514 | * @todo guilhermeblanco Remove nullable typehint once embeddables are back |
||||
515 | */ |
||||
516 | 372 | private function convertPropertyAnnotationsToProperty( |
|||
517 | array $propertyAnnotations, |
||||
518 | ReflectionProperty $reflectionProperty, |
||||
519 | Mapping\ClassMetadata $metadata, |
||||
520 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||||
521 | ) : ?Mapping\Property { |
||||
522 | switch (true) { |
||||
523 | 372 | case isset($propertyAnnotations[Annotation\Column::class]): |
|||
524 | 367 | $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext); |
|||
525 | $fieldMetadata = $fieldBuilder |
||||
526 | 367 | ->withComponentMetadata($metadata) |
|||
527 | 367 | ->withFieldName($reflectionProperty->getName()) |
|||
528 | 367 | ->withColumnAnnotation($propertyAnnotations[Annotation\Column::class]) |
|||
529 | 367 | ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null) |
|||
530 | 367 | ->withVersionAnnotation($propertyAnnotations[Annotation\Version::class] ?? null) |
|||
531 | 367 | ->withGeneratedValueAnnotation($propertyAnnotations[Annotation\GeneratedValue::class] ?? null) |
|||
532 | 367 | ->withSequenceGeneratorAnnotation($propertyAnnotations[Annotation\SequenceGenerator::class] ?? null) |
|||
533 | 367 | ->withCustomIdGeneratorAnnotation($propertyAnnotations[Annotation\CustomIdGenerator::class] ?? null) |
|||
534 | 367 | ->build(); |
|||
535 | |||||
536 | // Prevent column duplication |
||||
537 | 367 | $columnName = $fieldMetadata->getColumnName(); |
|||
538 | |||||
539 | 367 | if ($metadata->checkPropertyDuplication($columnName)) { |
|||
540 | throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName); |
||||
541 | } |
||||
542 | |||||
543 | 367 | $metadata->fieldNames[$fieldMetadata->getColumnName()] = $fieldMetadata->getName(); |
|||
544 | |||||
545 | 367 | return $fieldMetadata; |
|||
546 | 254 | case isset($propertyAnnotations[Annotation\OneToOne::class]): |
|||
547 | 111 | $oneToOneAssociationBuilder = new Builder\OneToOneAssociationMetadataBuilder($metadataBuildingContext); |
|||
548 | $associationMetadata = $oneToOneAssociationBuilder |
||||
549 | 111 | ->withComponentMetadata($metadata) |
|||
550 | 111 | ->withFieldName($reflectionProperty->getName()) |
|||
551 | 111 | ->withOneToOneAnnotation($propertyAnnotations[Annotation\OneToOne::class] ?? null) |
|||
0 ignored issues
–
show
It seems like
$propertyAnnotations[Doc...neToOne::class] ?? null can also be of type null ; however, parameter $oneToOneAnnotation of Doctrine\ORM\Mapping\Bui...ithOneToOneAnnotation() does only seem to accept Doctrine\ORM\Annotation\OneToOne , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
552 | 111 | ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null) |
|||
553 | 111 | ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null) |
|||
554 | 111 | ->withJoinColumnsAnnotation($propertyAnnotations[Annotation\JoinColumns::class] ?? null) |
|||
555 | 111 | ->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class] ?? null) |
|||
556 | 111 | ->build(); |
|||
557 | |||||
558 | // Prevent column duplication |
||||
559 | 111 | foreach ($associationMetadata->getJoinColumns() as $joinColumnMetadata) { |
|||
560 | 105 | $columnName = $joinColumnMetadata->getColumnName(); |
|||
561 | |||||
562 | // @todo guilhermeblanco Open an issue to discuss making this scenario impossible. |
||||
563 | //if ($metadata->checkPropertyDuplication($columnName)) { |
||||
564 | // throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName); |
||||
565 | //} |
||||
566 | |||||
567 | 105 | if ($associationMetadata->isOwningSide()) { |
|||
568 | 105 | $metadata->fieldNames[$columnName] = $associationMetadata->getName(); |
|||
569 | } |
||||
570 | } |
||||
571 | |||||
572 | 111 | return $associationMetadata; |
|||
573 | 202 | case isset($propertyAnnotations[Annotation\ManyToOne::class]): |
|||
574 | 141 | $manyToOneAssociationBuilder = new Builder\ManyToOneAssociationMetadataBuilder($metadataBuildingContext); |
|||
575 | $associationMetadata = $manyToOneAssociationBuilder |
||||
576 | 141 | ->withComponentMetadata($metadata) |
|||
577 | 141 | ->withFieldName($reflectionProperty->getName()) |
|||
578 | 141 | ->withManyToOneAnnotation($propertyAnnotations[Annotation\ManyToOne::class] ?? null) |
|||
0 ignored issues
–
show
It seems like
$propertyAnnotations[Doc...nyToOne::class] ?? null can also be of type null ; however, parameter $manyToOneAnnotation of Doctrine\ORM\Mapping\Bui...thManyToOneAnnotation() does only seem to accept Doctrine\ORM\Annotation\ManyToOne , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
579 | 141 | ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null) |
|||
580 | 141 | ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null) |
|||
581 | 141 | ->withJoinColumnsAnnotation($propertyAnnotations[Annotation\JoinColumns::class] ?? null) |
|||
582 | 141 | ->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class] ?? null) |
|||
583 | 141 | ->build(); |
|||
584 | |||||
585 | // Prevent column duplication |
||||
586 | 140 | foreach ($associationMetadata->getJoinColumns() as $joinColumnMetadata) { |
|||
587 | 140 | $columnName = $joinColumnMetadata->getColumnName(); |
|||
588 | |||||
589 | // @todo guilhermeblanco Open an issue to discuss making this scenario impossible. |
||||
590 | //if ($metadata->checkPropertyDuplication($columnName)) { |
||||
591 | // throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName); |
||||
592 | //} |
||||
593 | |||||
594 | 140 | if ($associationMetadata->isOwningSide()) { |
|||
595 | 140 | $metadata->fieldNames[$columnName] = $associationMetadata->getName(); |
|||
596 | } |
||||
597 | } |
||||
598 | |||||
599 | 140 | return $associationMetadata; |
|||
600 | 163 | case isset($propertyAnnotations[Annotation\OneToMany::class]): |
|||
601 | 109 | $oneToManyAssociationBuilder = new Builder\OneToManyAssociationMetadataBuilder($metadataBuildingContext); |
|||
602 | |||||
603 | return $oneToManyAssociationBuilder |
||||
604 | 109 | ->withComponentMetadata($metadata) |
|||
605 | 109 | ->withFieldName($reflectionProperty->getName()) |
|||
606 | 109 | ->withOneToManyAnnotation($propertyAnnotations[Annotation\OneToMany::class] ?? null) |
|||
0 ignored issues
–
show
It seems like
$propertyAnnotations[Doc...eToMany::class] ?? null can also be of type null ; however, parameter $oneToManyAnnotation of Doctrine\ORM\Mapping\Bui...thOneToManyAnnotation() does only seem to accept Doctrine\ORM\Annotation\OneToMany , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
607 | 109 | ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null) |
|||
608 | 109 | ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null) |
|||
609 | 109 | ->withOrderByAnnotation($propertyAnnotations[Annotation\OrderBy::class] ?? null) |
|||
610 | 109 | ->build(); |
|||
611 | 104 | case isset($propertyAnnotations[Annotation\ManyToMany::class]): |
|||
612 | 89 | $manyToManyAssociationBuilder = new Builder\ManyToManyAssociationMetadataBuilder($metadataBuildingContext); |
|||
613 | |||||
614 | return $manyToManyAssociationBuilder |
||||
615 | 89 | ->withComponentMetadata($metadata) |
|||
616 | 89 | ->withFieldName($reflectionProperty->getName()) |
|||
617 | 89 | ->withManyToManyAnnotation($propertyAnnotations[Annotation\ManyToMany::class] ?? null) |
|||
0 ignored issues
–
show
It seems like
$propertyAnnotations[Doc...yToMany::class] ?? null can also be of type null ; however, parameter $manyToManyAnnotation of Doctrine\ORM\Mapping\Bui...hManyToManyAnnotation() does only seem to accept Doctrine\ORM\Annotation\ManyToMany , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
618 | 89 | ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null) |
|||
619 | 89 | ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class] ?? null) |
|||
620 | 89 | ->withJoinTableAnnotation($propertyAnnotations[Annotation\JoinTable::class] ?? null) |
|||
621 | 89 | ->withOrderByAnnotation($propertyAnnotations[Annotation\OrderBy::class] ?? null) |
|||
622 | 89 | ->build(); |
|||
623 | 29 | case isset($propertyAnnotations[Annotation\Embedded::class]): |
|||
624 | return null; |
||||
625 | default: |
||||
626 | 29 | $transientBuilder = new Builder\TransientMetadataBuilder($metadataBuildingContext); |
|||
627 | |||||
628 | return $transientBuilder |
||||
629 | 29 | ->withComponentMetadata($metadata) |
|||
630 | 29 | ->withFieldName($reflectionProperty->getName()) |
|||
631 | 29 | ->build(); |
|||
632 | } |
||||
633 | } |
||||
634 | |||||
635 | /** |
||||
636 | * @param Annotation\Annotation[] $classAnnotations |
||||
637 | */ |
||||
638 | 373 | private function attachLifecycleCallbacks( |
|||
639 | array $classAnnotations, |
||||
640 | ReflectionClass $reflectionClass, |
||||
641 | Mapping\ClassMetadata $metadata |
||||
642 | ) : void { |
||||
643 | // Evaluate @HasLifecycleCallbacks annotation |
||||
644 | 373 | if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) { |
|||
645 | $eventMap = [ |
||||
646 | 14 | Events::prePersist => Annotation\PrePersist::class, |
|||
647 | 14 | Events::postPersist => Annotation\PostPersist::class, |
|||
648 | 14 | Events::preUpdate => Annotation\PreUpdate::class, |
|||
649 | 14 | Events::postUpdate => Annotation\PostUpdate::class, |
|||
650 | 14 | Events::preRemove => Annotation\PreRemove::class, |
|||
651 | 14 | Events::postRemove => Annotation\PostRemove::class, |
|||
652 | 14 | Events::postLoad => Annotation\PostLoad::class, |
|||
653 | 14 | Events::preFlush => Annotation\PreFlush::class, |
|||
654 | ]; |
||||
655 | |||||
656 | /** @var ReflectionMethod $reflectionMethod */ |
||||
657 | 14 | foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { |
|||
658 | 13 | $annotations = $this->getMethodAnnotations($reflectionMethod); |
|||
659 | |||||
660 | 13 | foreach ($eventMap as $eventName => $annotationClassName) { |
|||
661 | 13 | if (isset($annotations[$annotationClassName])) { |
|||
662 | 12 | $metadata->addLifecycleCallback($eventName, $reflectionMethod->getName()); |
|||
663 | } |
||||
664 | } |
||||
665 | } |
||||
666 | } |
||||
667 | 373 | } |
|||
668 | |||||
669 | /** |
||||
670 | * @param Annotation\Annotation[] $classAnnotations |
||||
671 | * |
||||
672 | * @throws ReflectionException |
||||
673 | * @throws Mapping\MappingException |
||||
674 | */ |
||||
675 | 373 | private function attachEntityListeners( |
|||
676 | array $classAnnotations, |
||||
677 | Mapping\ClassMetadata $metadata |
||||
678 | ) : void { |
||||
679 | // Evaluate @EntityListeners annotation |
||||
680 | 373 | if (isset($classAnnotations[Annotation\EntityListeners::class])) { |
|||
681 | /** @var Annotation\EntityListeners $entityListenersAnnot */ |
||||
682 | 8 | $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class]; |
|||
683 | $eventMap = [ |
||||
684 | 8 | Events::prePersist => Annotation\PrePersist::class, |
|||
685 | 8 | Events::postPersist => Annotation\PostPersist::class, |
|||
686 | 8 | Events::preUpdate => Annotation\PreUpdate::class, |
|||
687 | 8 | Events::postUpdate => Annotation\PostUpdate::class, |
|||
688 | 8 | Events::preRemove => Annotation\PreRemove::class, |
|||
689 | 8 | Events::postRemove => Annotation\PostRemove::class, |
|||
690 | 8 | Events::postLoad => Annotation\PostLoad::class, |
|||
691 | 8 | Events::preFlush => Annotation\PreFlush::class, |
|||
692 | ]; |
||||
693 | |||||
694 | 8 | foreach ($entityListenersAnnot->value as $listenerClassName) { |
|||
695 | 8 | if (! class_exists($listenerClassName)) { |
|||
696 | throw Mapping\MappingException::entityListenerClassNotFound( |
||||
697 | $listenerClassName, |
||||
698 | $metadata->getClassName() |
||||
699 | ); |
||||
700 | } |
||||
701 | |||||
702 | 8 | $listenerClass = new ReflectionClass($listenerClassName); |
|||
703 | |||||
704 | /** @var ReflectionMethod $reflectionMethod */ |
||||
705 | 8 | foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { |
|||
706 | 8 | $annotations = $this->getMethodAnnotations($reflectionMethod); |
|||
707 | |||||
708 | 8 | foreach ($eventMap as $eventName => $annotationClassName) { |
|||
709 | 8 | if (isset($annotations[$annotationClassName])) { |
|||
710 | 6 | $metadata->addEntityListener($eventName, $listenerClassName, $reflectionMethod->getName()); |
|||
711 | } |
||||
712 | } |
||||
713 | } |
||||
714 | } |
||||
715 | } |
||||
716 | 373 | } |
|||
717 | |||||
718 | /** |
||||
719 | * @param Annotation\Annotation[] $classAnnotations |
||||
720 | * |
||||
721 | * @throws Mapping\MappingException |
||||
722 | */ |
||||
723 | 370 | private function attachPropertyOverrides( |
|||
724 | array $classAnnotations, |
||||
725 | ReflectionClass $reflectionClass, |
||||
726 | Mapping\ClassMetadata $metadata, |
||||
727 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||||
728 | ) : void { |
||||
729 | // Evaluate AssociationOverrides annotation |
||||
730 | 370 | if (isset($classAnnotations[Annotation\AssociationOverrides::class])) { |
|||
731 | 5 | $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class]; |
|||
732 | |||||
733 | 5 | foreach ($associationOverridesAnnot->value as $associationOverrideAnnotation) { |
|||
734 | 5 | $fieldName = $associationOverrideAnnotation->name; |
|||
735 | 5 | $property = $metadata->getProperty($fieldName); |
|||
736 | |||||
737 | 5 | if (! $property) { |
|||
738 | throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName); |
||||
739 | } |
||||
740 | |||||
741 | 5 | $override = clone $property; |
|||
742 | |||||
743 | // Check for JoinColumn/JoinColumns annotations |
||||
744 | 5 | if ($associationOverrideAnnotation->joinColumns) { |
|||
745 | 3 | $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext); |
|||
746 | |||||
747 | $joinColumnBuilder |
||||
748 | 3 | ->withComponentMetadata($metadata) |
|||
749 | 3 | ->withFieldName($fieldName); |
|||
750 | |||||
751 | 3 | $joinColumns = []; |
|||
752 | |||||
753 | 3 | foreach ($associationOverrideAnnotation->joinColumns as $joinColumnAnnotation) { |
|||
754 | 3 | $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation); |
|||
755 | |||||
756 | 3 | $joinColumnMetadata = $joinColumnBuilder->build(); |
|||
757 | 3 | $columnName = $joinColumnMetadata->getColumnName(); |
|||
758 | |||||
759 | // @todo guilhermeblanco Open an issue to discuss making this scenario impossible. |
||||
760 | //if ($metadata->checkPropertyDuplication($columnName)) { |
||||
761 | // throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName); |
||||
762 | //} |
||||
763 | |||||
764 | 3 | if ($override->isOwningSide()) { |
|||
765 | 3 | $metadata->fieldNames[$columnName] = $fieldName; |
|||
766 | } |
||||
767 | |||||
768 | 3 | $joinColumns[] = $joinColumnMetadata; |
|||
769 | } |
||||
770 | |||||
771 | 3 | $override->setJoinColumns($joinColumns); |
|||
772 | } |
||||
773 | |||||
774 | // Check for JoinTable annotations |
||||
775 | 5 | if ($associationOverrideAnnotation->joinTable) { |
|||
776 | 2 | $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext); |
|||
777 | |||||
778 | $joinTableBuilder |
||||
779 | 2 | ->withComponentMetadata($metadata) |
|||
780 | 2 | ->withFieldName($fieldName) |
|||
781 | 2 | ->withTargetEntity($property->getTargetEntity()) |
|||
782 | 2 | ->withJoinTableAnnotation($associationOverrideAnnotation->joinTable); |
|||
783 | |||||
784 | 2 | $override->setJoinTable($joinTableBuilder->build()); |
|||
785 | } |
||||
786 | |||||
787 | // Check for inversedBy |
||||
788 | 5 | if ($associationOverrideAnnotation->inversedBy) { |
|||
789 | 1 | $override->setInversedBy($associationOverrideAnnotation->inversedBy); |
|||
790 | } |
||||
791 | |||||
792 | // Check for fetch |
||||
793 | 5 | if ($associationOverrideAnnotation->fetch) { |
|||
794 | 1 | $override->setFetchMode(constant(Mapping\FetchMode::class . '::' . $associationOverrideAnnotation->fetch)); |
|||
795 | } |
||||
796 | |||||
797 | 5 | $metadata->setPropertyOverride($override); |
|||
798 | } |
||||
799 | } |
||||
800 | |||||
801 | // Evaluate AttributeOverrides annotation |
||||
802 | 370 | if (isset($classAnnotations[Annotation\AttributeOverrides::class])) { |
|||
803 | 3 | $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class]; |
|||
804 | 3 | $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext); |
|||
805 | |||||
806 | $fieldBuilder |
||||
807 | 3 | ->withComponentMetadata($metadata) |
|||
808 | 3 | ->withIdAnnotation(null) |
|||
809 | 3 | ->withVersionAnnotation(null); |
|||
810 | |||||
811 | 3 | foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnotation) { |
|||
812 | 3 | $fieldName = $attributeOverrideAnnotation->name; |
|||
813 | 3 | $property = $metadata->getProperty($fieldName); |
|||
814 | |||||
815 | 3 | if (! $property) { |
|||
816 | throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName); |
||||
817 | } |
||||
818 | |||||
819 | $fieldBuilder |
||||
820 | 3 | ->withFieldName($fieldName) |
|||
821 | 3 | ->withColumnAnnotation($attributeOverrideAnnotation->column); |
|||
822 | |||||
823 | 3 | $fieldMetadata = $fieldBuilder->build(); |
|||
824 | 3 | $columnName = $fieldMetadata->getColumnName(); |
|||
825 | |||||
826 | // Prevent column duplication |
||||
827 | 3 | if ($metadata->checkPropertyDuplication($columnName)) { |
|||
828 | throw Mapping\MappingException::duplicateColumnName($metadata->getClassName(), $columnName); |
||||
829 | } |
||||
830 | |||||
831 | 3 | $metadata->fieldNames[$fieldMetadata->getColumnName()] = $fieldName; |
|||
832 | |||||
833 | 3 | $metadata->setPropertyOverride($fieldMetadata); |
|||
834 | } |
||||
835 | } |
||||
836 | 370 | } |
|||
837 | |||||
838 | /** |
||||
839 | * @return Annotation\Annotation[] |
||||
840 | */ |
||||
841 | 379 | private function getClassAnnotations(ReflectionClass $reflectionClass) : array |
|||
842 | { |
||||
843 | 379 | $classAnnotations = $this->reader->getClassAnnotations($reflectionClass); |
|||
844 | |||||
845 | 379 | foreach ($classAnnotations as $key => $annot) { |
|||
846 | 373 | if (! is_numeric($key)) { |
|||
847 | continue; |
||||
848 | } |
||||
849 | |||||
850 | 373 | $classAnnotations[get_class($annot)] = $annot; |
|||
851 | } |
||||
852 | |||||
853 | 379 | return $classAnnotations; |
|||
854 | } |
||||
855 | |||||
856 | /** |
||||
857 | * @return Annotation\Annotation[] |
||||
858 | */ |
||||
859 | 373 | private function getPropertyAnnotations(ReflectionProperty $reflectionProperty) : array |
|||
860 | { |
||||
861 | 373 | $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty); |
|||
862 | |||||
863 | 372 | foreach ($propertyAnnotations as $key => $annot) { |
|||
864 | 372 | if (! is_numeric($key)) { |
|||
865 | continue; |
||||
866 | } |
||||
867 | |||||
868 | 372 | $propertyAnnotations[get_class($annot)] = $annot; |
|||
869 | } |
||||
870 | |||||
871 | 372 | return $propertyAnnotations; |
|||
872 | } |
||||
873 | |||||
874 | /** |
||||
875 | * @return Annotation\Annotation[] |
||||
876 | */ |
||||
877 | 21 | private function getMethodAnnotations(ReflectionMethod $reflectionMethod) : array |
|||
878 | { |
||||
879 | 21 | $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod); |
|||
880 | |||||
881 | 21 | foreach ($methodAnnotations as $key => $annot) { |
|||
882 | 18 | if (! is_numeric($key)) { |
|||
883 | continue; |
||||
884 | } |
||||
885 | |||||
886 | 18 | $methodAnnotations[get_class($annot)] = $annot; |
|||
887 | } |
||||
888 | |||||
889 | 21 | return $methodAnnotations; |
|||
890 | } |
||||
891 | } |
||||
892 |
The
break
statement is not necessary if it is preceded for example by areturn
statement: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.