Skip to content

Commit

Permalink
Keep entities in identity map until the scheduled deletions are execu…
Browse files Browse the repository at this point in the history
…ted.

If the entity gets reloaded from database before the deletions are
executed UnitOfWork needs to be able to return the original instance in
REMOVED state.
  • Loading branch information
xificurk committed Apr 26, 2024
1 parent f79d166 commit 87f2f05
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/UnitOfWork.php
Expand Up @@ -1143,6 +1143,8 @@ private function executeDeletions(): void
$eventsToDispatch = [];

foreach ($entities as $entity) {
$this->removeFromIdentityMap($entity);

$oid = spl_object_id($entity);
$class = $this->em->getClassMetadata($entity::class);
$persister = $this->getEntityPersister($class->name);
Expand Down Expand Up @@ -1484,8 +1486,6 @@ public function scheduleForDelete(object $entity): void
return;
}

$this->removeFromIdentityMap($entity);

unset($this->entityUpdates[$oid]);

if (! isset($this->entityDeletions[$oid])) {
Expand Down
80 changes: 80 additions & 0 deletions tests/Tests/ORM/Functional/Ticket/GH6123Test.php
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\OrmFunctionalTestCase;

class GH6123Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();

$this->createSchemaForModels(
GH6123Entity::class,
);
}

public function testLoadingRemovedEntityFromDatabaseDoesNotCreateNewManagedEntityInstance(): void
{
$entity = new GH6123Entity();
$this->_em->persist($entity);
$this->_em->flush();

self::assertSame(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($entity));
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForDelete($entity));

$this->_em->remove($entity);

$freshEntity = $this->loadEntityFromDatabase($entity->id);
self::assertSame($entity, $freshEntity);

self::assertSame(UnitOfWork::STATE_REMOVED, $this->_em->getUnitOfWork()->getEntityState($freshEntity));
self::assertTrue($this->_em->getUnitOfWork()->isScheduledForDelete($freshEntity));
}

public function testRemovedEntityCanBePersistedAgain(): void
{
$entity = new GH6123Entity();
$this->_em->persist($entity);
$this->_em->flush();

$this->_em->remove($entity);
self::assertSame(UnitOfWork::STATE_REMOVED, $this->_em->getUnitOfWork()->getEntityState($entity));
self::assertTrue($this->_em->getUnitOfWork()->isScheduledForDelete($entity));

$this->loadEntityFromDatabase($entity->id);

$this->_em->persist($entity);
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($entity));
self::assertFalse($this->_em->getUnitOfWork()->isScheduledForDelete($entity));

$this->_em->flush();
}

private function loadEntityFromDatabase(int $id): GH6123Entity|null
{
return $this->_em->createQueryBuilder()
->select('e')
->from(GH6123Entity::class, 'e')
->where('e.id = :id')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
}

#[ORM\Entity]
class GH6123Entity
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[Column(type: Types::INTEGER, nullable: false)]
public int $id;
}
8 changes: 7 additions & 1 deletion tests/Tests/ORM/UnitOfWorkTest.php
Expand Up @@ -288,12 +288,18 @@ public function testRemovedAndRePersistedEntitiesAreInTheIdentityMapAndAreNotGar
$entity->id = 123;

$this->_unitOfWork->registerManaged($entity, ['id' => 123], []);
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_unitOfWork->getEntityState($entity));
self::assertFalse($this->_unitOfWork->isScheduledForDelete($entity));
self::assertTrue($this->_unitOfWork->isInIdentityMap($entity));

$this->_unitOfWork->remove($entity);
self::assertFalse($this->_unitOfWork->isInIdentityMap($entity));
self::assertSame(UnitOfWork::STATE_REMOVED, $this->_unitOfWork->getEntityState($entity));
self::assertTrue($this->_unitOfWork->isScheduledForDelete($entity));
self::assertTrue($this->_unitOfWork->isInIdentityMap($entity));

$this->_unitOfWork->persist($entity);
self::assertSame(UnitOfWork::STATE_MANAGED, $this->_unitOfWork->getEntityState($entity));
self::assertFalse($this->_unitOfWork->isScheduledForDelete($entity));
self::assertTrue($this->_unitOfWork->isInIdentityMap($entity));
}

Expand Down

0 comments on commit 87f2f05

Please sign in to comment.