Repository: doctrine/orm (php)

Issue:
Collections data loaded twice when using EAGER loading in combination with iterables
### Bug Report

<!-- Fill in the relevant information below to help triage your issue. -->

|    Q        |   A
|------------ | ------
| BC Break    | yes
| Version     | 2.17.0/2.17.1

#### Summary

<!-- Provide a summary describing the problem you are experiencing. -->

With #8391 optimisations where added with relation to eager loading collections. However, the eager load of the collection is not triggered when using an iterable result, causing the collection to be hydrated twice when actually accessed.

#### Current behavior

<!-- What is the current (buggy) behavior? -->

When an uninitialised collection within an entity coming from a iterable result is hit, it will trigger its load. However, this load also triggers the unit of work to process the stored eagerly loaded collection to load, resulting in the collection being loaded twice: it will contain duplicate items.

#### How to reproduce

<!--
Provide steps to reproduce the bug.
If possible, also add a code snippet with relevant configuration, entity mappings, DQL etc.
Adding a failing Unit or Functional Test would help us a lot - you can submit one in a Pull Request separately, referencing this bug report.
-->

See the added test, and proposed fix in #11082.

#### Expected behavior

<!-- What was the expected (correct) behavior? -->

The collection should not contains each item twice. I believe the eager load for the collection should be skipped by the UoW as it is already being loaded at that time.


Pull request description:
Do not defer eager collection loading when in iteration context
The test shows that eager collection loading is broken when used in conjunction with iterating over a query result. I also added a proposed fix to not defer eager collection loading when the iteration hint has been set.

Fixes #11081

Patch:
diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php
index b1fef6d20..0407c8455 100644
--- a/lib/Doctrine/ORM/UnitOfWork.php
+++ b/lib/Doctrine/ORM/UnitOfWork.php
@@ -3104,9 +3104,10 @@ EXCEPTION
                     $reflField->setValue($entity, $pColl);
 
                     if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
-                        if ($assoc['type'] === ClassMetadata::ONE_TO_MANY) {
+                        $isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
+                        if (! $isIteration && $assoc['type'] === ClassMetadata::ONE_TO_MANY) {
                             $this->scheduleCollectionForBatchLoading($pColl, $class);
-                        } elseif ($assoc['type'] === ClassMetadata::MANY_TO_MANY) {
+                        } elseif (($isIteration && $assoc['type'] === ClassMetadata::ONE_TO_MANY) || $assoc['type'] === ClassMetadata::MANY_TO_MANY) {
                             $this->loadCollection($pColl);
                             $pColl->takeSnapshot();
                         }


Test patch:
diff --git a/tests/Doctrine/Tests/ORM/Functional/EagerFetchCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/EagerFetchCollectionTest.php
index 90cd1793a..cd76ee8b0 100644
--- a/tests/Doctrine/Tests/ORM/Functional/EagerFetchCollectionTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/EagerFetchCollectionTest.php
@@ -84,6 +84,18 @@ class EagerFetchCollectionTest extends OrmFunctionalTestCase
         $query->getResult();
     }
 
+    public function testEagerFetchWithIterable(): void
+    {
+        $this->createOwnerWithChildren(2);
+        $this->_em->flush();
+        $this->_em->clear();
+
+        $iterable = $this->_em->getRepository(EagerFetchOwner::class)->createQueryBuilder('o')->getQuery()->toIterable();
+        $owner    = $iterable->current();
+
+        $this->assertCount(2, $owner->children);
+    }
+
     protected function createOwnerWithChildren(int $children): EagerFetchOwner
     {
         $owner = new EagerFetchOwner();