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