Skip to content

Commit

Permalink
Merge pull request #11428 from xificurk/keep-removed-entity-in-identi…
Browse files Browse the repository at this point in the history
…ty-map

Prevent creation of new MANAGED entity instance by reloading REMOVED entity from database
  • Loading branch information
greg0ire authored Apr 27, 2024
2 parents 2b81a8e + bb36d49 commit 8d34460
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
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,8 @@ private function executeDeletions(): void
$eventsToDispatch = [];

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

$oid = spl_object_id($entity);
$class = $this->em->getClassMetadata(get_class($entity));
$persister = $this->getEntityPersister($class->name);
Expand Down Expand Up @@ -1667,8 +1669,6 @@ public function scheduleForDelete($entity)
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -413,12 +413,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 8d34460

Please sign in to comment.