From 44c867827cbe5a9ea490b53dc974da56934fda11 Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sun, 27 May 2012 12:00:43 +0200 Subject: [PATCH] [DDC-1783] Fix memory leak in ObjectHydrator when using AbstractQuery#iterate() and EntityManager#clear() --- .../Internal/Hydration/AbstractHydrator.php | 12 +++++++ .../ORM/Internal/Hydration/ObjectHydrator.php | 17 ++++++++- .../Tests/ORM/Functional/QueryTest.php | 36 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 27e564fc3..a5eae8b72 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -23,6 +23,7 @@ use PDO, Doctrine\DBAL\Connection, Doctrine\DBAL\Types\Type, Doctrine\ORM\EntityManager, + Doctrine\ORM\Events, Doctrine\ORM\Mapping\ClassMetadata; /** @@ -83,6 +84,9 @@ abstract class AbstractHydrator $this->_rsm = $resultSetMapping; $this->_hints = $hints; + $evm = $this->_em->getEventManager(); + $evm->addEventListener(array(Events::onClear), $this); + $this->prepare(); return new IterableResult($this); @@ -375,4 +379,12 @@ abstract class AbstractHydrator $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); } + + /** + * When executed in a hydrate() loop we have to clear internal state to + * decrease memory consumption. + */ + public function onClear($eventArgs) + { + } } diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 133e9708c..51effe63c 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -54,7 +54,6 @@ class ObjectHydrator extends AbstractHydrator private $_rootAliases = array(); private $_initializedCollections = array(); private $_existingCollections = array(); - //private $_createdEntities; /** @override */ @@ -527,4 +526,20 @@ class ObjectHydrator extends AbstractHydrator } } } + + /** + * When executed in a hydrate() loop we may have to clear internal state to + * decrease memory consumption. + */ + public function onClear($eventArgs) + { + parent::onClear($eventArgs); + + $aliases = array_keys($this->_identifierMap); + $this->_identifierMap = array(); + + foreach ($aliases as $alias) { + $this->_identifierMap[$alias] = array(); + } + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 7ac2d1e02..74824ff15 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -227,6 +227,42 @@ class QueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); } + public function testIterateResultClearEveryCycle() + { + $article1 = new CmsArticle; + $article1->topic = "Doctrine 2"; + $article1->text = "This is an introduction to Doctrine 2."; + + $article2 = new CmsArticle; + $article2->topic = "Symfony 2"; + $article2->text = "This is an introduction to Symfony 2."; + + $this->_em->persist($article1); + $this->_em->persist($article2); + + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery("select a from Doctrine\Tests\Models\CMS\CmsArticle a"); + $articles = $query->iterate(); + + $iteratedCount = 0; + $topics = array(); + foreach($articles AS $row) { + $article = $row[0]; + $topics[] = $article->topic; + + $this->_em->clear(); + + $iteratedCount++; + } + + $this->assertEquals(array("Doctrine 2", "Symfony 2"), $topics); + $this->assertEquals(2, $iteratedCount); + + $this->_em->flush(); + } + /** * @expectedException \Doctrine\ORM\Query\QueryException */