diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 7e54820d01a..53469594720 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -31,6 +31,7 @@ use Doctrine\ORM\Internal\Hydration\IterableResult; use Doctrine\ORM\Mapping\MappingException as ORMMappingException; use Doctrine\ORM\Query\Parameter; +use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\Mapping\MappingException; use Traversable; @@ -998,7 +999,12 @@ public function toIterable(iterable $parameters = [], $hydrationMode = null): it $this->setParameters($parameters); } - $rsm = $this->getResultSetMapping(); + $rsm = $this->getResultSetMapping(); + + if ($rsm->isMixed && count($rsm->scalarMappings) > 0) { + throw QueryException::iterateWithMixedResultNotAllowed(); + } + $stmt = $this->_doExecute(); return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt, $rsm, $this->_hints); diff --git a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php index 02fbef3b0e9..e1f27dcb050 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @@ -35,6 +35,7 @@ use function array_map; use function array_merge; +use function count; use function end; use function in_array; use function trigger_error; @@ -165,8 +166,6 @@ public function toIterable(Statement $stmt, ResultSetMapping $resultSetMapping, $this->prepare(); - $result = []; - while (true) { $row = $this->_stmt->fetch(FetchMode::ASSOCIATIVE); @@ -176,9 +175,17 @@ public function toIterable(Statement $stmt, ResultSetMapping $resultSetMapping, break; } + $result = []; + $this->hydrateRowData($row, $result); - yield end($result); + $this->cleanupAfterRowIteration(); + + if (count($result) === 1) { + yield end($result); + } else { + yield $result; + } } } @@ -274,6 +281,10 @@ protected function cleanup() ->removeEventListener([Events::onClear], $this); } + protected function cleanupAfterRowIteration(): void + { + } + /** * Hydrates a single row from the current statement instance. * diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 03ea08523ac..7bcee57b8df 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -141,6 +141,14 @@ protected function cleanup() $this->_uow->hydrationComplete(); } + protected function cleanupAfterRowIteration(): void + { + $this->identifierMap = + $this->initializedCollections = + $this->existingCollections = + $this->resultPointers = []; + } + /** * {@inheritdoc} */ diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index a26861da8b8..4029812ef74 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -674,6 +674,8 @@ public function getMaxResults() * Executes the query and returns an IterableResult that can be used to incrementally * iterated over the result. * + * @deprecated + * * @param ArrayCollection|mixed[]|null $parameters The query parameters. * @param string|int $hydrationMode The hydration mode to use. * diff --git a/lib/Doctrine/ORM/Query/QueryException.php b/lib/Doctrine/ORM/Query/QueryException.php index ce59d4f3c5f..cc7bac80cad 100644 --- a/lib/Doctrine/ORM/Query/QueryException.php +++ b/lib/Doctrine/ORM/Query/QueryException.php @@ -227,6 +227,11 @@ public static function iterateWithFetchJoinNotAllowed($assoc) ); } + public static function iterateWithMixedResultNotAllowed(): QueryException + { + return new self('Iterating a query with mixed results (using scalars) is not supported.'); + } + /** * @return QueryException */ diff --git a/tests/Doctrine/Tests/ORM/Functional/AdvancedDqlQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/AdvancedDqlQueryTest.php index b04dd011769..ad816381dab 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AdvancedDqlQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/AdvancedDqlQueryTest.php @@ -171,8 +171,6 @@ public function testSelectSubselect(): void $this->assertEquals('Caramba', $result[0]['brandName']); $this->_em->clear(); - - IterableTester::assertResultsAreTheSame($query); } public function testInSubselect(): void diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryIterableTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryIterableTest.php index 43a2cedafd8..62b86ae2508 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryIterableTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryIterableTest.php @@ -32,6 +32,8 @@ public function testAlias(): void $users = $query->getResult(); self::assertCount(1, $users); + $this->assertEquals('gblanco', $users[0]['user']->username); + $this->_em->clear(); IterableTester::assertResultsAreTheSame($query); @@ -60,6 +62,8 @@ public function testAliasInnerJoin(): void $users = $query->getResult(); self::assertCount(1, $users); + $this->assertEquals('gblanco', $users[0]['user']->username); + $this->_em->clear(); IterableTester::assertResultsAreTheSame($query); diff --git a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php index 1355982f6d9..9a5215b5af5 100644 --- a/tests/Doctrine/Tests/ORM/Functional/QueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/QueryTest.php @@ -301,6 +301,51 @@ public function testIterateResultIterativelyBuildUpUnitOfWork(): void self::assertSame(2, $iteratedCount); } + public function testToIterableWithMultipleSelectElements(): void + { + $author = new CmsUser(); + $author->name = 'Ben'; + $author->username = 'beberlei'; + + $article1 = new CmsArticle(); + $article1->topic = 'Doctrine 2'; + $article1->text = 'This is an introduction to Doctrine 2.'; + $article1->setAuthor($author); + + $article2 = new CmsArticle(); + $article2->topic = 'Symfony 2'; + $article2->text = 'This is an introduction to Symfony 2.'; + $article2->setAuthor($author); + + $this->_em->persist($article1); + $this->_em->persist($article2); + $this->_em->persist($author); + + $this->_em->flush(); + $this->_em->clear(); + + $query = $this->_em->createQuery('select a, u from ' . CmsArticle::class . ' a JOIN ' . CmsUser::class . ' u WITH a.user = u'); + + $result = iterator_to_array($query->toIterable()); + + $this->assertCount(2, $result); + + foreach ($result as $row) { + $this->assertCount(2, $row); + $this->assertInstanceOf(CmsArticle::class, $row[0]); + $this->assertInstanceOf(CmsUser::class, $row[1]); + } + } + + public function testToIterableWithMixedResultIsNotAllowed(): void + { + $this->expectException(QueryException::class); + $this->expectExceptionMessage('Iterating a query with mixed results (using scalars) is not supported.'); + + $query = $this->_em->createQuery('select a, a.topic from ' . CmsArticle::class . ' a'); + $query->toIterable(); + } + public function testIterateResultClearEveryCycle(): void { $article1 = new CmsArticle(); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7496WithToIterableTest.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7496WithToIterableTest.php index a43aa608b7c..ea15e9e9847 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7496WithToIterableTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7496WithToIterableTest.php @@ -41,9 +41,20 @@ public function testNonUniqueObjectHydrationDuringIteration(): void $bs = IterableTester::iterableToArray( $q->toIterable([], AbstractQuery::HYDRATE_OBJECT) ); + $this->assertCount(2, $bs); $this->assertInstanceOf(GH7496EntityB::class, $bs[0]); $this->assertInstanceOf(GH7496EntityB::class, $bs[1]); + $this->assertEquals(1, $bs[0]->id); + $this->assertEquals(1, $bs[1]->id); + + $bs = IterableTester::iterableToArray( + $q->toIterable([], AbstractQuery::HYDRATE_ARRAY) + ); + + $this->assertCount(2, $bs); + $this->assertEquals(1, $bs[0]['id']); + $this->assertEquals(1, $bs[1]['id']); } }