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