summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php')
-rw-r--r--vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php768
1 files changed, 768 insertions, 0 deletions
diff --git a/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php b/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php
new file mode 100644
index 0000000..6fed1a2
--- /dev/null
+++ b/vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php
@@ -0,0 +1,768 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Mapping\Driver;
6
7use Doctrine\ORM\Events;
8use Doctrine\ORM\Mapping;
9use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
10use Doctrine\ORM\Mapping\ClassMetadata;
11use Doctrine\ORM\Mapping\MappingException;
12use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
13use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
14use Doctrine\Persistence\Mapping\Driver\MappingDriver;
15use InvalidArgumentException;
16use ReflectionClass;
17use ReflectionMethod;
18use ReflectionProperty;
19
20use function assert;
21use function class_exists;
22use function constant;
23use function defined;
24use function sprintf;
25
26class AttributeDriver implements MappingDriver
27{
28 use ColocatedMappingDriver;
29 use ReflectionBasedDriver;
30
31 private const ENTITY_ATTRIBUTE_CLASSES = [
32 Mapping\Entity::class => 1,
33 Mapping\MappedSuperclass::class => 2,
34 ];
35
36 private readonly AttributeReader $reader;
37
38 /**
39 * @param array<string> $paths
40 * @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
41 */
42 public function __construct(array $paths, bool $reportFieldsWhereDeclared = true)
43 {
44 if (! $reportFieldsWhereDeclared) {
45 throw new InvalidArgumentException(sprintf(
46 'The $reportFieldsWhereDeclared argument is no longer supported, make sure to omit it when calling %s.',
47 __METHOD__,
48 ));
49 }
50
51 $this->reader = new AttributeReader();
52 $this->addPaths($paths);
53 }
54
55 public function isTransient(string $className): bool
56 {
57 $classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className));
58
59 foreach ($classAttributes as $a) {
60 $attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a;
61 if (isset(self::ENTITY_ATTRIBUTE_CLASSES[$attr::class])) {
62 return false;
63 }
64 }
65
66 return true;
67 }
68
69 /**
70 * {@inheritDoc}
71 *
72 * @psalm-param class-string<T> $className
73 * @psalm-param ClassMetadata<T> $metadata
74 *
75 * @template T of object
76 */
77 public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void
78 {
79 $reflectionClass = $metadata->getReflectionClass()
80 // this happens when running attribute driver in combination with
81 // static reflection services. This is not the nicest fix
82 ?? new ReflectionClass($metadata->name);
83
84 $classAttributes = $this->reader->getClassAttributes($reflectionClass);
85
86 // Evaluate Entity attribute
87 if (isset($classAttributes[Mapping\Entity::class])) {
88 $entityAttribute = $classAttributes[Mapping\Entity::class];
89 if ($entityAttribute->repositoryClass !== null) {
90 $metadata->setCustomRepositoryClass($entityAttribute->repositoryClass);
91 }
92
93 if ($entityAttribute->readOnly) {
94 $metadata->markReadOnly();
95 }
96 } elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) {
97 $mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class];
98
99 $metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass);
100 $metadata->isMappedSuperclass = true;
101 } elseif (isset($classAttributes[Mapping\Embeddable::class])) {
102 $metadata->isEmbeddedClass = true;
103 } else {
104 throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
105 }
106
107 $primaryTable = [];
108
109 if (isset($classAttributes[Mapping\Table::class])) {
110 $tableAnnot = $classAttributes[Mapping\Table::class];
111 $primaryTable['name'] = $tableAnnot->name;
112 $primaryTable['schema'] = $tableAnnot->schema;
113
114 if ($tableAnnot->options) {
115 $primaryTable['options'] = $tableAnnot->options;
116 }
117 }
118
119 if (isset($classAttributes[Mapping\Index::class])) {
120 if ($metadata->isEmbeddedClass) {
121 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\Index::class);
122 }
123
124 foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) {
125 $index = [];
126
127 if (! empty($indexAnnot->columns)) {
128 $index['columns'] = $indexAnnot->columns;
129 }
130
131 if (! empty($indexAnnot->fields)) {
132 $index['fields'] = $indexAnnot->fields;
133 }
134
135 if (
136 isset($index['columns'], $index['fields'])
137 || (
138 ! isset($index['columns'])
139 && ! isset($index['fields'])
140 )
141 ) {
142 throw MappingException::invalidIndexConfiguration(
143 $className,
144 (string) ($indexAnnot->name ?? $idx),
145 );
146 }
147
148 if (! empty($indexAnnot->flags)) {
149 $index['flags'] = $indexAnnot->flags;
150 }
151
152 if (! empty($indexAnnot->options)) {
153 $index['options'] = $indexAnnot->options;
154 }
155
156 if (! empty($indexAnnot->name)) {
157 $primaryTable['indexes'][$indexAnnot->name] = $index;
158 } else {
159 $primaryTable['indexes'][] = $index;
160 }
161 }
162 }
163
164 if (isset($classAttributes[Mapping\UniqueConstraint::class])) {
165 if ($metadata->isEmbeddedClass) {
166 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\UniqueConstraint::class);
167 }
168
169 foreach ($classAttributes[Mapping\UniqueConstraint::class] as $idx => $uniqueConstraintAnnot) {
170 $uniqueConstraint = [];
171
172 if (! empty($uniqueConstraintAnnot->columns)) {
173 $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
174 }
175
176 if (! empty($uniqueConstraintAnnot->fields)) {
177 $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
178 }
179
180 if (
181 isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
182 || (
183 ! isset($uniqueConstraint['columns'])
184 && ! isset($uniqueConstraint['fields'])
185 )
186 ) {
187 throw MappingException::invalidUniqueConstraintConfiguration(
188 $className,
189 (string) ($uniqueConstraintAnnot->name ?? $idx),
190 );
191 }
192
193 if (! empty($uniqueConstraintAnnot->options)) {
194 $uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
195 }
196
197 if (! empty($uniqueConstraintAnnot->name)) {
198 $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
199 } else {
200 $primaryTable['uniqueConstraints'][] = $uniqueConstraint;
201 }
202 }
203 }
204
205 $metadata->setPrimaryTable($primaryTable);
206
207 // Evaluate #[Cache] attribute
208 if (isset($classAttributes[Mapping\Cache::class])) {
209 if ($metadata->isEmbeddedClass) {
210 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\Cache::class);
211 }
212
213 $cacheAttribute = $classAttributes[Mapping\Cache::class];
214 $cacheMap = [
215 'region' => $cacheAttribute->region,
216 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage),
217 ];
218
219 $metadata->enableCache($cacheMap);
220 }
221
222 // Evaluate InheritanceType attribute
223 if (isset($classAttributes[Mapping\InheritanceType::class])) {
224 if ($metadata->isEmbeddedClass) {
225 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\InheritanceType::class);
226 }
227
228 $inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class];
229
230 $metadata->setInheritanceType(
231 constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value),
232 );
233
234 if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
235 // Evaluate DiscriminatorColumn attribute
236 if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
237 $discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
238 assert($discrColumnAttribute instanceof Mapping\DiscriminatorColumn);
239
240 $columnDef = [
241 'name' => $discrColumnAttribute->name,
242 'type' => $discrColumnAttribute->type ?? 'string',
243 'length' => $discrColumnAttribute->length ?? 255,
244 'columnDefinition' => $discrColumnAttribute->columnDefinition,
245 'enumType' => $discrColumnAttribute->enumType,
246 ];
247
248 if ($discrColumnAttribute->options) {
249 $columnDef['options'] = $discrColumnAttribute->options;
250 }
251
252 $metadata->setDiscriminatorColumn($columnDef);
253 } else {
254 $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
255 }
256
257 // Evaluate DiscriminatorMap attribute
258 if (isset($classAttributes[Mapping\DiscriminatorMap::class])) {
259 $discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class];
260 $metadata->setDiscriminatorMap($discrMapAttribute->value);
261 }
262 }
263 }
264
265 // Evaluate DoctrineChangeTrackingPolicy attribute
266 if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) {
267 if ($metadata->isEmbeddedClass) {
268 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ChangeTrackingPolicy::class);
269 }
270
271 $changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class];
272 $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value));
273 }
274
275 foreach ($reflectionClass->getProperties() as $property) {
276 assert($property instanceof ReflectionProperty);
277
278 if ($this->isRepeatedPropertyDeclaration($property, $metadata)) {
279 continue;
280 }
281
282 $mapping = [];
283 $mapping['fieldName'] = $property->name;
284
285 // Evaluate #[Cache] attribute
286 $cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class);
287 if ($cacheAttribute !== null) {
288 assert($cacheAttribute instanceof Mapping\Cache);
289
290 $mapping['cache'] = $metadata->getAssociationCacheDefaults(
291 $mapping['fieldName'],
292 [
293 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage),
294 'region' => $cacheAttribute->region,
295 ],
296 );
297 }
298
299 // Check for JoinColumn/JoinColumns attributes
300 $joinColumns = [];
301
302 $joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class);
303
304 foreach ($joinColumnAttributes as $joinColumnAttribute) {
305 $joinColumns[] = $this->joinColumnToArray($joinColumnAttribute);
306 }
307
308 // Field can only be attributed with one of:
309 // Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded
310 $columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class);
311 $oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class);
312 $oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class);
313 $manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class);
314 $manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class);
315 $embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class);
316
317 if ($columnAttribute !== null) {
318 $mapping = $this->columnToArray($property->name, $columnAttribute);
319
320 if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
321 $mapping['id'] = true;
322 }
323
324 $generatedValueAttribute = $this->reader->getPropertyAttribute($property, Mapping\GeneratedValue::class);
325
326 if ($generatedValueAttribute !== null) {
327 $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy));
328 }
329
330 if ($this->reader->getPropertyAttribute($property, Mapping\Version::class)) {
331 $metadata->setVersionMapping($mapping);
332 }
333
334 $metadata->mapField($mapping);
335
336 // Check for SequenceGenerator/TableGenerator definition
337 $seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class);
338 $customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class);
339
340 if ($seqGeneratorAttribute !== null) {
341 $metadata->setSequenceGeneratorDefinition(
342 [
343 'sequenceName' => $seqGeneratorAttribute->sequenceName,
344 'allocationSize' => $seqGeneratorAttribute->allocationSize,
345 'initialValue' => $seqGeneratorAttribute->initialValue,
346 ],
347 );
348 } elseif ($customGeneratorAttribute !== null) {
349 $metadata->setCustomGeneratorDefinition(
350 [
351 'class' => $customGeneratorAttribute->class,
352 ],
353 );
354 }
355 } elseif ($oneToOneAttribute !== null) {
356 if ($metadata->isEmbeddedClass) {
357 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToOne::class);
358 }
359
360 if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
361 $mapping['id'] = true;
362 }
363
364 $mapping['targetEntity'] = $oneToOneAttribute->targetEntity;
365 $mapping['joinColumns'] = $joinColumns;
366 $mapping['mappedBy'] = $oneToOneAttribute->mappedBy;
367 $mapping['inversedBy'] = $oneToOneAttribute->inversedBy;
368 $mapping['cascade'] = $oneToOneAttribute->cascade;
369 $mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval;
370 $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAttribute->fetch);
371 $metadata->mapOneToOne($mapping);
372 } elseif ($oneToManyAttribute !== null) {
373 if ($metadata->isEmbeddedClass) {
374 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class);
375 }
376
377 $mapping['mappedBy'] = $oneToManyAttribute->mappedBy;
378 $mapping['targetEntity'] = $oneToManyAttribute->targetEntity;
379 $mapping['cascade'] = $oneToManyAttribute->cascade;
380 $mapping['indexBy'] = $oneToManyAttribute->indexBy;
381 $mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval;
382 $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch);
383
384 $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
385
386 if ($orderByAttribute !== null) {
387 $mapping['orderBy'] = $orderByAttribute->value;
388 }
389
390 $metadata->mapOneToMany($mapping);
391 } elseif ($manyToOneAttribute !== null) {
392 if ($metadata->isEmbeddedClass) {
393 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\OneToMany::class);
394 }
395
396 $idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class);
397
398 if ($idAttribute !== null) {
399 $mapping['id'] = true;
400 }
401
402 $mapping['joinColumns'] = $joinColumns;
403 $mapping['cascade'] = $manyToOneAttribute->cascade;
404 $mapping['inversedBy'] = $manyToOneAttribute->inversedBy;
405 $mapping['targetEntity'] = $manyToOneAttribute->targetEntity;
406 $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAttribute->fetch);
407 $metadata->mapManyToOne($mapping);
408 } elseif ($manyToManyAttribute !== null) {
409 if ($metadata->isEmbeddedClass) {
410 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\ManyToMany::class);
411 }
412
413 $joinTable = [];
414 $joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class);
415
416 if ($joinTableAttribute !== null) {
417 $joinTable = [
418 'name' => $joinTableAttribute->name,
419 'schema' => $joinTableAttribute->schema,
420 ];
421
422 if ($joinTableAttribute->options) {
423 $joinTable['options'] = $joinTableAttribute->options;
424 }
425
426 foreach ($joinTableAttribute->joinColumns as $joinColumn) {
427 $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
428 }
429
430 foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) {
431 $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
432 }
433 }
434
435 foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
436 $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
437 }
438
439 foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
440 $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
441 }
442
443 $mapping['joinTable'] = $joinTable;
444 $mapping['targetEntity'] = $manyToManyAttribute->targetEntity;
445 $mapping['mappedBy'] = $manyToManyAttribute->mappedBy;
446 $mapping['inversedBy'] = $manyToManyAttribute->inversedBy;
447 $mapping['cascade'] = $manyToManyAttribute->cascade;
448 $mapping['indexBy'] = $manyToManyAttribute->indexBy;
449 $mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval;
450 $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch);
451
452 $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
453
454 if ($orderByAttribute !== null) {
455 $mapping['orderBy'] = $orderByAttribute->value;
456 }
457
458 $metadata->mapManyToMany($mapping);
459 } elseif ($embeddedAttribute !== null) {
460 $mapping['class'] = $embeddedAttribute->class;
461 $mapping['columnPrefix'] = $embeddedAttribute->columnPrefix;
462
463 $metadata->mapEmbedded($mapping);
464 }
465 }
466
467 // Evaluate AssociationOverrides attribute
468 if (isset($classAttributes[Mapping\AssociationOverrides::class])) {
469 if ($metadata->isEmbeddedClass) {
470 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\AssociationOverride::class);
471 }
472
473 $associationOverride = $classAttributes[Mapping\AssociationOverrides::class];
474
475 foreach ($associationOverride->overrides as $associationOverride) {
476 $override = [];
477 $fieldName = $associationOverride->name;
478
479 // Check for JoinColumn/JoinColumns attributes
480 if ($associationOverride->joinColumns) {
481 $joinColumns = [];
482
483 foreach ($associationOverride->joinColumns as $joinColumn) {
484 $joinColumns[] = $this->joinColumnToArray($joinColumn);
485 }
486
487 $override['joinColumns'] = $joinColumns;
488 }
489
490 if ($associationOverride->inverseJoinColumns) {
491 $joinColumns = [];
492
493 foreach ($associationOverride->inverseJoinColumns as $joinColumn) {
494 $joinColumns[] = $this->joinColumnToArray($joinColumn);
495 }
496
497 $override['inverseJoinColumns'] = $joinColumns;
498 }
499
500 // Check for JoinTable attributes
501 if ($associationOverride->joinTable) {
502 $joinTableAnnot = $associationOverride->joinTable;
503 $joinTable = [
504 'name' => $joinTableAnnot->name,
505 'schema' => $joinTableAnnot->schema,
506 'joinColumns' => $override['joinColumns'] ?? [],
507 'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [],
508 ];
509
510 unset($override['joinColumns'], $override['inverseJoinColumns']);
511
512 $override['joinTable'] = $joinTable;
513 }
514
515 // Check for inversedBy
516 if ($associationOverride->inversedBy) {
517 $override['inversedBy'] = $associationOverride->inversedBy;
518 }
519
520 // Check for `fetch`
521 if ($associationOverride->fetch) {
522 $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
523 }
524
525 $metadata->setAssociationOverride($fieldName, $override);
526 }
527 }
528
529 // Evaluate AttributeOverrides attribute
530 if (isset($classAttributes[Mapping\AttributeOverrides::class])) {
531 if ($metadata->isEmbeddedClass) {
532 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\AttributeOverrides::class);
533 }
534
535 $attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class];
536
537 foreach ($attributeOverridesAnnot->overrides as $attributeOverride) {
538 $mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column);
539
540 $metadata->setAttributeOverride($attributeOverride->name, $mapping);
541 }
542 }
543
544 // Evaluate EntityListeners attribute
545 if (isset($classAttributes[Mapping\EntityListeners::class])) {
546 if ($metadata->isEmbeddedClass) {
547 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\EntityListeners::class);
548 }
549
550 $entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class];
551
552 foreach ($entityListenersAttribute->value as $item) {
553 $listenerClassName = $metadata->fullyQualifiedClassName($item);
554
555 if (! class_exists($listenerClassName)) {
556 throw MappingException::entityListenerClassNotFound($listenerClassName, $className);
557 }
558
559 $hasMapping = false;
560 $listenerClass = new ReflectionClass($listenerClassName);
561
562 foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
563 assert($method instanceof ReflectionMethod);
564 // find method callbacks.
565 $callbacks = $this->getMethodCallbacks($method);
566 $hasMapping = $hasMapping ?: ! empty($callbacks);
567
568 foreach ($callbacks as $value) {
569 $metadata->addEntityListener($value[1], $listenerClassName, $value[0]);
570 }
571 }
572
573 // Evaluate the listener using naming convention.
574 if (! $hasMapping) {
575 EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName);
576 }
577 }
578 }
579
580 // Evaluate #[HasLifecycleCallbacks] attribute
581 if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) {
582 if ($metadata->isEmbeddedClass) {
583 throw MappingException::invalidAttributeOnEmbeddable($metadata->name, Mapping\HasLifecycleCallbacks::class);
584 }
585
586 foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
587 assert($method instanceof ReflectionMethod);
588 foreach ($this->getMethodCallbacks($method) as $value) {
589 $metadata->addLifecycleCallback($value[0], $value[1]);
590 }
591 }
592 }
593 }
594
595 /**
596 * Attempts to resolve the fetch mode.
597 *
598 * @param class-string $className The class name.
599 * @param string $fetchMode The fetch mode.
600 *
601 * @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
602 *
603 * @throws MappingException If the fetch mode is not valid.
604 */
605 private function getFetchMode(string $className, string $fetchMode): int
606 {
607 if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) {
608 throw MappingException::invalidFetchMode($className, $fetchMode);
609 }
610
611 return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
612 }
613
614 /**
615 * Attempts to resolve the generated mode.
616 *
617 * @throws MappingException If the fetch mode is not valid.
618 */
619 private function getGeneratedMode(string $generatedMode): int
620 {
621 if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
622 throw MappingException::invalidGeneratedMode($generatedMode);
623 }
624
625 return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
626 }
627
628 /**
629 * Parses the given method.
630 *
631 * @return list<array{string, string}>
632 * @psalm-return list<array{string, (Events::*)}>
633 */
634 private function getMethodCallbacks(ReflectionMethod $method): array
635 {
636 $callbacks = [];
637 $attributes = $this->reader->getMethodAttributes($method);
638
639 foreach ($attributes as $attribute) {
640 if ($attribute instanceof Mapping\PrePersist) {
641 $callbacks[] = [$method->name, Events::prePersist];
642 }
643
644 if ($attribute instanceof Mapping\PostPersist) {
645 $callbacks[] = [$method->name, Events::postPersist];
646 }
647
648 if ($attribute instanceof Mapping\PreUpdate) {
649 $callbacks[] = [$method->name, Events::preUpdate];
650 }
651
652 if ($attribute instanceof Mapping\PostUpdate) {
653 $callbacks[] = [$method->name, Events::postUpdate];
654 }
655
656 if ($attribute instanceof Mapping\PreRemove) {
657 $callbacks[] = [$method->name, Events::preRemove];
658 }
659
660 if ($attribute instanceof Mapping\PostRemove) {
661 $callbacks[] = [$method->name, Events::postRemove];
662 }
663
664 if ($attribute instanceof Mapping\PostLoad) {
665 $callbacks[] = [$method->name, Events::postLoad];
666 }
667
668 if ($attribute instanceof Mapping\PreFlush) {
669 $callbacks[] = [$method->name, Events::preFlush];
670 }
671 }
672
673 return $callbacks;
674 }
675
676 /**
677 * Parse the given JoinColumn as array
678 *
679 * @return mixed[]
680 * @psalm-return array{
681 * name: string|null,
682 * unique: bool,
683 * nullable: bool,
684 * onDelete: mixed,
685 * columnDefinition: string|null,
686 * referencedColumnName: string,
687 * options?: array<string, mixed>
688 * }
689 */
690 private function joinColumnToArray(Mapping\JoinColumn|Mapping\InverseJoinColumn $joinColumn): array
691 {
692 $mapping = [
693 'name' => $joinColumn->name,
694 'unique' => $joinColumn->unique,
695 'nullable' => $joinColumn->nullable,
696 'onDelete' => $joinColumn->onDelete,
697 'columnDefinition' => $joinColumn->columnDefinition,
698 'referencedColumnName' => $joinColumn->referencedColumnName,
699 ];
700
701 if ($joinColumn->options) {
702 $mapping['options'] = $joinColumn->options;
703 }
704
705 return $mapping;
706 }
707
708 /**
709 * Parse the given Column as array
710 *
711 * @return mixed[]
712 * @psalm-return array{
713 * fieldName: string,
714 * type: mixed,
715 * scale: int,
716 * length: int,
717 * unique: bool,
718 * nullable: bool,
719 * precision: int,
720 * enumType?: class-string,
721 * options?: mixed[],
722 * columnName?: string,
723 * columnDefinition?: string
724 * }
725 */
726 private function columnToArray(string $fieldName, Mapping\Column $column): array
727 {
728 $mapping = [
729 'fieldName' => $fieldName,
730 'type' => $column->type,
731 'scale' => $column->scale,
732 'length' => $column->length,
733 'unique' => $column->unique,
734 'nullable' => $column->nullable,
735 'precision' => $column->precision,
736 ];
737
738 if ($column->options) {
739 $mapping['options'] = $column->options;
740 }
741
742 if (isset($column->name)) {
743 $mapping['columnName'] = $column->name;
744 }
745
746 if (isset($column->columnDefinition)) {
747 $mapping['columnDefinition'] = $column->columnDefinition;
748 }
749
750 if ($column->updatable === false) {
751 $mapping['notUpdatable'] = true;
752 }
753
754 if ($column->insertable === false) {
755 $mapping['notInsertable'] = true;
756 }
757
758 if ($column->generated !== null) {
759 $mapping['generated'] = $this->getGeneratedMode($column->generated);
760 }
761
762 if ($column->enumType) {
763 $mapping['enumType'] = $column->enumType;
764 }
765
766 return $mapping;
767 }
768}