diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 8c675509cf7..581dce6b77c 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -14,6 +14,7 @@ use function array_fill_keys; use function array_keys; +use function array_map; use function count; use function is_array; use function key; @@ -284,13 +285,17 @@ private function getEntityFromIdentityMap(string $className, array $data) $class = $this->_metadataCache[$className]; if ($class->isIdentifierComposite) { - $idHash = ''; - - foreach ($class->identifier as $fieldName) { - $idHash .= ' ' . (isset($class->associationMappings[$fieldName]) - ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] - : $data[$fieldName]); - } + $idHash = UnitOfWork::getIdHashByIdentifier( + array_map( + /** @return mixed */ + static function (string $fieldName) use ($data, $class) { + return isset($class->associationMappings[$fieldName]) + ? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] + : $data[$fieldName]; + }, + $class->identifier + ) + ); return $this->_uow->tryGetByIdHash(ltrim($idHash), $class->rootEntityName); } elseif (isset($class->associationMappings[$class->identifier[0]])) { diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index 0b655254dfd..efcce4b3c4f 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -271,6 +271,10 @@ public function executeInserts() $paramIndex = 1; foreach ($insertData[$tableName] as $column => $value) { + if ($value instanceof BackedEnum) { + $value = $value->value; + } + $stmt->bindValue($paramIndex++, $value, $this->columnTypes[$column]); } } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index a859ccc46b1..568420258ae 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -1566,14 +1566,8 @@ public function isEntityScheduled($entity) public function addToIdentityMap($entity) { $classMetadata = $this->em->getClassMetadata(get_class($entity)); - $identifier = $this->entityIdentifiers[spl_object_id($entity)]; - - if (empty($identifier) || in_array(null, $identifier, true)) { - throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity); - } - - $idHash = implode(' ', $identifier); - $className = $classMetadata->rootEntityName; + $idHash = $this->getIdHashByEntity($entity); + $className = $classMetadata->rootEntityName; if (isset($this->identityMap[$className][$idHash])) { return false; @@ -1584,6 +1578,50 @@ public function addToIdentityMap($entity) return true; } + /** + * Gets the id hash of an entity by its identifier. + * + * @param array $identifier The identifier of an entity + * + * @return string The entity id hash. + */ + final public static function getIdHashByIdentifier(array $identifier): string + { + return implode( + ' ', + array_map( + static function ($value) { + if ($value instanceof BackedEnum) { + return $value->value; + } + + return $value; + }, + $identifier + ) + ); + } + + /** + * Gets the id hash of an entity. + * + * @param object $entity The entity managed by Unit Of Work + * + * @return string The entity id hash. + */ + public function getIdHashByEntity($entity): string + { + $identifier = $this->entityIdentifiers[spl_object_id($entity)]; + + if (empty($identifier) || in_array(null, $identifier, true)) { + $classMetadata = $this->em->getClassMetadata(get_class($entity)); + + throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity); + } + + return self::getIdHashByIdentifier($identifier); + } + /** * Gets the state of an entity with regard to the current unit of work. * @@ -1686,7 +1724,7 @@ public function removeFromIdentityMap($entity) { $oid = spl_object_id($entity); $classMetadata = $this->em->getClassMetadata(get_class($entity)); - $idHash = implode(' ', $this->entityIdentifiers[$oid]); + $idHash = self::getIdHashByIdentifier($this->entityIdentifiers[$oid]); if ($idHash === '') { throw ORMInvalidArgumentException::entityHasNoIdentity($entity, 'remove from identity map'); @@ -1756,7 +1794,7 @@ public function isInIdentityMap($entity) } $classMetadata = $this->em->getClassMetadata(get_class($entity)); - $idHash = implode(' ', $this->entityIdentifiers[$oid]); + $idHash = self::getIdHashByIdentifier($this->entityIdentifiers[$oid]); return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]); } @@ -2713,7 +2751,7 @@ public function createEntity($className, array $data, &$hints = []) $class = $this->em->getClassMetadata($className); $id = $this->identifierFlattener->flattenIdentifier($class, $data); - $idHash = implode(' ', $id); + $idHash = self::getIdHashByIdentifier($id); if (isset($this->identityMap[$class->rootEntityName][$idHash])) { $entity = $this->identityMap[$class->rootEntityName][$idHash]; @@ -2872,7 +2910,7 @@ public function createEntity($className, array $data, &$hints = []) // Check identity map first // FIXME: Can break easily with composite keys if join column values are in // wrong order. The correct order is the one in ClassMetadata#identifier. - $relatedIdHash = implode(' ', $associatedId); + $relatedIdHash = self::getIdHashByIdentifier($associatedId); switch (true) { case isset($this->identityMap[$targetClass->rootEntityName][$relatedIdHash]): @@ -3157,7 +3195,7 @@ public function getSingleIdentifierValue($entity) */ public function tryGetById($id, $rootClassName) { - $idHash = implode(' ', (array) $id); + $idHash = self::getIdHashByIdentifier((array) $id); return $this->identityMap[$rootClassName][$idHash] ?? false; } @@ -3539,7 +3577,7 @@ private function isIdentifierEquals($entity1, $entity2): bool $id1 = $this->entityIdentifiers[$oid1] ?? $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity1)); $id2 = $this->entityIdentifiers[$oid2] ?? $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($entity2)); - return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2); + return $id1 === $id2 || self::getIdHashByIdentifier($id1) === self::getIdHashByIdentifier($id2); } /** @throws ORMInvalidArgumentException */ diff --git a/tests/Doctrine/Tests/Models/GH10334/GH10334Foo.php b/tests/Doctrine/Tests/Models/GH10334/GH10334Foo.php new file mode 100644 index 00000000000..1918d32f7b3 --- /dev/null +++ b/tests/Doctrine/Tests/Models/GH10334/GH10334Foo.php @@ -0,0 +1,40 @@ +collection = $collection; + $this->productTypeId = $productTypeId; + } +} diff --git a/tests/Doctrine/Tests/Models/GH10334/GH10334FooCollection.php b/tests/Doctrine/Tests/Models/GH10334/GH10334FooCollection.php new file mode 100644 index 00000000000..749fd899c20 --- /dev/null +++ b/tests/Doctrine/Tests/Models/GH10334/GH10334FooCollection.php @@ -0,0 +1,46 @@ + $foos + */ + private $foos; + + public function __construct() + { + $this->foos = new ArrayCollection(); + } + + /** + * @return Collection + */ + public function getFoos(): Collection + { + return $this->foos; + } +} diff --git a/tests/Doctrine/Tests/Models/GH10334/GH10334Product.php b/tests/Doctrine/Tests/Models/GH10334/GH10334Product.php new file mode 100644 index 00000000000..28c5c39c5a6 --- /dev/null +++ b/tests/Doctrine/Tests/Models/GH10334/GH10334Product.php @@ -0,0 +1,55 @@ +name = $name; + $this->productType = $productType; + } + + public function getProductType(): GH10334ProductType + { + return $this->productType; + } + + public function setProductType(GH10334ProductType $productType): void + { + $this->productType = $productType; + } +} diff --git a/tests/Doctrine/Tests/Models/GH10334/GH10334ProductType.php b/tests/Doctrine/Tests/Models/GH10334/GH10334ProductType.php new file mode 100644 index 00000000000..0e26e956563 --- /dev/null +++ b/tests/Doctrine/Tests/Models/GH10334/GH10334ProductType.php @@ -0,0 +1,55 @@ +id = $id; + $this->value = $value; + $this->products = new ArrayCollection(); + } + + public function getId(): GH10334ProductTypeId + { + return $this->id; + } + + public function addProduct(GH10334Product $product): void + { + $product->setProductType($this); + $this->products->add($product); + } +} diff --git a/tests/Doctrine/Tests/Models/GH10334/GH10334ProductTypeId.php b/tests/Doctrine/Tests/Models/GH10334/GH10334ProductTypeId.php new file mode 100644 index 00000000000..92314e4ba94 --- /dev/null +++ b/tests/Doctrine/Tests/Models/GH10334/GH10334ProductTypeId.php @@ -0,0 +1,11 @@ +setUpEntitySchema([GH10334FooCollection::class, GH10334Foo::class, GH10334ProductType::class, GH10334Product::class]); + } + + public function testTicket(): void + { + $collection = new GH10334FooCollection(); + $foo = new GH10334Foo($collection, GH10334ProductTypeId::Jean); + $foo2 = new GH10334Foo($collection, GH10334ProductTypeId::Short); + + $this->_em->persist($collection); + $this->_em->persist($foo); + $this->_em->persist($foo2); + + $this->_em->flush(); + $this->_em->clear(); + + $result = $this->_em + ->getRepository(GH10334FooCollection::class) + ->createQueryBuilder('collection') + ->leftJoin('collection.foos', 'foo')->addSelect('foo') + ->getQuery() + ->getResult(); + + $this->_em + ->getRepository(GH10334FooCollection::class) + ->createQueryBuilder('collection') + ->leftJoin('collection.foos', 'foo')->addSelect('foo') + ->getQuery() + ->getResult(); + + $this->assertCount(1, $result); + $this->assertCount(2, $result[0]->getFoos()); + } + + public function testGetChildWithBackedEnumId(): void + { + $jean = new GH10334ProductType(GH10334ProductTypeId::Jean, 23.5); + $short = new GH10334ProductType(GH10334ProductTypeId::Short, 45.2); + $product = new GH10334Product('Extra Large Blue', $jean); + + $jean->addProduct($product); + + $this->_em->persist($jean); + $this->_em->persist($short); + $this->_em->persist($product); + + $this->_em->flush(); + $this->_em->clear(); + + $entity = $this->_em->find(GH10334Product::class, 1); + + self::assertNotNull($entity); + self::assertSame($entity->getProductType()->getId(), GH10334ProductTypeId::Jean); + } +}