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