Skip to content

Commit

Permalink
DDC-2332: Ensure managed entities are always tracked by UOW
Browse files Browse the repository at this point in the history
  • Loading branch information
sandvige committed Feb 12, 2019
1 parent 30c5a00 commit 1db1e88
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 1 deletion.
17 changes: 16 additions & 1 deletion lib/Doctrine/ORM/UnitOfWork.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ class UnitOfWork implements PropertyChangedListener
*/
private $identityMap = [];

/**
* Associate entities with OIDs to ensure the GC won't recycle a managed entity
*
* DDC-2332 / #3037
*
* @var array
*/
private $oidMap = array();

/**
* Map of all identifiers of managed entities.
* Keys are object ids (spl_object_hash).
Expand Down Expand Up @@ -1498,7 +1507,8 @@ public function isEntityScheduled($entity)
public function addToIdentityMap($entity)
{
$classMetadata = $this->em->getClassMetadata(get_class($entity));
$identifier = $this->entityIdentifiers[spl_object_hash($entity)];
$oid = spl_object_hash($entity);
$identifier = $this->entityIdentifiers[$oid];

if (empty($identifier) || in_array(null, $identifier, true)) {
throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->name, $entity);
Expand All @@ -1507,6 +1517,7 @@ public function addToIdentityMap($entity)
$idHash = implode(' ', $identifier);
$className = $classMetadata->rootEntityName;

$this->oidMap[$oid] = $entity;
if (isset($this->identityMap[$className][$idHash])) {
return false;
}
Expand Down Expand Up @@ -1622,6 +1633,7 @@ public function removeFromIdentityMap($entity)

$className = $classMetadata->rootEntityName;

unset($this->oidMap[$oid]);
if (isset($this->identityMap[$className][$idHash])) {
unset($this->identityMap[$className][$idHash]);
unset($this->readOnlyObjects[$oid]);
Expand Down Expand Up @@ -2482,6 +2494,7 @@ public function clear($entityName = null)
{
if ($entityName === null) {
$this->identityMap =
$this->oidMap =
$this->entityIdentifiers =
$this->originalEntityData =
$this->entityChangeSets =
Expand Down Expand Up @@ -2656,6 +2669,7 @@ public function createEntity($className, array $data, &$hints = [])
$this->entityStates[$oid] = self::STATE_MANAGED;
$this->originalEntityData[$oid] = $data;

$this->oidMap[$oid] = $entity;
$this->identityMap[$class->rootEntityName][$idHash] = $entity;

if ($entity instanceof NotifyPropertyChanged) {
Expand Down Expand Up @@ -2806,6 +2820,7 @@ public function createEntity($className, array $data, &$hints = [])
// PERF: Inlined & optimized code from UnitOfWork#registerManaged()
$newValueOid = spl_object_hash($newValue);
$this->entityIdentifiers[$newValueOid] = $associatedId;
$this->oidMap[$newValueOid] = $newValue;
$this->identityMap[$targetClass->rootEntityName][$relatedIdHash] = $newValue;

if (
Expand Down
46 changes: 46 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/IdentityMapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Doctrine\ORM\Query;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmFunctionalTestCase;
Expand Down Expand Up @@ -254,5 +255,50 @@ public function testCollectionValuedAssociationIdentityMapBehaviorWithRefresh()
// Now the collection should be refreshed with correct count
$this->assertEquals(4, count($user2->getPhonenumbers()));
}

/**
* @group HashCollision
*/
public function testHashCollision() {
$user = new CmsUser();
$user->username = "Test";
$user->name = "Test";
$this->_em->persist($user);
$this->_em->flush();

$articles = [];
for ($i = 0; $i < 100; $i++) {
$article = new CmsArticle();
$article->topic = "Test";
$article->text = "Test";
$article->setAuthor($this->_em->merge($user));
$this->_em->persist($article);
$this->_em->flush();
$this->_em->clear();
$articles [] = $article;
}

$user = $this->_em->merge($user);
foreach ($articles as $article) {
$article = $this->_em->merge($article);
$article->setAuthor($user);
}

unset($article);
gc_collect_cycles();

$keep = [];
for ($x = 0; $x < 1000; $x++) {
$keep[] = $article = new CmsArticle();

$article->topic = "Test";
$article->text = "Test";
$article->setAuthor($this->_em->merge($user));

$this->_em->persist($article);
$this->_em->flush();
$this->assertNotNull($article->id, "Article wasn't persisted on iteration $x");
}
}
}

0 comments on commit 1db1e88

Please sign in to comment.