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

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