vendor/doctrine/orm/src/Mapping/ClassMetadataFactory.php line 248

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM\Mapping;
  4. use Doctrine\Common\EventManager;
  5. use Doctrine\DBAL\Platforms;
  6. use Doctrine\DBAL\Platforms\AbstractPlatform;
  7. use Doctrine\Deprecations\Deprecation;
  8. use Doctrine\ORM\EntityManagerInterface;
  9. use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
  10. use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
  11. use Doctrine\ORM\Events;
  12. use Doctrine\ORM\Exception\ORMException;
  13. use Doctrine\ORM\Id\AssignedGenerator;
  14. use Doctrine\ORM\Id\BigIntegerIdentityGenerator;
  15. use Doctrine\ORM\Id\IdentityGenerator;
  16. use Doctrine\ORM\Id\SequenceGenerator;
  17. use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator;
  18. use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType;
  19. use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
  20. use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
  21. use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
  22. use Doctrine\Persistence\Mapping\Driver\MappingDriver;
  23. use Doctrine\Persistence\Mapping\ReflectionService;
  24. use ReflectionClass;
  25. use ReflectionException;
  26. use function assert;
  27. use function class_exists;
  28. use function count;
  29. use function end;
  30. use function explode;
  31. use function in_array;
  32. use function is_a;
  33. use function is_subclass_of;
  34. use function method_exists;
  35. use function str_contains;
  36. use function strlen;
  37. use function strtolower;
  38. use function substr;
  39. use const PHP_VERSION_ID;
  40. /**
  41. * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
  42. * metadata mapping information of a class which describes how a class should be mapped
  43. * to a relational database.
  44. *
  45. * @extends AbstractClassMetadataFactory<ClassMetadata>
  46. */
  47. class ClassMetadataFactory extends AbstractClassMetadataFactory
  48. {
  49. private EntityManagerInterface|null $em = null;
  50. private AbstractPlatform|null $targetPlatform = null;
  51. private MappingDriver|null $driver = null;
  52. private EventManager|null $evm = null;
  53. /** @var mixed[] */
  54. private array $embeddablesActiveNesting = [];
  55. private const NON_IDENTITY_DEFAULT_STRATEGY = [
  56. Platforms\OraclePlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
  57. ];
  58. public function setEntityManager(EntityManagerInterface $em): void
  59. {
  60. if (! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
  61. parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
  62. }
  63. $this->em = $em;
  64. }
  65. /**
  66. * @param A $maybeOwningSide
  67. *
  68. * @return (A is ManyToManyAssociationMapping ? ManyToManyOwningSideMapping : (
  69. * A is OneToOneAssociationMapping ? OneToOneOwningSideMapping : (
  70. * A is OneToManyAssociationMapping ? ManyToOneAssociationMapping : (
  71. * A is ManyToOneAssociationMapping ? ManyToOneAssociationMapping :
  72. * ManyToManyOwningSideMapping|OneToOneOwningSideMapping|ManyToOneAssociationMapping
  73. * ))))
  74. *
  75. * @template A of AssociationMapping
  76. */
  77. final public function getOwningSide(AssociationMapping $maybeOwningSide): OwningSideMapping
  78. {
  79. if ($maybeOwningSide instanceof OwningSideMapping) {
  80. assert($maybeOwningSide instanceof ManyToManyOwningSideMapping ||
  81. $maybeOwningSide instanceof OneToOneOwningSideMapping ||
  82. $maybeOwningSide instanceof ManyToOneAssociationMapping);
  83. return $maybeOwningSide;
  84. }
  85. assert($maybeOwningSide instanceof InverseSideMapping);
  86. $owningSide = $this->getMetadataFor($maybeOwningSide->targetEntity)
  87. ->associationMappings[$maybeOwningSide->mappedBy];
  88. assert($owningSide instanceof ManyToManyOwningSideMapping ||
  89. $owningSide instanceof OneToOneOwningSideMapping ||
  90. $owningSide instanceof ManyToOneAssociationMapping);
  91. return $owningSide;
  92. }
  93. protected function initialize(): void
  94. {
  95. $this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
  96. $this->evm = $this->em->getEventManager();
  97. $this->initialized = true;
  98. }
  99. protected function onNotFoundMetadata(string $className): ClassMetadata|null
  100. {
  101. if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
  102. return null;
  103. }
  104. $eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em);
  105. $this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
  106. $classMetadata = $eventArgs->getFoundMetadata();
  107. assert($classMetadata instanceof ClassMetadata || $classMetadata === null);
  108. return $classMetadata;
  109. }
  110. /**
  111. * {@inheritDoc}
  112. */
  113. protected function doLoadMetadata(
  114. ClassMetadataInterface $class,
  115. ClassMetadataInterface|null $parent,
  116. bool $rootEntityFound,
  117. array $nonSuperclassParents,
  118. ): void {
  119. if ($parent) {
  120. $class->setInheritanceType($parent->inheritanceType);
  121. $class->setDiscriminatorColumn($parent->discriminatorColumn === null ? null : clone $parent->discriminatorColumn);
  122. $class->setIdGeneratorType($parent->generatorType);
  123. $this->addInheritedFields($class, $parent);
  124. $this->addInheritedRelations($class, $parent);
  125. $this->addInheritedEmbeddedClasses($class, $parent);
  126. $class->setIdentifier($parent->identifier);
  127. $class->setVersioned($parent->isVersioned);
  128. $class->setVersionField($parent->versionField);
  129. $class->setDiscriminatorMap($parent->discriminatorMap);
  130. $class->addSubClasses($parent->subClasses);
  131. $class->setLifecycleCallbacks($parent->lifecycleCallbacks);
  132. $class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
  133. if (! empty($parent->customGeneratorDefinition)) {
  134. $class->setCustomGeneratorDefinition($parent->customGeneratorDefinition);
  135. }
  136. if ($parent->isMappedSuperclass) {
  137. $class->setCustomRepositoryClass($parent->customRepositoryClassName);
  138. }
  139. }
  140. // Invoke driver
  141. try {
  142. $this->driver->loadMetadataForClass($class->getName(), $class);
  143. } catch (ReflectionException $e) {
  144. throw MappingException::reflectionFailure($class->getName(), $e);
  145. }
  146. // If this class has a parent the id generator strategy is inherited.
  147. // However this is only true if the hierarchy of parents contains the root entity,
  148. // if it consists of mapped superclasses these don't necessarily include the id field.
  149. if ($parent && $rootEntityFound) {
  150. $this->inheritIdGeneratorMapping($class, $parent);
  151. } else {
  152. $this->completeIdGeneratorMapping($class);
  153. }
  154. if (! $class->isMappedSuperclass) {
  155. if ($rootEntityFound && $class->isInheritanceTypeNone()) {
  156. throw MappingException::missingInheritanceTypeDeclaration(end($nonSuperclassParents), $class->name);
  157. }
  158. foreach ($class->embeddedClasses as $property => $embeddableClass) {
  159. if (isset($embeddableClass->inherited)) {
  160. continue;
  161. }
  162. if (isset($this->embeddablesActiveNesting[$embeddableClass->class])) {
  163. throw MappingException::infiniteEmbeddableNesting($class->name, $property);
  164. }
  165. $this->embeddablesActiveNesting[$class->name] = true;
  166. $embeddableMetadata = $this->getMetadataFor($embeddableClass->class);
  167. if ($embeddableMetadata->isEmbeddedClass) {
  168. $this->addNestedEmbeddedClasses($embeddableMetadata, $class, $property);
  169. }
  170. $identifier = $embeddableMetadata->getIdentifier();
  171. if (! empty($identifier)) {
  172. $this->inheritIdGeneratorMapping($class, $embeddableMetadata);
  173. }
  174. $class->inlineEmbeddable($property, $embeddableMetadata);
  175. unset($this->embeddablesActiveNesting[$class->name]);
  176. }
  177. }
  178. if ($parent) {
  179. if ($parent->isInheritanceTypeSingleTable()) {
  180. $class->setPrimaryTable($parent->table);
  181. }
  182. $this->addInheritedIndexes($class, $parent);
  183. if ($parent->cache) {
  184. $class->cache = $parent->cache;
  185. }
  186. if ($parent->containsForeignIdentifier) {
  187. $class->containsForeignIdentifier = true;
  188. }
  189. if ($parent->containsEnumIdentifier) {
  190. $class->containsEnumIdentifier = true;
  191. }
  192. if (! empty($parent->entityListeners) && empty($class->entityListeners)) {
  193. $class->entityListeners = $parent->entityListeners;
  194. }
  195. }
  196. $class->setParentClasses($nonSuperclassParents);
  197. if ($class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) {
  198. $this->addDefaultDiscriminatorMap($class);
  199. }
  200. // During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402.
  201. // So, we must not discover the missing subclasses before that.
  202. if ($this->evm->hasListeners(Events::loadClassMetadata)) {
  203. $eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
  204. $this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
  205. }
  206. $this->findAbstractEntityClassesNotListedInDiscriminatorMap($class);
  207. $this->validateRuntimeMetadata($class, $parent);
  208. }
  209. /**
  210. * Validate runtime metadata is correctly defined.
  211. *
  212. * @throws MappingException
  213. */
  214. protected function validateRuntimeMetadata(ClassMetadata $class, ClassMetadataInterface|null $parent): void
  215. {
  216. if (! $class->reflClass) {
  217. // only validate if there is a reflection class instance
  218. return;
  219. }
  220. $class->validateIdentifier();
  221. $class->validateAssociations();
  222. $class->validateLifecycleCallbacks($this->getReflectionService());
  223. // verify inheritance
  224. if (! $class->isMappedSuperclass && ! $class->isInheritanceTypeNone()) {
  225. if (! $parent) {
  226. if (count($class->discriminatorMap) === 0) {
  227. throw MappingException::missingDiscriminatorMap($class->name);
  228. }
  229. if (! $class->discriminatorColumn) {
  230. throw MappingException::missingDiscriminatorColumn($class->name);
  231. }
  232. foreach ($class->subClasses as $subClass) {
  233. if ((new ReflectionClass($subClass))->name !== $subClass) {
  234. throw MappingException::invalidClassInDiscriminatorMap($subClass, $class->name);
  235. }
  236. }
  237. } else {
  238. assert($parent instanceof ClassMetadata); // https://github.com/doctrine/orm/issues/8746
  239. if (
  240. ! $class->reflClass->isAbstract()
  241. && ! in_array($class->name, $class->discriminatorMap, true)
  242. ) {
  243. throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName);
  244. }
  245. }
  246. } elseif ($class->isMappedSuperclass && $class->name === $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) {
  247. // second condition is necessary for mapped superclasses in the middle of an inheritance hierarchy
  248. throw MappingException::noInheritanceOnMappedSuperClass($class->name);
  249. }
  250. }
  251. protected function newClassMetadataInstance(string $className): ClassMetadata
  252. {
  253. return new ClassMetadata(
  254. $className,
  255. $this->em->getConfiguration()->getNamingStrategy(),
  256. $this->em->getConfiguration()->getTypedFieldMapper(),
  257. );
  258. }
  259. /**
  260. * Adds a default discriminator map if no one is given
  261. *
  262. * If an entity is of any inheritance type and does not contain a
  263. * discriminator map, then the map is generated automatically. This process
  264. * is expensive computation wise.
  265. *
  266. * The automatically generated discriminator map contains the lowercase short name of
  267. * each class as key.
  268. *
  269. * @throws MappingException
  270. */
  271. private function addDefaultDiscriminatorMap(ClassMetadata $class): void
  272. {
  273. $allClasses = $this->driver->getAllClassNames();
  274. $fqcn = $class->getName();
  275. $map = [$this->getShortName($class->name) => $fqcn];
  276. $duplicates = [];
  277. foreach ($allClasses as $subClassCandidate) {
  278. if (is_subclass_of($subClassCandidate, $fqcn)) {
  279. $shortName = $this->getShortName($subClassCandidate);
  280. if (isset($map[$shortName])) {
  281. $duplicates[] = $shortName;
  282. }
  283. $map[$shortName] = $subClassCandidate;
  284. }
  285. }
  286. if ($duplicates) {
  287. throw MappingException::duplicateDiscriminatorEntry($class->name, $duplicates, $map);
  288. }
  289. $class->setDiscriminatorMap($map);
  290. }
  291. private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void
  292. {
  293. // Only root classes in inheritance hierarchies need contain a discriminator map,
  294. // so skip for other classes.
  295. if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) {
  296. return;
  297. }
  298. $processedClasses = [$rootEntityClass->name => true];
  299. foreach ($rootEntityClass->subClasses as $knownSubClass) {
  300. $processedClasses[$knownSubClass] = true;
  301. }
  302. foreach ($rootEntityClass->discriminatorMap as $declaredClassName) {
  303. // This fetches non-transient parent classes only
  304. $parentClasses = $this->getParentClasses($declaredClassName);
  305. foreach ($parentClasses as $parentClass) {
  306. if (isset($processedClasses[$parentClass])) {
  307. continue;
  308. }
  309. $processedClasses[$parentClass] = true;
  310. // All non-abstract entity classes must be listed in the discriminator map, and
  311. // this will be validated/enforced at runtime (possibly at a later time, when the
  312. // subclass is loaded, but anyways). Also, subclasses is about entity classes only.
  313. // That means we can ignore non-abstract classes here. The (expensive) driver
  314. // check for mapped superclasses need only be run for abstract candidate classes.
  315. if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) {
  316. continue;
  317. }
  318. // We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter)
  319. $rootEntityClass->addSubClass($parentClass);
  320. }
  321. }
  322. }
  323. /** @param class-string $className */
  324. private function peekIfIsMappedSuperclass(string $className): bool
  325. {
  326. $reflService = $this->getReflectionService();
  327. $class = $this->newClassMetadataInstance($className);
  328. $this->initializeReflection($class, $reflService);
  329. $this->getDriver()->loadMetadataForClass($className, $class);
  330. return $class->isMappedSuperclass;
  331. }
  332. /**
  333. * Gets the lower-case short name of a class.
  334. *
  335. * @param class-string $className
  336. */
  337. private function getShortName(string $className): string
  338. {
  339. if (! str_contains($className, '\\')) {
  340. return strtolower($className);
  341. }
  342. $parts = explode('\\', $className);
  343. return strtolower(end($parts));
  344. }
  345. /**
  346. * Puts the `inherited` and `declared` values into mapping information for fields, associations
  347. * and embedded classes.
  348. */
  349. private function addMappingInheritanceInformation(
  350. AssociationMapping|EmbeddedClassMapping|FieldMapping $mapping,
  351. ClassMetadata $parentClass,
  352. ): void {
  353. if (! isset($mapping->inherited) && ! $parentClass->isMappedSuperclass) {
  354. $mapping->inherited = $parentClass->name;
  355. }
  356. if (! isset($mapping->declared)) {
  357. $mapping->declared = $parentClass->name;
  358. }
  359. }
  360. /**
  361. * Adds inherited fields to the subclass mapping.
  362. */
  363. private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void
  364. {
  365. foreach ($parentClass->fieldMappings as $mapping) {
  366. $subClassMapping = clone $mapping;
  367. $this->addMappingInheritanceInformation($subClassMapping, $parentClass);
  368. $subClass->addInheritedFieldMapping($subClassMapping);
  369. }
  370. foreach ($parentClass->propertyAccessors as $name => $field) {
  371. $subClass->propertyAccessors[$name] = $field;
  372. }
  373. }
  374. /**
  375. * Adds inherited association mappings to the subclass mapping.
  376. *
  377. * @throws MappingException
  378. */
  379. private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void
  380. {
  381. foreach ($parentClass->associationMappings as $field => $mapping) {
  382. $subClassMapping = clone $mapping;
  383. $this->addMappingInheritanceInformation($subClassMapping, $parentClass);
  384. // When the class inheriting the relation ($subClass) is the first entity class since the
  385. // relation has been defined in a mapped superclass (or in a chain
  386. // of mapped superclasses) above, then declare this current entity class as the source of
  387. // the relationship.
  388. // According to the definitions given in https://github.com/doctrine/orm/pull/10396/,
  389. // this is the case <=> ! isset($mapping['inherited']).
  390. if (! isset($subClassMapping->inherited)) {
  391. $subClassMapping->sourceEntity = $subClass->name;
  392. }
  393. $subClass->addInheritedAssociationMapping($subClassMapping);
  394. }
  395. }
  396. private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void
  397. {
  398. foreach ($parentClass->embeddedClasses as $field => $embeddedClass) {
  399. $subClassMapping = clone $embeddedClass;
  400. $this->addMappingInheritanceInformation($subClassMapping, $parentClass);
  401. $subClass->embeddedClasses[$field] = $subClassMapping;
  402. }
  403. }
  404. /**
  405. * Adds nested embedded classes metadata to a parent class.
  406. *
  407. * @param ClassMetadata $subClass Sub embedded class metadata to add nested embedded classes metadata from.
  408. * @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to.
  409. * @param string $prefix Embedded classes' prefix to use for nested embedded classes field names.
  410. */
  411. private function addNestedEmbeddedClasses(
  412. ClassMetadata $subClass,
  413. ClassMetadata $parentClass,
  414. string $prefix,
  415. ): void {
  416. foreach ($subClass->embeddedClasses as $property => $embeddableClass) {
  417. if (isset($embeddableClass->inherited)) {
  418. continue;
  419. }
  420. $embeddableMetadata = $this->getMetadataFor($embeddableClass->class);
  421. $parentClass->mapEmbedded(
  422. [
  423. 'fieldName' => $prefix . '.' . $property,
  424. 'class' => $embeddableMetadata->name,
  425. 'columnPrefix' => $embeddableClass->columnPrefix,
  426. 'declaredField' => $embeddableClass->declaredField
  427. ? $prefix . '.' . $embeddableClass->declaredField
  428. : $prefix,
  429. 'originalField' => $embeddableClass->originalField ?: $property,
  430. ],
  431. );
  432. }
  433. }
  434. /**
  435. * Copy the table indices from the parent class superclass to the child class
  436. */
  437. private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass): void
  438. {
  439. if (! $parentClass->isMappedSuperclass) {
  440. return;
  441. }
  442. foreach (['uniqueConstraints', 'indexes'] as $indexType) {
  443. if (isset($parentClass->table[$indexType])) {
  444. foreach ($parentClass->table[$indexType] as $indexName => $index) {
  445. if (isset($subClass->table[$indexType][$indexName])) {
  446. continue; // Let the inheriting table override indices
  447. }
  448. $subClass->table[$indexType][$indexName] = $index;
  449. }
  450. }
  451. }
  452. }
  453. /**
  454. * Completes the ID generator mapping. If "auto" is specified we choose the generator
  455. * most appropriate for the targeted database platform.
  456. *
  457. * @throws ORMException
  458. */
  459. private function completeIdGeneratorMapping(ClassMetadata $class): void
  460. {
  461. $idGenType = $class->generatorType;
  462. if ($idGenType === ClassMetadata::GENERATOR_TYPE_AUTO) {
  463. $class->setIdGeneratorType($this->determineIdGeneratorStrategy($this->getTargetPlatform()));
  464. }
  465. // Create & assign an appropriate ID generator instance
  466. switch ($class->generatorType) {
  467. case ClassMetadata::GENERATOR_TYPE_IDENTITY:
  468. $sequenceName = null;
  469. $fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null;
  470. $platform = $this->getTargetPlatform();
  471. $generator = $fieldName && $class->fieldMappings[$fieldName]->type === 'bigint'
  472. ? new BigIntegerIdentityGenerator()
  473. : new IdentityGenerator();
  474. $class->setIdGenerator($generator);
  475. break;
  476. case ClassMetadata::GENERATOR_TYPE_SEQUENCE:
  477. // If there is no sequence definition yet, create a default definition
  478. $definition = $class->sequenceGeneratorDefinition;
  479. if (! $definition) {
  480. $fieldName = $class->getSingleIdentifierFieldName();
  481. $sequenceName = $class->getSequenceName($this->getTargetPlatform());
  482. $quoted = isset($class->fieldMappings[$fieldName]->quoted) || isset($class->table['quoted']);
  483. $definition = [
  484. 'sequenceName' => $this->truncateSequenceName($sequenceName),
  485. 'allocationSize' => 1,
  486. 'initialValue' => 1,
  487. ];
  488. if ($quoted) {
  489. $definition['quoted'] = true;
  490. }
  491. $class->setSequenceGeneratorDefinition($definition);
  492. }
  493. $sequenceGenerator = new SequenceGenerator(
  494. $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->getTargetPlatform()),
  495. (int) $definition['allocationSize'],
  496. );
  497. $class->setIdGenerator($sequenceGenerator);
  498. break;
  499. case ClassMetadata::GENERATOR_TYPE_NONE:
  500. $class->setIdGenerator(new AssignedGenerator());
  501. break;
  502. case ClassMetadata::GENERATOR_TYPE_CUSTOM:
  503. $definition = $class->customGeneratorDefinition;
  504. if ($definition === null) {
  505. throw InvalidCustomGenerator::onClassNotConfigured();
  506. }
  507. if (! class_exists($definition['class'])) {
  508. throw InvalidCustomGenerator::onMissingClass($definition);
  509. }
  510. $class->setIdGenerator(new $definition['class']());
  511. break;
  512. default:
  513. throw UnknownGeneratorType::create($class->generatorType);
  514. }
  515. }
  516. /** @phpstan-return ClassMetadata::GENERATOR_TYPE_* */
  517. private function determineIdGeneratorStrategy(AbstractPlatform $platform): int
  518. {
  519. assert($this->em !== null);
  520. foreach ($this->em->getConfiguration()->getIdentityGenerationPreferences() as $platformFamily => $strategy) {
  521. if (is_a($platform, $platformFamily)) {
  522. return $strategy;
  523. }
  524. }
  525. $nonIdentityDefaultStrategy = self::NON_IDENTITY_DEFAULT_STRATEGY;
  526. // DBAL 3
  527. if (method_exists($platform, 'getIdentitySequenceName')) {
  528. $nonIdentityDefaultStrategy[Platforms\PostgreSQLPlatform::class] = ClassMetadata::GENERATOR_TYPE_SEQUENCE;
  529. }
  530. foreach ($nonIdentityDefaultStrategy as $platformFamily => $strategy) {
  531. if (is_a($platform, $platformFamily)) {
  532. if ($platform instanceof Platforms\PostgreSQLPlatform) {
  533. Deprecation::trigger(
  534. 'doctrine/orm',
  535. 'https://github.com/doctrine/orm/issues/8893',
  536. <<<'DEPRECATION'
  537. Relying on non-optimal defaults for ID generation is deprecated, and IDENTITY
  538. results in SERIAL, which is not recommended.
  539. Instead, configure identifier generation strategies explicitly through
  540. configuration.
  541. We currently recommend "SEQUENCE" for "%s", when using DBAL 3,
  542. and "IDENTITY" when using DBAL 4,
  543. so you should probably use the following configuration before upgrading to DBAL 4,
  544. and remove it after deploying that upgrade:
  545. $configuration->setIdentityGenerationPreferences([
  546. "%s" => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
  547. ]);
  548. DEPRECATION,
  549. $platformFamily,
  550. $platformFamily,
  551. );
  552. }
  553. return $strategy;
  554. }
  555. }
  556. return ClassMetadata::GENERATOR_TYPE_IDENTITY;
  557. }
  558. private function truncateSequenceName(string $schemaElementName): string
  559. {
  560. $platform = $this->getTargetPlatform();
  561. if (! $platform instanceof Platforms\OraclePlatform) {
  562. return $schemaElementName;
  563. }
  564. $maxIdentifierLength = $platform->getMaxIdentifierLength();
  565. if (strlen($schemaElementName) > $maxIdentifierLength) {
  566. return substr($schemaElementName, 0, $maxIdentifierLength);
  567. }
  568. return $schemaElementName;
  569. }
  570. /**
  571. * Inherits the ID generator mapping from a parent class.
  572. */
  573. private function inheritIdGeneratorMapping(ClassMetadata $class, ClassMetadata $parent): void
  574. {
  575. if ($parent->isIdGeneratorSequence()) {
  576. $class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
  577. }
  578. if ($parent->generatorType) {
  579. $class->setIdGeneratorType($parent->generatorType);
  580. }
  581. if ($parent->idGenerator ?? null) {
  582. $class->setIdGenerator($parent->idGenerator);
  583. }
  584. }
  585. protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService): void
  586. {
  587. $class->wakeupReflection($reflService);
  588. if (PHP_VERSION_ID < 80400) {
  589. return;
  590. }
  591. foreach ($class->propertyAccessors as $propertyAccessor) {
  592. $property = $propertyAccessor->getUnderlyingReflector();
  593. if ($property->isVirtual()) {
  594. throw MappingException::mappingVirtualPropertyNotAllowed($class->name, $property->getName());
  595. }
  596. }
  597. }
  598. protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService): void
  599. {
  600. $class->initializeReflection($reflService);
  601. }
  602. protected function getDriver(): MappingDriver
  603. {
  604. assert($this->driver !== null);
  605. return $this->driver;
  606. }
  607. protected function isEntity(ClassMetadataInterface $class): bool
  608. {
  609. return ! $class->isMappedSuperclass;
  610. }
  611. private function getTargetPlatform(): Platforms\AbstractPlatform
  612. {
  613. if (! $this->targetPlatform) {
  614. $this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
  615. }
  616. return $this->targetPlatform;
  617. }
  618. }