From cbec236e8b1168c7b60e6c2a223333bebbae594c Mon Sep 17 00:00:00 2001 From: Simon Podlipsky Date: Thu, 25 Apr 2024 10:32:40 +0200 Subject: [PATCH] fix: always cleanup in `AbstractHydrator::toIterable()` (#11101) Previously it didn't cleanup anything as long as the iteration hasn't reached the final row. Co-authored-by: Oleg Andreyev --- src/Internal/Hydration/AbstractHydrator.php | 34 ++++++++++--------- .../ORM/Hydration/AbstractHydratorTest.php | 30 ++++++++++++++++ 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/src/Internal/Hydration/AbstractHydrator.php b/src/Internal/Hydration/AbstractHydrator.php index e9782e6d5fa..d7071cf7f68 100644 --- a/src/Internal/Hydration/AbstractHydrator.php +++ b/src/Internal/Hydration/AbstractHydrator.php @@ -182,29 +182,31 @@ public function toIterable($stmt, ResultSetMapping $resultSetMapping, array $hin $this->prepare(); - while (true) { - $row = $this->statement()->fetchAssociative(); - - if ($row === false) { - $this->cleanup(); + try { + while (true) { + $row = $this->statement()->fetchAssociative(); - break; - } + if ($row === false) { + break; + } - $result = []; + $result = []; - $this->hydrateRowData($row, $result); + $this->hydrateRowData($row, $result); - $this->cleanupAfterRowIteration(); - if (count($result) === 1) { - if (count($resultSetMapping->indexByMap) === 0) { - yield end($result); + $this->cleanupAfterRowIteration(); + if (count($result) === 1) { + if (count($resultSetMapping->indexByMap) === 0) { + yield end($result); + } else { + yield from $result; + } } else { - yield from $result; + yield $result; } - } else { - yield $result; } + } finally { + $this->cleanup(); } } diff --git a/tests/Tests/ORM/Hydration/AbstractHydratorTest.php b/tests/Tests/ORM/Hydration/AbstractHydratorTest.php index 24ababac507..c08c24c0e5c 100644 --- a/tests/Tests/ORM/Hydration/AbstractHydratorTest.php +++ b/tests/Tests/ORM/Hydration/AbstractHydratorTest.php @@ -12,6 +12,7 @@ use Doctrine\ORM\Internal\Hydration\AbstractHydrator; use Doctrine\ORM\ORMException; use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\Tests\Models\Hydration\SimpleEntity; use Doctrine\Tests\OrmFunctionalTestCase; use PHPUnit\Framework\MockObject\MockObject; @@ -154,4 +155,33 @@ public function testHydrateAllClearsAllAttachedListenersEvenOnError(): void $this->expectException(ORMException::class); $this->hydrator->hydrateAll($this->mockResult, $this->mockResultMapping); } + + public function testToIterableIfYieldAndBreakBeforeFinishAlwaysCleansUp(): void + { + $this->setUpEntitySchema([SimpleEntity::class]); + + $entity1 = new SimpleEntity(); + $this->_em->persist($entity1); + $entity2 = new SimpleEntity(); + $this->_em->persist($entity2); + + $this->_em->flush(); + $this->_em->clear(); + + $evm = $this->_em->getEventManager(); + + $q = $this->_em->createQuery('SELECT e.id FROM ' . SimpleEntity::class . ' e'); + + // select two entities, but do no iterate + $q->toIterable(); + self::assertCount(0, $evm->getListeners(Events::onClear)); + + // select two entities, but abort after first record + foreach ($q->toIterable() as $result) { + self::assertCount(1, $evm->getListeners(Events::onClear)); + break; + } + + self::assertCount(0, $evm->getListeners(Events::onClear)); + } }