vendor/doctrine/orm/src/Mapping/Driver/AttributeDriver.php line 133

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Mapping\Driver;
  4. use Doctrine\Deprecations\Deprecation;
  5. use Doctrine\ORM\Events;
  6. use Doctrine\ORM\Mapping;
  7. use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
  8. use Doctrine\ORM\Mapping\ClassMetadata;
  9. use Doctrine\ORM\Mapping\MappingAttribute;
  10. use Doctrine\ORM\Mapping\MappingException;
  11. use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
  12. use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
  13. use LogicException;
  14. use ReflectionClass;
  15. use ReflectionMethod;
  16. use function class_exists;
  17. use function constant;
  18. use function defined;
  19. use function get_class;
  20. use const PHP_VERSION_ID;
  21. class AttributeDriver extends CompatibilityAnnotationDriver
  22. {
  23. use ColocatedMappingDriver;
  24. use ReflectionBasedDriver;
  25. private const ENTITY_ATTRIBUTE_CLASSES = [
  26. Mapping\Entity::class => 1,
  27. Mapping\MappedSuperclass::class => 2,
  28. ];
  29. /**
  30. * @deprecated override isTransient() instead of overriding this property
  31. *
  32. * @var array<class-string<MappingAttribute>, int>
  33. */
  34. protected $entityAnnotationClasses = self::ENTITY_ATTRIBUTE_CLASSES;
  35. /**
  36. * The attribute reader.
  37. *
  38. * @internal this property will be private in 3.0
  39. *
  40. * @var AttributeReader
  41. */
  42. protected $reader;
  43. /** @param array<string> $paths */
  44. public function __construct(array $paths, bool $reportFieldsWhereDeclared = false)
  45. {
  46. if (PHP_VERSION_ID < 80000) {
  47. throw new LogicException(
  48. 'The attribute metadata driver cannot be enabled on PHP 7. Please upgrade to PHP 8 or choose a different'
  49. . ' metadata driver.'
  50. );
  51. }
  52. $this->reader = new AttributeReader();
  53. $this->addPaths($paths);
  54. // @phpstan-ignore property.deprecated
  55. if ($this->entityAnnotationClasses !== self::ENTITY_ATTRIBUTE_CLASSES) {
  56. Deprecation::trigger(
  57. 'doctrine/orm',
  58. 'https://github.com/doctrine/orm/pull/10204',
  59. 'Changing the value of %s::$entityAnnotationClasses is deprecated and will have no effect in Doctrine ORM 3.0.',
  60. self::class
  61. );
  62. }
  63. if (! $reportFieldsWhereDeclared) {
  64. Deprecation::trigger(
  65. 'doctrine/orm',
  66. 'https://github.com/doctrine/orm/pull/10455',
  67. 'In ORM 3.0, the AttributeDriver will report fields for the classes where they are declared. This may uncover invalid mapping configurations. To opt into the new mode today, set the "reportFieldsWhereDeclared" constructor parameter to true.',
  68. self::class
  69. );
  70. }
  71. $this->reportFieldsWhereDeclared = $reportFieldsWhereDeclared;
  72. }
  73. /**
  74. * Retrieve the current annotation reader
  75. *
  76. * @deprecated no replacement planned.
  77. *
  78. * @return AttributeReader
  79. */
  80. public function getReader()
  81. {
  82. Deprecation::trigger(
  83. 'doctrine/orm',
  84. 'https://github.com/doctrine/orm/pull/9587',
  85. '%s is deprecated with no replacement',
  86. __METHOD__
  87. );
  88. return $this->reader;
  89. }
  90. /**
  91. * {@inheritDoc}
  92. */
  93. public function isTransient($className)
  94. {
  95. $classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className));
  96. foreach ($classAttributes as $a) {
  97. $attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a;
  98. // @phpstan-ignore property.deprecated
  99. if (isset($this->entityAnnotationClasses[get_class($attr)])) {
  100. return false;
  101. }
  102. }
  103. return true;
  104. }
  105. /**
  106. * {@inheritDoc}
  107. *
  108. * @param class-string<T> $className
  109. * @param ClassMetadata<T> $metadata
  110. *
  111. * @template T of object
  112. */
  113. public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
  114. {
  115. $reflectionClass = $metadata->getReflectionClass()
  116. // this happens when running attribute driver in combination with
  117. // static reflection services. This is not the nicest fix
  118. ?? new ReflectionClass($metadata->name);
  119. $classAttributes = $this->reader->getClassAttributes($reflectionClass);
  120. // Evaluate Entity attribute
  121. if (isset($classAttributes[Mapping\Entity::class])) {
  122. $entityAttribute = $classAttributes[Mapping\Entity::class];
  123. if ($entityAttribute->repositoryClass !== null) {
  124. $metadata->setCustomRepositoryClass($entityAttribute->repositoryClass);
  125. }
  126. if ($entityAttribute->readOnly) {
  127. $metadata->markReadOnly();
  128. }
  129. } elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) {
  130. $mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class];
  131. $metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass);
  132. $metadata->isMappedSuperclass = true;
  133. } elseif (isset($classAttributes[Mapping\Embeddable::class])) {
  134. $metadata->isEmbeddedClass = true;
  135. } else {
  136. throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
  137. }
  138. $primaryTable = [];
  139. if (isset($classAttributes[Mapping\Table::class])) {
  140. $tableAnnot = $classAttributes[Mapping\Table::class];
  141. $primaryTable['name'] = $tableAnnot->name;
  142. $primaryTable['schema'] = $tableAnnot->schema;
  143. if ($tableAnnot->options) {
  144. $primaryTable['options'] = $tableAnnot->options;
  145. }
  146. }
  147. if (isset($classAttributes[Mapping\Index::class])) {
  148. foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) {
  149. $index = [];
  150. if (! empty($indexAnnot->columns)) {
  151. $index['columns'] = $indexAnnot->columns;
  152. }
  153. if (! empty($indexAnnot->fields)) {
  154. $index['fields'] = $indexAnnot->fields;
  155. }
  156. if (
  157. isset($index['columns'], $index['fields'])
  158. || (
  159. ! isset($index['columns'])
  160. && ! isset($index['fields'])
  161. )
  162. ) {
  163. throw MappingException::invalidIndexConfiguration(
  164. $className,
  165. (string) ($indexAnnot->name ?? $idx)
  166. );
  167. }
  168. if (! empty($indexAnnot->flags)) {
  169. $index['flags'] = $indexAnnot->flags;
  170. }
  171. if (! empty($indexAnnot->options)) {
  172. $index['options'] = $indexAnnot->options;
  173. }
  174. if (! empty($indexAnnot->name)) {
  175. $primaryTable['indexes'][$indexAnnot->name] = $index;
  176. } else {
  177. $primaryTable['indexes'][] = $index;
  178. }
  179. }
  180. }
  181. if (isset($classAttributes[Mapping\UniqueConstraint::class])) {
  182. foreach ($classAttributes[Mapping\UniqueConstraint::class] as $idx => $uniqueConstraintAnnot) {
  183. $uniqueConstraint = [];
  184. if (! empty($uniqueConstraintAnnot->columns)) {
  185. $uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
  186. }
  187. if (! empty($uniqueConstraintAnnot->fields)) {
  188. $uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
  189. }
  190. if (
  191. isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
  192. || (
  193. ! isset($uniqueConstraint['columns'])
  194. && ! isset($uniqueConstraint['fields'])
  195. )
  196. ) {
  197. throw MappingException::invalidUniqueConstraintConfiguration(
  198. $className,
  199. (string) ($uniqueConstraintAnnot->name ?? $idx)
  200. );
  201. }
  202. if (! empty($uniqueConstraintAnnot->options)) {
  203. $uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
  204. }
  205. if (! empty($uniqueConstraintAnnot->name)) {
  206. $primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
  207. } else {
  208. $primaryTable['uniqueConstraints'][] = $uniqueConstraint;
  209. }
  210. }
  211. }
  212. $metadata->setPrimaryTable($primaryTable);
  213. // Evaluate #[Cache] attribute
  214. if (isset($classAttributes[Mapping\Cache::class])) {
  215. $cacheAttribute = $classAttributes[Mapping\Cache::class];
  216. $cacheMap = [
  217. 'region' => $cacheAttribute->region,
  218. 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage),
  219. ];
  220. $metadata->enableCache($cacheMap);
  221. }
  222. // Evaluate InheritanceType attribute
  223. if (isset($classAttributes[Mapping\InheritanceType::class])) {
  224. $inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class];
  225. $metadata->setInheritanceType(
  226. constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value)
  227. );
  228. if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
  229. // Evaluate DiscriminatorColumn attribute
  230. if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
  231. $discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
  232. $columnDef = [
  233. 'name' => isset($discrColumnAttribute->name) ? (string) $discrColumnAttribute->name : null,
  234. 'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string',
  235. 'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255,
  236. 'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null,
  237. 'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null,
  238. ];
  239. if ($discrColumnAttribute->options) {
  240. $columnDef['options'] = (array) $discrColumnAttribute->options;
  241. }
  242. $metadata->setDiscriminatorColumn($columnDef);
  243. } else {
  244. $metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
  245. }
  246. // Evaluate DiscriminatorMap attribute
  247. if (isset($classAttributes[Mapping\DiscriminatorMap::class])) {
  248. $discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class];
  249. $metadata->setDiscriminatorMap($discrMapAttribute->value);
  250. }
  251. }
  252. }
  253. // Evaluate DoctrineChangeTrackingPolicy attribute
  254. if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) {
  255. $changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class];
  256. $metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value));
  257. }
  258. foreach ($reflectionClass->getProperties() as $property) {
  259. if ($this->isRepeatedPropertyDeclaration($property, $metadata)) {
  260. continue;
  261. }
  262. $mapping = [];
  263. $mapping['fieldName'] = $property->name;
  264. // Evaluate #[Cache] attribute
  265. $cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class);
  266. if ($cacheAttribute !== null) {
  267. $mapping['cache'] = $metadata->getAssociationCacheDefaults(
  268. $mapping['fieldName'],
  269. [
  270. 'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage),
  271. 'region' => $cacheAttribute->region,
  272. ]
  273. );
  274. }
  275. // Check for JoinColumn/JoinColumns attributes
  276. $joinColumns = [];
  277. $joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class);
  278. foreach ($joinColumnAttributes as $joinColumnAttribute) {
  279. $joinColumns[] = $this->joinColumnToArray($joinColumnAttribute);
  280. }
  281. // Field can only be attributed with one of:
  282. // Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded
  283. $columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class);
  284. $oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class);
  285. $oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class);
  286. $manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class);
  287. $manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class);
  288. $embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class);
  289. if ($columnAttribute !== null) {
  290. $mapping = $this->columnToArray($property->name, $columnAttribute);
  291. if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
  292. $mapping['id'] = true;
  293. }
  294. $generatedValueAttribute = $this->reader->getPropertyAttribute($property, Mapping\GeneratedValue::class);
  295. if ($generatedValueAttribute !== null) {
  296. $metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy));
  297. }
  298. if ($this->reader->getPropertyAttribute($property, Mapping\Version::class)) {
  299. $metadata->setVersionMapping($mapping);
  300. }
  301. $metadata->mapField($mapping);
  302. // Check for SequenceGenerator/TableGenerator definition
  303. $seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class);
  304. $customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class);
  305. if ($seqGeneratorAttribute !== null) {
  306. $metadata->setSequenceGeneratorDefinition(
  307. [
  308. 'sequenceName' => $seqGeneratorAttribute->sequenceName,
  309. 'allocationSize' => $seqGeneratorAttribute->allocationSize,
  310. 'initialValue' => $seqGeneratorAttribute->initialValue,
  311. ]
  312. );
  313. } elseif ($customGeneratorAttribute !== null) {
  314. $metadata->setCustomGeneratorDefinition(
  315. [
  316. 'class' => $customGeneratorAttribute->class,
  317. ]
  318. );
  319. }
  320. } elseif ($oneToOneAttribute !== null) {
  321. if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
  322. $mapping['id'] = true;
  323. }
  324. $mapping['targetEntity'] = $oneToOneAttribute->targetEntity;
  325. $mapping['joinColumns'] = $joinColumns;
  326. $mapping['mappedBy'] = $oneToOneAttribute->mappedBy;
  327. $mapping['inversedBy'] = $oneToOneAttribute->inversedBy;
  328. $mapping['cascade'] = $oneToOneAttribute->cascade;
  329. $mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval;
  330. $mapping['fetch'] = $this->getFetchMode($className, $oneToOneAttribute->fetch);
  331. $metadata->mapOneToOne($mapping);
  332. } elseif ($oneToManyAttribute !== null) {
  333. $mapping['mappedBy'] = $oneToManyAttribute->mappedBy;
  334. $mapping['targetEntity'] = $oneToManyAttribute->targetEntity;
  335. $mapping['cascade'] = $oneToManyAttribute->cascade;
  336. $mapping['indexBy'] = $oneToManyAttribute->indexBy;
  337. $mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval;
  338. $mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch);
  339. $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
  340. if ($orderByAttribute !== null) {
  341. $mapping['orderBy'] = $orderByAttribute->value;
  342. }
  343. $metadata->mapOneToMany($mapping);
  344. } elseif ($manyToOneAttribute !== null) {
  345. $idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class);
  346. if ($idAttribute !== null) {
  347. $mapping['id'] = true;
  348. }
  349. $mapping['joinColumns'] = $joinColumns;
  350. $mapping['cascade'] = $manyToOneAttribute->cascade;
  351. $mapping['inversedBy'] = $manyToOneAttribute->inversedBy;
  352. $mapping['targetEntity'] = $manyToOneAttribute->targetEntity;
  353. $mapping['fetch'] = $this->getFetchMode($className, $manyToOneAttribute->fetch);
  354. $metadata->mapManyToOne($mapping);
  355. } elseif ($manyToManyAttribute !== null) {
  356. $joinTable = [];
  357. $joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class);
  358. if ($joinTableAttribute !== null) {
  359. $joinTable = [
  360. 'name' => $joinTableAttribute->name,
  361. 'schema' => $joinTableAttribute->schema,
  362. ];
  363. if ($joinTableAttribute->options) {
  364. $joinTable['options'] = $joinTableAttribute->options;
  365. }
  366. foreach ($joinTableAttribute->joinColumns as $joinColumn) {
  367. $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
  368. }
  369. foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) {
  370. $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
  371. }
  372. }
  373. foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
  374. $joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
  375. }
  376. foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
  377. $joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
  378. }
  379. $mapping['joinTable'] = $joinTable;
  380. $mapping['targetEntity'] = $manyToManyAttribute->targetEntity;
  381. $mapping['mappedBy'] = $manyToManyAttribute->mappedBy;
  382. $mapping['inversedBy'] = $manyToManyAttribute->inversedBy;
  383. $mapping['cascade'] = $manyToManyAttribute->cascade;
  384. $mapping['indexBy'] = $manyToManyAttribute->indexBy;
  385. $mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval;
  386. $mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch);
  387. $orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
  388. if ($orderByAttribute !== null) {
  389. $mapping['orderBy'] = $orderByAttribute->value;
  390. }
  391. $metadata->mapManyToMany($mapping);
  392. } elseif ($embeddedAttribute !== null) {
  393. $mapping['class'] = $embeddedAttribute->class;
  394. $mapping['columnPrefix'] = $embeddedAttribute->columnPrefix;
  395. $metadata->mapEmbedded($mapping);
  396. }
  397. }
  398. // Evaluate AssociationOverrides attribute
  399. if (isset($classAttributes[Mapping\AssociationOverrides::class])) {
  400. $associationOverride = $classAttributes[Mapping\AssociationOverrides::class];
  401. foreach ($associationOverride->overrides as $associationOverride) {
  402. $override = [];
  403. $fieldName = $associationOverride->name;
  404. // Check for JoinColumn/JoinColumns attributes
  405. if ($associationOverride->joinColumns) {
  406. $joinColumns = [];
  407. foreach ($associationOverride->joinColumns as $joinColumn) {
  408. $joinColumns[] = $this->joinColumnToArray($joinColumn);
  409. }
  410. $override['joinColumns'] = $joinColumns;
  411. }
  412. if ($associationOverride->inverseJoinColumns) {
  413. $joinColumns = [];
  414. foreach ($associationOverride->inverseJoinColumns as $joinColumn) {
  415. $joinColumns[] = $this->joinColumnToArray($joinColumn);
  416. }
  417. $override['inverseJoinColumns'] = $joinColumns;
  418. }
  419. // Check for JoinTable attributes
  420. if ($associationOverride->joinTable) {
  421. $joinTableAnnot = $associationOverride->joinTable;
  422. $joinTable = [
  423. 'name' => $joinTableAnnot->name,
  424. 'schema' => $joinTableAnnot->schema,
  425. 'joinColumns' => $override['joinColumns'] ?? [],
  426. 'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [],
  427. ];
  428. unset($override['joinColumns'], $override['inverseJoinColumns']);
  429. $override['joinTable'] = $joinTable;
  430. }
  431. // Check for inversedBy
  432. if ($associationOverride->inversedBy) {
  433. $override['inversedBy'] = $associationOverride->inversedBy;
  434. }
  435. // Check for `fetch`
  436. if ($associationOverride->fetch) {
  437. $override['fetch'] = constant(ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
  438. }
  439. $metadata->setAssociationOverride($fieldName, $override);
  440. }
  441. }
  442. // Evaluate AttributeOverrides attribute
  443. if (isset($classAttributes[Mapping\AttributeOverrides::class])) {
  444. $attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class];
  445. foreach ($attributeOverridesAnnot->overrides as $attributeOverride) {
  446. $mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column);
  447. $metadata->setAttributeOverride($attributeOverride->name, $mapping);
  448. }
  449. }
  450. // Evaluate EntityListeners attribute
  451. if (isset($classAttributes[Mapping\EntityListeners::class])) {
  452. $entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class];
  453. foreach ($entityListenersAttribute->value as $item) {
  454. $listenerClassName = $metadata->fullyQualifiedClassName($item);
  455. if (! class_exists($listenerClassName)) {
  456. throw MappingException::entityListenerClassNotFound($listenerClassName, $className);
  457. }
  458. $hasMapping = false;
  459. $listenerClass = new ReflectionClass($listenerClassName);
  460. foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
  461. // find method callbacks.
  462. $callbacks = $this->getMethodCallbacks($method);
  463. $hasMapping = $hasMapping ?: ! empty($callbacks);
  464. foreach ($callbacks as $value) {
  465. $metadata->addEntityListener($value[1], $listenerClassName, $value[0]);
  466. }
  467. }
  468. // Evaluate the listener using naming convention.
  469. if (! $hasMapping) {
  470. EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName);
  471. }
  472. }
  473. }
  474. // Evaluate #[HasLifecycleCallbacks] attribute
  475. if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) {
  476. foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
  477. foreach ($this->getMethodCallbacks($method) as $value) {
  478. $metadata->addLifecycleCallback($value[0], $value[1]);
  479. }
  480. }
  481. }
  482. }
  483. /**
  484. * Attempts to resolve the fetch mode.
  485. *
  486. * @param class-string $className The class name.
  487. * @param string $fetchMode The fetch mode.
  488. *
  489. * @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
  490. *
  491. * @throws MappingException If the fetch mode is not valid.
  492. */
  493. private function getFetchMode(string $className, string $fetchMode): int
  494. {
  495. if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) {
  496. throw MappingException::invalidFetchMode($className, $fetchMode);
  497. }
  498. return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
  499. }
  500. /**
  501. * Attempts to resolve the generated mode.
  502. *
  503. * @throws MappingException If the fetch mode is not valid.
  504. */
  505. private function getGeneratedMode(string $generatedMode): int
  506. {
  507. if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
  508. throw MappingException::invalidGeneratedMode($generatedMode);
  509. }
  510. return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
  511. }
  512. /**
  513. * Parses the given method.
  514. *
  515. * @return list<array{string, string}>
  516. * @phpstan-return list<array{string, (Events::*)}>
  517. */
  518. private function getMethodCallbacks(ReflectionMethod $method): array
  519. {
  520. $callbacks = [];
  521. $attributes = $this->reader->getMethodAttributes($method);
  522. foreach ($attributes as $attribute) {
  523. if ($attribute instanceof Mapping\PrePersist) {
  524. $callbacks[] = [$method->name, Events::prePersist];
  525. }
  526. if ($attribute instanceof Mapping\PostPersist) {
  527. $callbacks[] = [$method->name, Events::postPersist];
  528. }
  529. if ($attribute instanceof Mapping\PreUpdate) {
  530. $callbacks[] = [$method->name, Events::preUpdate];
  531. }
  532. if ($attribute instanceof Mapping\PostUpdate) {
  533. $callbacks[] = [$method->name, Events::postUpdate];
  534. }
  535. if ($attribute instanceof Mapping\PreRemove) {
  536. $callbacks[] = [$method->name, Events::preRemove];
  537. }
  538. if ($attribute instanceof Mapping\PostRemove) {
  539. $callbacks[] = [$method->name, Events::postRemove];
  540. }
  541. if ($attribute instanceof Mapping\PostLoad) {
  542. $callbacks[] = [$method->name, Events::postLoad];
  543. }
  544. if ($attribute instanceof Mapping\PreFlush) {
  545. $callbacks[] = [$method->name, Events::preFlush];
  546. }
  547. }
  548. return $callbacks;
  549. }
  550. /**
  551. * Parse the given JoinColumn as array
  552. *
  553. * @param Mapping\JoinColumn|Mapping\InverseJoinColumn $joinColumn
  554. *
  555. * @return mixed[]
  556. * @phpstan-return array{
  557. * name: string|null,
  558. * unique: bool,
  559. * nullable: bool,
  560. * onDelete: mixed,
  561. * columnDefinition: string|null,
  562. * referencedColumnName: string,
  563. * options?: array<string, mixed>
  564. * }
  565. */
  566. private function joinColumnToArray($joinColumn): array
  567. {
  568. $mapping = [
  569. 'name' => $joinColumn->name,
  570. 'unique' => $joinColumn->unique,
  571. 'nullable' => $joinColumn->nullable,
  572. 'onDelete' => $joinColumn->onDelete,
  573. 'columnDefinition' => $joinColumn->columnDefinition,
  574. 'referencedColumnName' => $joinColumn->referencedColumnName,
  575. ];
  576. if ($joinColumn->options) {
  577. $mapping['options'] = $joinColumn->options;
  578. }
  579. return $mapping;
  580. }
  581. /**
  582. * Parse the given Column as array
  583. *
  584. * @return mixed[]
  585. * @phpstan-return array{
  586. * fieldName: string,
  587. * type: mixed,
  588. * scale: int,
  589. * length: int,
  590. * unique: bool,
  591. * nullable: bool,
  592. * precision: int,
  593. * enumType?: class-string,
  594. * options?: mixed[],
  595. * columnName?: string,
  596. * columnDefinition?: string
  597. * }
  598. */
  599. private function columnToArray(string $fieldName, Mapping\Column $column): array
  600. {
  601. $mapping = [
  602. 'fieldName' => $fieldName,
  603. 'type' => $column->type,
  604. 'scale' => $column->scale,
  605. 'length' => $column->length,
  606. 'unique' => $column->unique,
  607. 'nullable' => $column->nullable,
  608. 'precision' => $column->precision,
  609. ];
  610. if ($column->options) {
  611. $mapping['options'] = $column->options;
  612. }
  613. if (isset($column->name)) {
  614. $mapping['columnName'] = $column->name;
  615. }
  616. if (isset($column->columnDefinition)) {
  617. $mapping['columnDefinition'] = $column->columnDefinition;
  618. }
  619. if ($column->updatable === false) {
  620. $mapping['notUpdatable'] = true;
  621. }
  622. if ($column->insertable === false) {
  623. $mapping['notInsertable'] = true;
  624. }
  625. if ($column->generated !== null) {
  626. $mapping['generated'] = $this->getGeneratedMode($column->generated);
  627. }
  628. if ($column->enumType) {
  629. $mapping['enumType'] = $column->enumType;
  630. }
  631. return $mapping;
  632. }
  633. }