1 | <?php /** @noinspection ALL */ |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Doctrine\ORM\Mapping\Driver; |
||
6 | |||
7 | use Doctrine\Common\Annotations\AnnotationReader; |
||
8 | use Doctrine\Common\Annotations\Reader; |
||
9 | use Doctrine\DBAL\Types\Type; |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
10 | use Doctrine\ORM\Annotation; |
||
11 | use Doctrine\ORM\Cache\Exception\CacheException; |
||
12 | use Doctrine\ORM\Events; |
||
13 | use Doctrine\ORM\Mapping; |
||
14 | use Doctrine\ORM\Mapping\Builder; |
||
15 | use FilesystemIterator; |
||
16 | use RecursiveDirectoryIterator; |
||
17 | use RecursiveIteratorIterator; |
||
18 | use RecursiveRegexIterator; |
||
19 | use ReflectionClass; |
||
20 | use ReflectionException; |
||
21 | use ReflectionMethod; |
||
22 | use ReflectionProperty; |
||
23 | use RegexIterator; |
||
24 | use RuntimeException; |
||
25 | use UnexpectedValueException; |
||
26 | use function array_diff; |
||
27 | use function array_intersect; |
||
28 | use function array_map; |
||
29 | use function array_merge; |
||
30 | use function array_unique; |
||
31 | use function class_exists; |
||
32 | use function constant; |
||
33 | use function count; |
||
34 | use function defined; |
||
35 | use function get_class; |
||
36 | use function get_declared_classes; |
||
37 | use function in_array; |
||
38 | use function is_dir; |
||
39 | use function is_numeric; |
||
40 | use function preg_match; |
||
41 | use function preg_quote; |
||
42 | use function realpath; |
||
43 | use function sprintf; |
||
44 | use function str_replace; |
||
45 | use function strpos; |
||
46 | use function strtoupper; |
||
0 ignored issues
–
show
|
|||
47 | use function var_export; |
||
0 ignored issues
–
show
|
|||
48 | |||
49 | /** |
||
50 | * The AnnotationDriver reads the mapping metadata from docblock annotations. |
||
51 | */ |
||
52 | class AnnotationDriver implements MappingDriver |
||
53 | { |
||
54 | /** @var int[] */ |
||
55 | protected $entityAnnotationClasses = [ |
||
56 | Annotation\Entity::class => 1, |
||
57 | Annotation\MappedSuperclass::class => 2, |
||
58 | ]; |
||
59 | |||
60 | /** |
||
61 | * The AnnotationReader. |
||
62 | * |
||
63 | * @var AnnotationReader |
||
64 | */ |
||
65 | protected $reader; |
||
66 | |||
67 | /** |
||
68 | * The paths where to look for mapping files. |
||
69 | * |
||
70 | * @var string[] |
||
71 | */ |
||
72 | protected $paths = []; |
||
73 | |||
74 | /** |
||
75 | * The paths excluded from path where to look for mapping files. |
||
76 | * |
||
77 | * @var string[] |
||
78 | */ |
||
79 | protected $excludePaths = []; |
||
80 | |||
81 | /** |
||
82 | * The file extension of mapping documents. |
||
83 | * |
||
84 | * @var string |
||
85 | */ |
||
86 | protected $fileExtension = '.php'; |
||
87 | |||
88 | /** |
||
89 | * Cache for AnnotationDriver#getAllClassNames(). |
||
90 | * |
||
91 | * @var string[]|null |
||
92 | */ |
||
93 | protected $classNames; |
||
94 | |||
95 | /** |
||
96 | * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading |
||
97 | * docblock annotations. |
||
98 | * |
||
99 | * @param Reader $reader The AnnotationReader to use, duck-typed. |
||
100 | * @param string|string[]|null $paths One or multiple paths where mapping classes can be found. |
||
101 | */ |
||
102 | 2297 | public function __construct(Reader $reader, $paths = null) |
|
103 | { |
||
104 | 2297 | $this->reader = $reader; |
|
105 | |||
106 | 2297 | if ($paths) { |
|
107 | 2211 | $this->addPaths((array) $paths); |
|
108 | } |
||
109 | 2297 | } |
|
110 | |||
111 | /** |
||
112 | * Appends lookup paths to metadata driver. |
||
113 | * |
||
114 | * @param string[] $paths |
||
115 | */ |
||
116 | 2215 | public function addPaths(array $paths) |
|
117 | { |
||
118 | 2215 | $this->paths = array_unique(array_merge($this->paths, $paths)); |
|
119 | 2215 | } |
|
120 | |||
121 | /** |
||
122 | * Retrieves the defined metadata lookup paths. |
||
123 | * |
||
124 | * @return string[] |
||
125 | */ |
||
126 | public function getPaths() |
||
127 | { |
||
128 | return $this->paths; |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * Append exclude lookup paths to metadata driver. |
||
133 | * |
||
134 | * @param string[] $paths |
||
135 | */ |
||
136 | public function addExcludePaths(array $paths) |
||
137 | { |
||
138 | $this->excludePaths = array_unique(array_merge($this->excludePaths, $paths)); |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * Retrieve the defined metadata lookup exclude paths. |
||
143 | * |
||
144 | * @return string[] |
||
145 | */ |
||
146 | public function getExcludePaths() |
||
147 | { |
||
148 | return $this->excludePaths; |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Retrieve the current annotation reader |
||
153 | * |
||
154 | * @return Reader |
||
155 | */ |
||
156 | 1 | public function getReader() |
|
157 | { |
||
158 | 1 | return $this->reader; |
|
159 | } |
||
160 | |||
161 | /** |
||
162 | * Gets the file extension used to look for mapping files under. |
||
163 | * |
||
164 | * @return string |
||
165 | */ |
||
166 | public function getFileExtension() |
||
167 | { |
||
168 | return $this->fileExtension; |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * Sets the file extension used to look for mapping files under. |
||
173 | * |
||
174 | * @param string $fileExtension The file extension to set. |
||
175 | */ |
||
176 | public function setFileExtension($fileExtension) |
||
177 | { |
||
178 | $this->fileExtension = $fileExtension; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Returns whether the class with the specified name is transient. Only non-transient |
||
183 | * classes, that is entities and mapped superclasses, should have their metadata loaded. |
||
184 | * |
||
185 | * A class is non-transient if it is annotated with an annotation |
||
186 | * from the {@see AnnotationDriver::entityAnnotationClasses}. |
||
187 | * |
||
188 | * @param string $className |
||
189 | * |
||
190 | * @throws ReflectionException |
||
191 | */ |
||
192 | 193 | public function isTransient($className) : bool |
|
193 | { |
||
194 | 193 | $classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className)); |
|
195 | |||
196 | 193 | foreach ($classAnnotations as $annotation) { |
|
197 | 188 | if (isset($this->entityAnnotationClasses[get_class($annotation)])) { |
|
198 | 188 | return false; |
|
199 | } |
||
200 | } |
||
201 | |||
202 | 12 | return true; |
|
203 | } |
||
204 | |||
205 | /** |
||
206 | * {@inheritdoc} |
||
207 | * |
||
208 | * @throws ReflectionException |
||
209 | */ |
||
210 | 60 | public function getAllClassNames() : array |
|
211 | { |
||
212 | 60 | if ($this->classNames !== null) { |
|
213 | 45 | return $this->classNames; |
|
214 | } |
||
215 | |||
216 | 60 | if (! $this->paths) { |
|
217 | throw Mapping\MappingException::pathRequired(); |
||
218 | } |
||
219 | |||
220 | 60 | $classes = []; |
|
221 | 60 | $includedFiles = []; |
|
222 | |||
223 | 60 | foreach ($this->paths as $path) { |
|
224 | 60 | if (! is_dir($path)) { |
|
225 | throw Mapping\MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); |
||
226 | } |
||
227 | |||
228 | 60 | $iterator = new RegexIterator( |
|
229 | 60 | new RecursiveIteratorIterator( |
|
230 | 60 | new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), |
|
231 | 60 | RecursiveIteratorIterator::LEAVES_ONLY |
|
232 | ), |
||
233 | 60 | '/^.+' . preg_quote($this->fileExtension) . '$/i', |
|
234 | 60 | RecursiveRegexIterator::GET_MATCH |
|
235 | ); |
||
236 | |||
237 | 60 | foreach ($iterator as $file) { |
|
238 | 60 | $sourceFile = $file[0]; |
|
239 | |||
240 | 60 | if (! preg_match('(^phar:)i', $sourceFile)) { |
|
241 | 60 | $sourceFile = realpath($sourceFile); |
|
242 | } |
||
243 | |||
244 | 60 | foreach ($this->excludePaths as $excludePath) { |
|
245 | $exclude = str_replace('\\', '/', realpath($excludePath)); |
||
246 | $current = str_replace('\\', '/', $sourceFile); |
||
247 | |||
248 | if (strpos($current, $exclude) !== false) { |
||
249 | continue 2; |
||
250 | } |
||
251 | } |
||
252 | |||
253 | 60 | require_once $sourceFile; |
|
254 | |||
255 | 60 | $includedFiles[] = $sourceFile; |
|
256 | } |
||
257 | } |
||
258 | |||
259 | 60 | $declared = get_declared_classes(); |
|
260 | |||
261 | 60 | foreach ($declared as $className) { |
|
262 | 60 | $reflectionClass = new ReflectionClass($className); |
|
263 | 60 | $sourceFile = $reflectionClass->getFileName(); |
|
264 | |||
265 | 60 | if (in_array($sourceFile, $includedFiles, true) && ! $this->isTransient($className)) { |
|
266 | 60 | $classes[] = $className; |
|
267 | } |
||
268 | } |
||
269 | |||
270 | 60 | $this->classNames = $classes; |
|
271 | |||
272 | 60 | return $classes; |
|
273 | } |
||
274 | |||
275 | /** |
||
276 | * {@inheritDoc} |
||
277 | * |
||
278 | * @throws CacheException |
||
279 | * @throws Mapping\MappingException |
||
280 | * @throws ReflectionException |
||
281 | * @throws RuntimeException |
||
282 | * @throws UnexpectedValueException |
||
283 | */ |
||
284 | 379 | public function loadMetadataForClass( |
|
285 | string $className, |
||
286 | ?Mapping\ComponentMetadata $parent, |
||
287 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
288 | ) : Mapping\ComponentMetadata { |
||
289 | 379 | $reflectionClass = new ReflectionClass($className); |
|
290 | 379 | $metadata = new Mapping\ClassMetadata($className, $parent, $metadataBuildingContext); |
|
291 | 379 | $classAnnotations = $this->getClassAnnotations($reflectionClass); |
|
292 | 379 | $classMetadata = $this->convertClassAnnotationsToClassMetadata( |
|
293 | 379 | $classAnnotations, |
|
294 | 379 | $reflectionClass, |
|
295 | 379 | $metadata, |
|
296 | 379 | $metadataBuildingContext |
|
297 | ); |
||
298 | |||
299 | // Evaluate @Cache annotation |
||
300 | 373 | if (isset($classAnnotations[Annotation\Cache::class])) { |
|
301 | 18 | $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext); |
|
302 | |||
303 | $cacheBuilder |
||
304 | 18 | ->withComponentMetadata($metadata) |
|
305 | 18 | ->withCacheAnnotation($classAnnotations[Annotation\Cache::class]); |
|
306 | |||
307 | 18 | $metadata->setCache($cacheBuilder->build()); |
|
308 | } |
||
309 | |||
310 | // Evaluate annotations on properties/fields |
||
311 | /** @var ReflectionProperty $reflProperty */ |
||
312 | 373 | foreach ($reflectionClass->getProperties() as $reflectionProperty) { |
|
313 | 373 | if ($reflectionProperty->getDeclaringClass()->getName() !== $reflectionClass->getName()) { |
|
314 | 74 | continue; |
|
315 | } |
||
316 | |||
317 | 373 | $propertyAnnotations = $this->getPropertyAnnotations($reflectionProperty); |
|
318 | 372 | $property = $this->convertPropertyAnnotationsToProperty( |
|
319 | 372 | $propertyAnnotations, |
|
320 | 372 | $reflectionProperty, |
|
321 | 372 | $classMetadata, |
|
322 | 372 | $metadataBuildingContext |
|
323 | ); |
||
324 | |||
325 | 372 | if ($classMetadata->isMappedSuperclass && |
|
326 | 372 | $property instanceof Mapping\ToManyAssociationMetadata && |
|
327 | 372 | ! $property->isOwningSide()) { |
|
328 | 1 | throw Mapping\MappingException::illegalToManyAssociationOnMappedSuperclass( |
|
329 | 1 | $classMetadata->getClassName(), |
|
330 | 1 | $property->getName() |
|
331 | ); |
||
332 | } |
||
333 | |||
334 | 371 | $metadata->addProperty($property); |
|
335 | } |
||
336 | |||
337 | 370 | $this->attachPropertyOverrides($classAnnotations, $reflectionClass, $metadata, $metadataBuildingContext); |
|
338 | |||
339 | 370 | return $classMetadata; |
|
340 | } |
||
341 | |||
342 | /** |
||
343 | * @param Annotation\Annotation[] $classAnnotations |
||
344 | * |
||
345 | * @throws Mapping\MappingException |
||
346 | * @throws UnexpectedValueException |
||
347 | * @throws ReflectionException |
||
348 | */ |
||
349 | 379 | private function convertClassAnnotationsToClassMetadata( |
|
350 | array $classAnnotations, |
||
351 | ReflectionClass $reflectionClass, |
||
352 | Mapping\ClassMetadata $metadata, |
||
353 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
354 | ) : Mapping\ClassMetadata { |
||
355 | switch (true) { |
||
356 | 379 | case isset($classAnnotations[Annotation\Entity::class]): |
|
357 | 372 | return $this->convertClassAnnotationsToEntityClassMetadata( |
|
358 | 372 | $classAnnotations, |
|
359 | 372 | $reflectionClass, |
|
360 | 372 | $metadata, |
|
361 | 372 | $metadataBuildingContext |
|
362 | ); |
||
363 | |||
364 | break; |
||
365 | |||
366 | 29 | case isset($classAnnotations[Annotation\MappedSuperclass::class]): |
|
367 | 23 | return $this->convertClassAnnotationsToMappedSuperClassMetadata( |
|
368 | 23 | $classAnnotations, |
|
369 | 23 | $reflectionClass, |
|
370 | 23 | $metadata |
|
371 | ); |
||
372 | 6 | case isset($classAnnotations[Annotation\Embeddable::class]): |
|
373 | return $this->convertClassAnnotationsToEmbeddableClassMetadata( |
||
374 | $classAnnotations, |
||
375 | $reflectionClass, |
||
376 | $metadata |
||
377 | ); |
||
378 | default: |
||
379 | 6 | throw Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass($reflectionClass->getName()); |
|
380 | } |
||
381 | } |
||
382 | |||
383 | /** |
||
384 | * @param Annotation\Annotation[] $classAnnotations |
||
385 | * |
||
386 | * @return Mapping\ClassMetadata |
||
387 | * |
||
388 | * @throws Mapping\MappingException |
||
389 | * @throws ReflectionException |
||
390 | * @throws UnexpectedValueException |
||
391 | */ |
||
392 | 372 | private function convertClassAnnotationsToEntityClassMetadata( |
|
393 | array $classAnnotations, |
||
394 | ReflectionClass $reflectionClass, |
||
395 | Mapping\ClassMetadata $metadata, |
||
396 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
397 | ) { |
||
398 | /** @var Annotation\Entity $entityAnnot */ |
||
399 | 372 | $entityAnnot = $classAnnotations[Annotation\Entity::class]; |
|
400 | |||
401 | 372 | if ($entityAnnot->repositoryClass !== null) { |
|
402 | 3 | $metadata->setCustomRepositoryClassName($entityAnnot->repositoryClass); |
|
403 | } |
||
404 | |||
405 | 372 | if ($entityAnnot->readOnly) { |
|
406 | 1 | $metadata->asReadOnly(); |
|
407 | } |
||
408 | |||
409 | 372 | $metadata->isMappedSuperclass = false; |
|
410 | 372 | $metadata->isEmbeddedClass = false; |
|
411 | |||
412 | // Process table information |
||
413 | 372 | $parent = $metadata->getParent(); |
|
414 | |||
415 | 372 | if ($parent && $parent->inheritanceType === Mapping\InheritanceType::SINGLE_TABLE) { |
|
416 | // Handle the case where a middle mapped super class inherits from a single table inheritance tree. |
||
417 | do { |
||
418 | 29 | if (! $parent->isMappedSuperclass) { |
|
419 | 29 | $metadata->setTable($parent->table); |
|
420 | |||
421 | 29 | break; |
|
422 | } |
||
423 | |||
424 | 4 | $parent = $parent->getParent(); |
|
425 | 29 | } while ($parent !== null); |
|
426 | } else { |
||
427 | 372 | $tableBuilder = new Builder\TableMetadataBuilder($metadataBuildingContext); |
|
428 | |||
429 | $tableBuilder |
||
430 | 372 | ->withEntityClassMetadata($metadata) |
|
431 | 372 | ->withTableAnnotation($classAnnotations[Annotation\Table::class] ?? null); |
|
432 | |||
433 | 372 | $metadata->setTable($tableBuilder->build()); |
|
434 | } |
||
435 | |||
436 | // Evaluate @ChangeTrackingPolicy annotation |
||
437 | 372 | if (isset($classAnnotations[Annotation\ChangeTrackingPolicy::class])) { |
|
438 | 6 | $changeTrackingAnnot = $classAnnotations[Annotation\ChangeTrackingPolicy::class]; |
|
439 | |||
440 | 6 | $metadata->setChangeTrackingPolicy( |
|
441 | 6 | constant(sprintf('%s::%s', Mapping\ChangeTrackingPolicy::class, $changeTrackingAnnot->value)) |
|
442 | ); |
||
443 | } |
||
444 | |||
445 | // Evaluate @InheritanceType annotation |
||
446 | 372 | if (isset($classAnnotations[Annotation\InheritanceType::class])) { |
|
447 | 80 | $inheritanceTypeAnnot = $classAnnotations[Annotation\InheritanceType::class]; |
|
448 | |||
449 | 80 | $metadata->setInheritanceType( |
|
450 | 80 | constant(sprintf('%s::%s', Mapping\InheritanceType::class, $inheritanceTypeAnnot->value)) |
|
451 | ); |
||
452 | |||
453 | 80 | if ($metadata->inheritanceType !== Mapping\InheritanceType::NONE) { |
|
454 | 80 | $discriminatorColumnBuilder = new Builder\DiscriminatorColumnMetadataBuilder($metadataBuildingContext); |
|
455 | |||
456 | $discriminatorColumnBuilder |
||
457 | 80 | ->withComponentMetadata($metadata) |
|
458 | 80 | ->withDiscriminatorColumnAnnotation($classAnnotations[Annotation\DiscriminatorColumn::class] ?? null); |
|
459 | |||
460 | 80 | $metadata->setDiscriminatorColumn($discriminatorColumnBuilder->build()); |
|
461 | |||
462 | // Evaluate DiscriminatorMap annotation |
||
463 | 80 | if (isset($classAnnotations[Annotation\DiscriminatorMap::class])) { |
|
464 | 77 | $discriminatorMapAnnotation = $classAnnotations[Annotation\DiscriminatorMap::class]; |
|
465 | 77 | $discriminatorMap = $discriminatorMapAnnotation->value; |
|
466 | |||
467 | 77 | $metadata->setDiscriminatorMap($discriminatorMap); |
|
468 | } |
||
469 | } |
||
470 | } |
||
471 | |||
472 | 372 | $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata); |
|
473 | 372 | $this->attachEntityListeners($classAnnotations, $metadata); |
|
474 | |||
475 | 372 | return $metadata; |
|
476 | } |
||
477 | |||
478 | /** |
||
479 | * @param Annotation\Annotation[] $classAnnotations |
||
480 | * |
||
481 | * @throws Mapping\MappingException |
||
482 | * @throws ReflectionException |
||
483 | */ |
||
484 | 23 | private function convertClassAnnotationsToMappedSuperClassMetadata( |
|
485 | array $classAnnotations, |
||
486 | ReflectionClass $reflectionClass, |
||
487 | Mapping\ClassMetadata $metadata |
||
488 | ) : Mapping\ClassMetadata { |
||
489 | /** @var Annotation\MappedSuperclass $mappedSuperclassAnnot */ |
||
490 | 23 | $mappedSuperclassAnnot = $classAnnotations[Annotation\MappedSuperclass::class]; |
|
491 | |||
492 | 23 | if ($mappedSuperclassAnnot->repositoryClass !== null) { |
|
493 | 2 | $metadata->setCustomRepositoryClassName($mappedSuperclassAnnot->repositoryClass); |
|
494 | } |
||
495 | |||
496 | 23 | $metadata->isMappedSuperclass = true; |
|
497 | 23 | $metadata->isEmbeddedClass = false; |
|
498 | |||
499 | 23 | $this->attachLifecycleCallbacks($classAnnotations, $reflectionClass, $metadata); |
|
500 | 23 | $this->attachEntityListeners($classAnnotations, $metadata); |
|
501 | |||
502 | 23 | return $metadata; |
|
503 | } |
||
504 | |||
505 | /** |
||
506 | * @param Annotation\Annotation[] $classAnnotations |
||
507 | */ |
||
508 | private function convertClassAnnotationsToEmbeddableClassMetadata( |
||
509 | array $classAnnotations, |
||
510 | ReflectionClass $reflectionClass, |
||
511 | Mapping\ClassMetadata $metadata |
||
512 | ) : Mapping\ClassMetadata { |
||
513 | $metadata->isMappedSuperclass = false; |
||
514 | $metadata->isEmbeddedClass = true; |
||
515 | |||
516 | return $metadata; |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * @param Annotation\Annotation[] $propertyAnnotations |
||
521 | * |
||
522 | * @todo guilhermeblanco Remove nullable typehint once embeddables are back |
||
523 | */ |
||
524 | 372 | private function convertPropertyAnnotationsToProperty( |
|
525 | array $propertyAnnotations, |
||
526 | ReflectionProperty $reflectionProperty, |
||
527 | Mapping\ClassMetadata $metadata, |
||
528 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
529 | ) : ?Mapping\Property { |
||
530 | switch (true) { |
||
531 | 372 | case isset($propertyAnnotations[Annotation\Column::class]): |
|
532 | 367 | return $this->convertReflectionPropertyToFieldMetadata( |
|
533 | 367 | $reflectionProperty, |
|
534 | 367 | $propertyAnnotations, |
|
535 | 367 | $metadata, |
|
536 | 367 | $metadataBuildingContext |
|
537 | ); |
||
538 | 254 | case isset($propertyAnnotations[Annotation\OneToOne::class]): |
|
539 | 111 | return $this->convertReflectionPropertyToOneToOneAssociationMetadata( |
|
540 | 111 | $reflectionProperty, |
|
541 | 111 | $propertyAnnotations, |
|
542 | 111 | $metadata, |
|
543 | 111 | $metadataBuildingContext |
|
544 | ); |
||
545 | 202 | case isset($propertyAnnotations[Annotation\ManyToOne::class]): |
|
546 | 141 | return $this->convertReflectionPropertyToManyToOneAssociationMetadata( |
|
547 | 141 | $reflectionProperty, |
|
548 | 141 | $propertyAnnotations, |
|
549 | 141 | $metadata, |
|
550 | 141 | $metadataBuildingContext |
|
551 | ); |
||
552 | 163 | case isset($propertyAnnotations[Annotation\OneToMany::class]): |
|
553 | 109 | return $this->convertReflectionPropertyToOneToManyAssociationMetadata( |
|
554 | 109 | $reflectionProperty, |
|
555 | 109 | $propertyAnnotations, |
|
556 | 109 | $metadata, |
|
557 | 109 | $metadataBuildingContext |
|
558 | ); |
||
559 | 104 | case isset($propertyAnnotations[Annotation\ManyToMany::class]): |
|
560 | 89 | return $this->convertReflectionPropertyToManyToManyAssociationMetadata( |
|
561 | 89 | $reflectionProperty, |
|
562 | 89 | $propertyAnnotations, |
|
563 | 89 | $metadata, |
|
564 | 89 | $metadataBuildingContext |
|
565 | ); |
||
566 | 29 | case isset($propertyAnnotations[Annotation\Embedded::class]): |
|
567 | return null; |
||
568 | default: |
||
569 | 29 | $transientBuilder = new Builder\TransientMetadataBuilder($metadataBuildingContext); |
|
570 | |||
571 | $transientBuilder |
||
572 | 29 | ->withComponentMetadata($metadata) |
|
573 | 29 | ->withFieldName($reflectionProperty->getName()); |
|
574 | |||
575 | 29 | return $transientBuilder->build(); |
|
576 | } |
||
577 | } |
||
578 | |||
579 | /** |
||
580 | * @param Annotation\Annotation[] $propertyAnnotations |
||
581 | * |
||
582 | * @throws Mapping\MappingException |
||
583 | */ |
||
584 | 367 | private function convertReflectionPropertyToFieldMetadata( |
|
585 | ReflectionProperty $reflectionProperty, |
||
586 | array $propertyAnnotations, |
||
587 | Mapping\ClassMetadata $metadata, |
||
588 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
589 | ) : Mapping\FieldMetadata { |
||
590 | 367 | $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext); |
|
591 | $fieldMetadata = $fieldBuilder |
||
592 | 367 | ->withComponentMetadata($metadata) |
|
593 | 367 | ->withFieldName($reflectionProperty->getName()) |
|
594 | 367 | ->withColumnAnnotation($propertyAnnotations[Annotation\Column::class]) |
|
595 | 367 | ->withIdAnnotation($propertyAnnotations[Annotation\Id::class] ?? null) |
|
596 | 367 | ->withVersionAnnotation($propertyAnnotations[Annotation\Version::class] ?? null) |
|
597 | 367 | ->withGeneratedValueAnnotation($propertyAnnotations[Annotation\GeneratedValue::class] ?? null) |
|
598 | 367 | ->withSequenceGeneratorAnnotation($propertyAnnotations[Annotation\SequenceGenerator::class] ?? null) |
|
599 | 367 | ->withCustomIdGeneratorAnnotation($propertyAnnotations[Annotation\CustomIdGenerator::class] ?? null) |
|
600 | 367 | ->build(); |
|
601 | |||
602 | // Prevent column duplication |
||
603 | 367 | if ($metadata->checkPropertyDuplication($fieldMetadata->getColumnName())) { |
|
604 | throw Mapping\MappingException::duplicateColumnName( |
||
605 | $metadata->getClassName(), |
||
606 | $fieldMetadata->getColumnName() |
||
607 | ); |
||
608 | } |
||
609 | |||
610 | // // Check for GeneratedValue strategy |
||
611 | // if (isset($propertyAnnotations[Annotation\GeneratedValue::class])) { |
||
612 | // $generatedValueAnnot = $propertyAnnotations[Annotation\GeneratedValue::class]; |
||
613 | // $strategy = strtoupper($generatedValueAnnot->strategy); |
||
614 | // $idGeneratorType = constant(sprintf('%s::%s', Mapping\GeneratorType::class, $strategy)); |
||
615 | // |
||
616 | // if ($idGeneratorType !== Mapping\GeneratorType::NONE) { |
||
617 | // $idGeneratorDefinition = []; |
||
618 | // |
||
619 | // // Check for CustomGenerator/SequenceGenerator/TableGenerator definition |
||
620 | // switch (true) { |
||
621 | // case isset($propertyAnnotations[Annotation\SequenceGenerator::class]): |
||
622 | // $seqGeneratorAnnot = $propertyAnnotations[Annotation\SequenceGenerator::class]; |
||
623 | // |
||
624 | // $idGeneratorDefinition = [ |
||
625 | // 'sequenceName' => $seqGeneratorAnnot->sequenceName, |
||
626 | // 'allocationSize' => $seqGeneratorAnnot->allocationSize, |
||
627 | // ]; |
||
628 | // |
||
629 | // break; |
||
630 | // |
||
631 | // case isset($propertyAnnotations[Annotation\CustomIdGenerator::class]): |
||
632 | // $customGeneratorAnnot = $propertyAnnotations[Annotation\CustomIdGenerator::class]; |
||
633 | // |
||
634 | // $idGeneratorDefinition = [ |
||
635 | // 'class' => $customGeneratorAnnot->class, |
||
636 | // 'arguments' => $customGeneratorAnnot->arguments, |
||
637 | // ]; |
||
638 | // |
||
639 | // if (! isset($idGeneratorDefinition['class'])) { |
||
640 | // throw new Mapping\MappingException( |
||
641 | // sprintf('Cannot instantiate custom generator, no class has been defined') |
||
642 | // ); |
||
643 | // } |
||
644 | // |
||
645 | // if (! class_exists($idGeneratorDefinition['class'])) { |
||
646 | // throw new Mapping\MappingException( |
||
647 | // sprintf('Cannot instantiate custom generator : %s', var_export($idGeneratorDefinition, true)) |
||
648 | // ); |
||
649 | // } |
||
650 | // |
||
651 | // break; |
||
652 | // |
||
653 | // /** @todo If it is not supported, why does this exist? */ |
||
654 | // case isset($propertyAnnotations['Doctrine\ORM\Mapping\TableGenerator']): |
||
655 | // throw Mapping\MappingException::tableIdGeneratorNotImplemented($metadata->getClassName()); |
||
656 | // } |
||
657 | // |
||
658 | // $fieldMetadata->setValueGenerator( |
||
659 | // new Mapping\ValueGeneratorMetadata($idGeneratorType, $idGeneratorDefinition) |
||
660 | // ); |
||
661 | // } |
||
662 | // } |
||
663 | |||
664 | 367 | return $fieldMetadata; |
|
665 | } |
||
666 | |||
667 | /** |
||
668 | * @param Annotation\Annotation[] $propertyAnnotations |
||
669 | */ |
||
670 | 111 | private function convertReflectionPropertyToOneToOneAssociationMetadata( |
|
671 | ReflectionProperty $reflectionProperty, |
||
672 | array $propertyAnnotations, |
||
673 | Mapping\ClassMetadata $metadata, |
||
674 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
675 | ) : Mapping\OneToOneAssociationMetadata { |
||
676 | 111 | $className = $metadata->getClassName(); |
|
677 | 111 | $fieldName = $reflectionProperty->getName(); |
|
678 | 111 | $oneToOneAnnot = $propertyAnnotations[Annotation\OneToOne::class]; |
|
679 | 111 | $assocMetadata = new Mapping\OneToOneAssociationMetadata($fieldName); |
|
680 | 111 | $targetEntity = $oneToOneAnnot->targetEntity; |
|
681 | |||
682 | 111 | $assocMetadata->setTargetEntity($targetEntity); |
|
683 | 111 | $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToOneAnnot->cascade)); |
|
684 | 111 | $assocMetadata->setOrphanRemoval($oneToOneAnnot->orphanRemoval); |
|
685 | 111 | $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToOneAnnot->fetch)); |
|
686 | |||
687 | 111 | if (! empty($oneToOneAnnot->mappedBy)) { |
|
688 | 40 | $assocMetadata->setMappedBy($oneToOneAnnot->mappedBy); |
|
689 | 40 | $assocMetadata->setOwningSide(false); |
|
690 | } |
||
691 | |||
692 | 111 | if (! empty($oneToOneAnnot->inversedBy)) { |
|
693 | 51 | $assocMetadata->setInversedBy($oneToOneAnnot->inversedBy); |
|
694 | 51 | $assocMetadata->setOwningSide(true); |
|
695 | } |
||
696 | |||
697 | // Check for Id |
||
698 | 111 | if (isset($propertyAnnotations[Annotation\Id::class])) { |
|
699 | 12 | $assocMetadata->setPrimaryKey(true); |
|
700 | } |
||
701 | |||
702 | // Check for Cache |
||
703 | 111 | if (isset($propertyAnnotations[Annotation\Cache::class])) { |
|
704 | 4 | $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext); |
|
705 | |||
706 | $cacheBuilder |
||
707 | 4 | ->withComponentMetadata($metadata) |
|
708 | 4 | ->withFieldName($fieldName) |
|
709 | 4 | ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class]); |
|
710 | |||
711 | 4 | $assocMetadata->setCache($cacheBuilder->build()); |
|
712 | } |
||
713 | |||
714 | // Check for owning side to consider join column |
||
715 | 111 | if (! $assocMetadata->isOwningSide()) { |
|
716 | 40 | return $assocMetadata; |
|
717 | } |
||
718 | |||
719 | // Check for JoinColumn/JoinColumns annotations |
||
720 | 105 | $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext); |
|
721 | |||
722 | $joinColumnBuilder |
||
723 | 105 | ->withComponentMetadata($metadata) |
|
724 | 105 | ->withFieldName($fieldName); |
|
725 | |||
726 | switch (true) { |
||
727 | 105 | case isset($propertyAnnotations[Annotation\JoinColumn::class]): |
|
728 | 79 | $joinColumnBuilder->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class]); |
|
729 | |||
730 | 79 | $assocMetadata->addJoinColumn($joinColumnBuilder->build()); |
|
731 | 79 | break; |
|
732 | |||
733 | 29 | case isset($propertyAnnotations[Annotation\JoinColumns::class]): |
|
734 | 3 | $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class]; |
|
735 | |||
736 | 3 | foreach ($joinColumnsAnnot->value as $joinColumnAnnot) { |
|
737 | 3 | $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnot); |
|
738 | |||
739 | 3 | $assocMetadata->addJoinColumn($joinColumnBuilder->build()); |
|
740 | } |
||
741 | |||
742 | 3 | break; |
|
743 | |||
744 | default: |
||
745 | 26 | $assocMetadata->addJoinColumn($joinColumnBuilder->build()); |
|
746 | 26 | break; |
|
747 | } |
||
748 | |||
749 | 105 | return $assocMetadata; |
|
750 | } |
||
751 | |||
752 | /** |
||
753 | * @param Annotation\Annotation[] $propertyAnnotations |
||
754 | */ |
||
755 | 141 | private function convertReflectionPropertyToManyToOneAssociationMetadata( |
|
756 | ReflectionProperty $reflectionProperty, |
||
757 | array $propertyAnnotations, |
||
758 | Mapping\ClassMetadata $metadata, |
||
759 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
760 | ) : Mapping\ManyToOneAssociationMetadata { |
||
761 | // ManyToOne must be owning side by design |
||
762 | 141 | $className = $metadata->getClassName(); |
|
763 | 141 | $fieldName = $reflectionProperty->getName(); |
|
764 | 141 | $manyToOneAnnot = $propertyAnnotations[Annotation\ManyToOne::class]; |
|
765 | 141 | $assocMetadata = new Mapping\ManyToOneAssociationMetadata($fieldName); |
|
766 | 141 | $targetEntity = $manyToOneAnnot->targetEntity; |
|
767 | |||
768 | 141 | $assocMetadata->setTargetEntity($targetEntity); |
|
769 | 141 | $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToOneAnnot->cascade)); |
|
770 | 141 | $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToOneAnnot->fetch)); |
|
771 | |||
772 | 141 | if (! empty($manyToOneAnnot->inversedBy)) { |
|
773 | 94 | $assocMetadata->setInversedBy($manyToOneAnnot->inversedBy); |
|
774 | } |
||
775 | |||
776 | // Check for Id |
||
777 | 141 | if (isset($propertyAnnotations[Annotation\Id::class])) { |
|
778 | 34 | $assocMetadata->setPrimaryKey(true); |
|
779 | } |
||
780 | |||
781 | // Check for Cache |
||
782 | 141 | if (isset($propertyAnnotations[Annotation\Cache::class])) { |
|
783 | 12 | $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext); |
|
784 | |||
785 | $cacheBuilder |
||
786 | 12 | ->withComponentMetadata($metadata) |
|
787 | 12 | ->withFieldName($fieldName) |
|
788 | 12 | ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class]); |
|
789 | |||
790 | 12 | $assocMetadata->setCache($cacheBuilder->build()); |
|
791 | } |
||
792 | |||
793 | // Check for JoinColumn/JoinColumns annotations |
||
794 | 141 | $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext); |
|
795 | |||
796 | $joinColumnBuilder |
||
797 | 141 | ->withComponentMetadata($metadata) |
|
798 | 141 | ->withFieldName($fieldName); |
|
799 | |||
800 | switch (true) { |
||
801 | 141 | case isset($propertyAnnotations[Annotation\JoinColumn::class]): |
|
802 | 81 | $joinColumnBuilder->withJoinColumnAnnotation($propertyAnnotations[Annotation\JoinColumn::class]); |
|
803 | |||
804 | 81 | $assocMetadata->addJoinColumn($joinColumnBuilder->build()); |
|
805 | 81 | break; |
|
806 | |||
807 | 69 | case isset($propertyAnnotations[Annotation\JoinColumns::class]): |
|
808 | 16 | $joinColumnsAnnot = $propertyAnnotations[Annotation\JoinColumns::class]; |
|
809 | |||
810 | 16 | foreach ($joinColumnsAnnot->value as $joinColumnAnnot) { |
|
811 | 16 | $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnot); |
|
812 | |||
813 | 16 | $assocMetadata->addJoinColumn($joinColumnBuilder->build()); |
|
814 | } |
||
815 | |||
816 | 16 | break; |
|
817 | |||
818 | default: |
||
819 | 56 | $assocMetadata->addJoinColumn($joinColumnBuilder->build()); |
|
820 | 56 | break; |
|
821 | } |
||
822 | |||
823 | 141 | return $assocMetadata; |
|
824 | } |
||
825 | |||
826 | /** |
||
827 | * @param Annotation\Annotation[] $propertyAnnotations |
||
828 | * |
||
829 | * @throws Mapping\MappingException |
||
830 | */ |
||
831 | 109 | private function convertReflectionPropertyToOneToManyAssociationMetadata( |
|
832 | ReflectionProperty $reflectionProperty, |
||
833 | array $propertyAnnotations, |
||
834 | Mapping\ClassMetadata $metadata, |
||
835 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
836 | ) : Mapping\OneToManyAssociationMetadata { |
||
837 | 109 | $className = $metadata->getClassName(); |
|
838 | 109 | $fieldName = $reflectionProperty->getName(); |
|
839 | 109 | $oneToManyAnnot = $propertyAnnotations[Annotation\OneToMany::class]; |
|
840 | 109 | $assocMetadata = new Mapping\OneToManyAssociationMetadata($fieldName); |
|
841 | 109 | $targetEntity = $oneToManyAnnot->targetEntity; |
|
842 | |||
843 | 109 | $assocMetadata->setTargetEntity($targetEntity); |
|
844 | 109 | $assocMetadata->setCascade($this->getCascade($className, $fieldName, $oneToManyAnnot->cascade)); |
|
845 | 109 | $assocMetadata->setOrphanRemoval($oneToManyAnnot->orphanRemoval); |
|
846 | 109 | $assocMetadata->setFetchMode($this->getFetchMode($className, $oneToManyAnnot->fetch)); |
|
847 | 109 | $assocMetadata->setOwningSide(false); |
|
848 | 109 | $assocMetadata->setMappedBy($oneToManyAnnot->mappedBy); |
|
849 | |||
850 | 109 | if (! empty($oneToManyAnnot->indexBy)) { |
|
851 | 8 | $assocMetadata->setIndexedBy($oneToManyAnnot->indexBy); |
|
852 | } |
||
853 | |||
854 | // Check for OrderBy |
||
855 | 109 | if (isset($propertyAnnotations[Annotation\OrderBy::class])) { |
|
856 | 14 | $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class]; |
|
857 | |||
858 | 14 | $assocMetadata->setOrderBy($orderByAnnot->value); |
|
859 | } |
||
860 | |||
861 | // Check for Id |
||
862 | 109 | if (isset($propertyAnnotations[Annotation\Id::class])) { |
|
863 | throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName); |
||
864 | } |
||
865 | |||
866 | // Check for Cache |
||
867 | 109 | if (isset($propertyAnnotations[Annotation\Cache::class])) { |
|
868 | 9 | $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext); |
|
869 | |||
870 | $cacheBuilder |
||
871 | 9 | ->withComponentMetadata($metadata) |
|
872 | 9 | ->withFieldName($fieldName) |
|
873 | 9 | ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class]); |
|
874 | |||
875 | 9 | $assocMetadata->setCache($cacheBuilder->build()); |
|
876 | } |
||
877 | |||
878 | 109 | return $assocMetadata; |
|
879 | } |
||
880 | |||
881 | /** |
||
882 | * @param Annotation\Annotation[] $propertyAnnotations |
||
883 | * |
||
884 | * @throws Mapping\MappingException |
||
885 | */ |
||
886 | 89 | private function convertReflectionPropertyToManyToManyAssociationMetadata( |
|
887 | ReflectionProperty $reflectionProperty, |
||
888 | array $propertyAnnotations, |
||
889 | Mapping\ClassMetadata $metadata, |
||
890 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
891 | ) : Mapping\ManyToManyAssociationMetadata { |
||
892 | 89 | $className = $metadata->getClassName(); |
|
893 | 89 | $fieldName = $reflectionProperty->getName(); |
|
894 | 89 | $manyToManyAnnot = $propertyAnnotations[Annotation\ManyToMany::class]; |
|
895 | 89 | $assocMetadata = new Mapping\ManyToManyAssociationMetadata($fieldName); |
|
896 | 89 | $targetEntity = $manyToManyAnnot->targetEntity; |
|
897 | |||
898 | 89 | $assocMetadata->setTargetEntity($targetEntity); |
|
899 | 89 | $assocMetadata->setCascade($this->getCascade($className, $fieldName, $manyToManyAnnot->cascade)); |
|
900 | 89 | $assocMetadata->setOrphanRemoval($manyToManyAnnot->orphanRemoval); |
|
901 | 89 | $assocMetadata->setFetchMode($this->getFetchMode($className, $manyToManyAnnot->fetch)); |
|
902 | |||
903 | 89 | if (! empty($manyToManyAnnot->mappedBy)) { |
|
904 | 36 | $assocMetadata->setMappedBy($manyToManyAnnot->mappedBy); |
|
905 | 36 | $assocMetadata->setOwningSide(false); |
|
906 | } |
||
907 | |||
908 | 89 | if (! empty($manyToManyAnnot->inversedBy)) { |
|
909 | 45 | $assocMetadata->setInversedBy($manyToManyAnnot->inversedBy); |
|
910 | } |
||
911 | |||
912 | 89 | if (! empty($manyToManyAnnot->indexBy)) { |
|
913 | 3 | $assocMetadata->setIndexedBy($manyToManyAnnot->indexBy); |
|
914 | } |
||
915 | |||
916 | // Check for OrderBy |
||
917 | 89 | if (isset($propertyAnnotations[Annotation\OrderBy::class])) { |
|
918 | 3 | $orderByAnnot = $propertyAnnotations[Annotation\OrderBy::class]; |
|
919 | |||
920 | 3 | $assocMetadata->setOrderBy($orderByAnnot->value); |
|
921 | } |
||
922 | |||
923 | // Check for Id |
||
924 | 89 | if (isset($propertyAnnotations[Annotation\Id::class])) { |
|
925 | throw Mapping\MappingException::illegalToManyIdentifierAssociation($className, $fieldName); |
||
926 | } |
||
927 | |||
928 | // Check for Cache |
||
929 | 89 | if (isset($propertyAnnotations[Annotation\Cache::class])) { |
|
930 | 2 | $cacheBuilder = new Builder\CacheMetadataBuilder($metadataBuildingContext); |
|
931 | |||
932 | $cacheBuilder |
||
933 | 2 | ->withComponentMetadata($metadata) |
|
934 | 2 | ->withFieldName($fieldName) |
|
935 | 2 | ->withCacheAnnotation($propertyAnnotations[Annotation\Cache::class]); |
|
936 | |||
937 | 2 | $assocMetadata->setCache($cacheBuilder->build()); |
|
938 | } |
||
939 | |||
940 | // Check for owning side to consider join column |
||
941 | 89 | if (! $assocMetadata->isOwningSide()) { |
|
942 | 36 | return $assocMetadata; |
|
943 | } |
||
944 | |||
945 | 79 | $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext); |
|
946 | |||
947 | $joinTableBuilder |
||
948 | 79 | ->withComponentMetadata($metadata) |
|
949 | 79 | ->withTargetEntity($targetEntity) |
|
950 | 79 | ->withFieldName($fieldName); |
|
951 | |||
952 | // Check for JoinTable |
||
953 | 79 | if (isset($propertyAnnotations[Annotation\JoinTable::class])) { |
|
954 | 71 | $joinTableBuilder->withJoinTableAnnotation($propertyAnnotations[Annotation\JoinTable::class]); |
|
955 | } |
||
956 | |||
957 | 79 | $assocMetadata->setJoinTable($joinTableBuilder->build()); |
|
958 | |||
959 | 79 | return $assocMetadata; |
|
960 | } |
||
961 | |||
962 | /** |
||
963 | * @param Annotation\Annotation[] $classAnnotations |
||
964 | */ |
||
965 | 373 | private function attachLifecycleCallbacks( |
|
966 | array $classAnnotations, |
||
967 | ReflectionClass $reflectionClass, |
||
968 | Mapping\ClassMetadata $metadata |
||
969 | ) : void { |
||
970 | // Evaluate @HasLifecycleCallbacks annotation |
||
971 | 373 | if (isset($classAnnotations[Annotation\HasLifecycleCallbacks::class])) { |
|
972 | $eventMap = [ |
||
973 | 14 | Events::prePersist => Annotation\PrePersist::class, |
|
974 | 14 | Events::postPersist => Annotation\PostPersist::class, |
|
975 | 14 | Events::preUpdate => Annotation\PreUpdate::class, |
|
976 | 14 | Events::postUpdate => Annotation\PostUpdate::class, |
|
977 | 14 | Events::preRemove => Annotation\PreRemove::class, |
|
978 | 14 | Events::postRemove => Annotation\PostRemove::class, |
|
979 | 14 | Events::postLoad => Annotation\PostLoad::class, |
|
980 | 14 | Events::preFlush => Annotation\PreFlush::class, |
|
981 | ]; |
||
982 | |||
983 | /** @var ReflectionMethod $reflectionMethod */ |
||
984 | 14 | foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { |
|
985 | 13 | $annotations = $this->getMethodAnnotations($reflectionMethod); |
|
986 | |||
987 | 13 | foreach ($eventMap as $eventName => $annotationClassName) { |
|
988 | 13 | if (isset($annotations[$annotationClassName])) { |
|
989 | 12 | $metadata->addLifecycleCallback($eventName, $reflectionMethod->getName()); |
|
990 | } |
||
991 | } |
||
992 | } |
||
993 | } |
||
994 | 373 | } |
|
995 | |||
996 | /** |
||
997 | * @param Annotation\Annotation[] $classAnnotations |
||
998 | * |
||
999 | * @throws ReflectionException |
||
1000 | * @throws Mapping\MappingException |
||
1001 | */ |
||
1002 | 373 | private function attachEntityListeners( |
|
1003 | array $classAnnotations, |
||
1004 | Mapping\ClassMetadata $metadata |
||
1005 | ) : void { |
||
1006 | // Evaluate @EntityListeners annotation |
||
1007 | 373 | if (isset($classAnnotations[Annotation\EntityListeners::class])) { |
|
1008 | /** @var Annotation\EntityListeners $entityListenersAnnot */ |
||
1009 | 8 | $entityListenersAnnot = $classAnnotations[Annotation\EntityListeners::class]; |
|
1010 | $eventMap = [ |
||
1011 | 8 | Events::prePersist => Annotation\PrePersist::class, |
|
1012 | 8 | Events::postPersist => Annotation\PostPersist::class, |
|
1013 | 8 | Events::preUpdate => Annotation\PreUpdate::class, |
|
1014 | 8 | Events::postUpdate => Annotation\PostUpdate::class, |
|
1015 | 8 | Events::preRemove => Annotation\PreRemove::class, |
|
1016 | 8 | Events::postRemove => Annotation\PostRemove::class, |
|
1017 | 8 | Events::postLoad => Annotation\PostLoad::class, |
|
1018 | 8 | Events::preFlush => Annotation\PreFlush::class, |
|
1019 | ]; |
||
1020 | |||
1021 | 8 | foreach ($entityListenersAnnot->value as $listenerClassName) { |
|
1022 | 8 | if (! class_exists($listenerClassName)) { |
|
1023 | throw Mapping\MappingException::entityListenerClassNotFound( |
||
1024 | $listenerClassName, |
||
1025 | $metadata->getClassName() |
||
1026 | ); |
||
1027 | } |
||
1028 | |||
1029 | 8 | $listenerClass = new ReflectionClass($listenerClassName); |
|
1030 | |||
1031 | /** @var ReflectionMethod $reflectionMethod */ |
||
1032 | 8 | foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { |
|
1033 | 8 | $annotations = $this->getMethodAnnotations($reflectionMethod); |
|
1034 | |||
1035 | 8 | foreach ($eventMap as $eventName => $annotationClassName) { |
|
1036 | 8 | if (isset($annotations[$annotationClassName])) { |
|
1037 | 6 | $metadata->addEntityListener($eventName, $listenerClassName, $reflectionMethod->getName()); |
|
1038 | } |
||
1039 | } |
||
1040 | } |
||
1041 | } |
||
1042 | } |
||
1043 | 373 | } |
|
1044 | |||
1045 | /** |
||
1046 | * @param Annotation\Annotation[] $classAnnotations |
||
1047 | * |
||
1048 | * @throws Mapping\MappingException |
||
1049 | */ |
||
1050 | 370 | private function attachPropertyOverrides( |
|
1051 | array $classAnnotations, |
||
1052 | ReflectionClass $reflectionClass, |
||
1053 | Mapping\ClassMetadata $metadata, |
||
1054 | Mapping\ClassMetadataBuildingContext $metadataBuildingContext |
||
1055 | ) : void { |
||
1056 | // Evaluate AssociationOverrides annotation |
||
1057 | 370 | if (isset($classAnnotations[Annotation\AssociationOverrides::class])) { |
|
1058 | 5 | $associationOverridesAnnot = $classAnnotations[Annotation\AssociationOverrides::class]; |
|
1059 | |||
1060 | 5 | foreach ($associationOverridesAnnot->value as $associationOverride) { |
|
1061 | 5 | $fieldName = $associationOverride->name; |
|
1062 | 5 | $property = $metadata->getProperty($fieldName); |
|
1063 | |||
1064 | 5 | if (! $property) { |
|
1065 | throw Mapping\MappingException::invalidOverrideFieldName($metadata->getClassName(), $fieldName); |
||
1066 | } |
||
1067 | |||
1068 | 5 | $existingClass = get_class($property); |
|
1069 | 5 | $override = new $existingClass($fieldName); |
|
1070 | |||
1071 | 5 | $override->setTargetEntity($property->getTargetEntity()); |
|
1072 | |||
1073 | // Check for JoinColumn/JoinColumns annotations |
||
1074 | 5 | if ($associationOverride->joinColumns) { |
|
1075 | 3 | $joinColumnBuilder = new Builder\JoinColumnMetadataBuilder($metadataBuildingContext); |
|
1076 | |||
1077 | $joinColumnBuilder |
||
1078 | 3 | ->withComponentMetadata($metadata) |
|
1079 | 3 | ->withFieldName($fieldName); |
|
1080 | |||
1081 | 3 | $joinColumns = []; |
|
1082 | |||
1083 | 3 | foreach ($associationOverride->joinColumns as $joinColumnAnnotation) { |
|
1084 | 3 | $joinColumnBuilder->withJoinColumnAnnotation($joinColumnAnnotation); |
|
1085 | |||
1086 | 3 | $override->addJoinColumn($joinColumnBuilder->build()); |
|
1087 | } |
||
1088 | } |
||
1089 | |||
1090 | // Check for JoinTable annotations |
||
1091 | 5 | if ($associationOverride->joinTable) { |
|
1092 | 2 | $joinTableBuilder = new Builder\JoinTableMetadataBuilder($metadataBuildingContext); |
|
1093 | |||
1094 | $joinTableBuilder |
||
1095 | 2 | ->withComponentMetadata($metadata) |
|
1096 | 2 | ->withFieldName($fieldName) |
|
1097 | 2 | ->withTargetEntity($property->getTargetEntity()) |
|
1098 | 2 | ->withJoinTableAnnotation($associationOverride->joinTable); |
|
1099 | |||
1100 | 2 | $override->setJoinTable($joinTableBuilder->build()); |
|
1101 | } |
||
1102 | |||
1103 | // Check for inversedBy |
||
1104 | 5 | if ($associationOverride->inversedBy) { |
|
1105 | 1 | $override->setInversedBy($associationOverride->inversedBy); |
|
1106 | } |
||
1107 | |||
1108 | // Check for fetch |
||
1109 | 5 | if ($associationOverride->fetch) { |
|
1110 | 1 | $override->setFetchMode( |
|
1111 | 1 | constant(Mapping\FetchMode::class . '::' . $associationOverride->fetch) |
|
1112 | ); |
||
1113 | } |
||
1114 | |||
1115 | 5 | $metadata->setPropertyOverride($override); |
|
1116 | } |
||
1117 | } |
||
1118 | |||
1119 | // Evaluate AttributeOverrides annotation |
||
1120 | 370 | if (isset($classAnnotations[Annotation\AttributeOverrides::class])) { |
|
1121 | 3 | $attributeOverridesAnnot = $classAnnotations[Annotation\AttributeOverrides::class]; |
|
1122 | 3 | $fieldBuilder = new Builder\FieldMetadataBuilder($metadataBuildingContext); |
|
1123 | |||
1124 | $fieldBuilder |
||
1125 | 3 | ->withComponentMetadata($metadata) |
|
1126 | 3 | ->withIdAnnotation(null) |
|
1127 | 3 | ->withVersionAnnotation(null); |
|
1128 | |||
1129 | 3 | foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnotation) { |
|
1130 | $fieldBuilder |
||
1131 | 3 | ->withFieldName($attributeOverrideAnnotation->name) |
|
1132 | 3 | ->withColumnAnnotation($attributeOverrideAnnotation->column); |
|
1133 | |||
1134 | 3 | $metadata->setPropertyOverride($fieldBuilder->build()); |
|
1135 | } |
||
1136 | } |
||
1137 | 370 | } |
|
1138 | |||
1139 | /** |
||
1140 | * Attempts to resolve the cascade modes. |
||
1141 | * |
||
1142 | * @param string $className The class name. |
||
1143 | * @param string $fieldName The field name. |
||
1144 | * @param string[] $originalCascades The original unprocessed field cascades. |
||
1145 | * |
||
1146 | * @return string[] The processed field cascades. |
||
1147 | * |
||
1148 | * @throws Mapping\MappingException If a cascade option is not valid. |
||
1149 | */ |
||
1150 | 251 | private function getCascade(string $className, string $fieldName, array $originalCascades) : array |
|
1151 | { |
||
1152 | 251 | $cascadeTypes = ['remove', 'persist', 'refresh']; |
|
1153 | 251 | $cascades = array_map('strtolower', $originalCascades); |
|
1154 | |||
1155 | 251 | if (in_array('all', $cascades, true)) { |
|
1156 | 23 | $cascades = $cascadeTypes; |
|
1157 | } |
||
1158 | |||
1159 | 251 | if (count($cascades) !== count(array_intersect($cascades, $cascadeTypes))) { |
|
1160 | $diffCascades = array_diff($cascades, array_intersect($cascades, $cascadeTypes)); |
||
1161 | |||
1162 | throw Mapping\MappingException::invalidCascadeOption($diffCascades, $className, $fieldName); |
||
1163 | } |
||
1164 | |||
1165 | 251 | return $cascades; |
|
1166 | } |
||
1167 | |||
1168 | /** |
||
1169 | * Attempts to resolve the fetch mode. |
||
1170 | * |
||
1171 | * @param string $className The class name. |
||
1172 | * @param string $fetchMode The fetch mode. |
||
1173 | * |
||
1174 | * @return string The fetch mode as defined in ClassMetadata. |
||
1175 | * |
||
1176 | * @throws Mapping\MappingException If the fetch mode is not valid. |
||
1177 | */ |
||
1178 | 251 | private function getFetchMode($className, $fetchMode) : string |
|
1179 | { |
||
1180 | 251 | $fetchModeConstant = sprintf('%s::%s', Mapping\FetchMode::class, $fetchMode); |
|
1181 | |||
1182 | 251 | if (! defined($fetchModeConstant)) { |
|
1183 | throw Mapping\MappingException::invalidFetchMode($className, $fetchMode); |
||
1184 | } |
||
1185 | |||
1186 | 251 | return constant($fetchModeConstant); |
|
1187 | } |
||
1188 | |||
1189 | /** |
||
1190 | * @return Annotation\Annotation[] |
||
1191 | */ |
||
1192 | 379 | private function getClassAnnotations(ReflectionClass $reflectionClass) : array |
|
1193 | { |
||
1194 | 379 | $classAnnotations = $this->reader->getClassAnnotations($reflectionClass); |
|
1195 | |||
1196 | 379 | foreach ($classAnnotations as $key => $annot) { |
|
1197 | 373 | if (! is_numeric($key)) { |
|
1198 | continue; |
||
1199 | } |
||
1200 | |||
1201 | 373 | $classAnnotations[get_class($annot)] = $annot; |
|
1202 | } |
||
1203 | |||
1204 | 379 | return $classAnnotations; |
|
1205 | } |
||
1206 | |||
1207 | /** |
||
1208 | * @return Annotation\Annotation[] |
||
1209 | */ |
||
1210 | 373 | private function getPropertyAnnotations(ReflectionProperty $reflectionProperty) : array |
|
1211 | { |
||
1212 | 373 | $propertyAnnotations = $this->reader->getPropertyAnnotations($reflectionProperty); |
|
1213 | |||
1214 | 372 | foreach ($propertyAnnotations as $key => $annot) { |
|
1215 | 372 | if (! is_numeric($key)) { |
|
1216 | continue; |
||
1217 | } |
||
1218 | |||
1219 | 372 | $propertyAnnotations[get_class($annot)] = $annot; |
|
1220 | } |
||
1221 | |||
1222 | 372 | return $propertyAnnotations; |
|
1223 | } |
||
1224 | |||
1225 | /** |
||
1226 | * @return Annotation\Annotation[] |
||
1227 | */ |
||
1228 | 21 | private function getMethodAnnotations(ReflectionMethod $reflectionMethod) : array |
|
1229 | { |
||
1230 | 21 | $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod); |
|
1231 | |||
1232 | 21 | foreach ($methodAnnotations as $key => $annot) { |
|
1233 | 18 | if (! is_numeric($key)) { |
|
1234 | continue; |
||
1235 | } |
||
1236 | |||
1237 | 18 | $methodAnnotations[get_class($annot)] = $annot; |
|
1238 | } |
||
1239 | |||
1240 | 21 | return $methodAnnotations; |
|
1241 | } |
||
1242 | } |
||
1243 |