Repository: doctrine/orm (php)

Issue:
Fetch mode hint is ignored when using `Paginator` 
### Bug Report

<!-- Fill in the relevant information below to help triage your issue. -->

|    Q                                        |   A
|-------------------------------------------- | --------------
| Version                                     | 2.20.0 / 3.3.0
| Previous working version                    | 2.19.8 / 3.2.3

#### Summary

This is related to issue #11128 and PR #11445.

When a `fetch=EAGER` association is filtered using `WITH`, it raises an error unless fetch mode is overridden by calling `setFetchMode(LAZY)` over the association.

This worked well after the fix in #11445.

Somehow, in 2.20.x and 3.3.x this bug returned, partially, only when using `Paginator` with `fetchJoinCollection` parameter set to `true`.

Seems like #11188 introduced this bug.


#### Current behavior

An exception is raised even when fetch mode is overridden.

#### Expected behavior

It should work the same with or without pagination.

#### How to reproduce

See test in https://github.com/aprat84/orm/tree/gh-11741

```
vendor/bin/phpunit --filter testSubselectFetchJoinWithAllowedWhenOverriddenNotEagerPaginator

There was 1 error:

1) Doctrine\Tests\ORM\Functional\EagerFetchCollectionTest::testSubselectFetchJoinWithAllowedWhenOverriddenNotEagerPaginator
Doctrine\ORM\Query\QueryException: Associations with fetch-mode=EAGER may not be using WITH conditions in "Doctrine\Tests\ORM\Functional\EagerFetchChild#owner".
```

Pull request:
Clone query hints and parameters in `LimitSubqueryOutputWalker` constructor
Fixes #11741

Should be merged up to at least v2.20.0 and v3.3.0.

The problem seems to be that `LimitSubqueryOutputWalker` clones the `Query` object, but not its hints (including the `fetchMode` hint, which causes the issue).
Don't know if cloning the parameters is really needed.
Been inspired on what the class `Paginator` does when cloning a `Query`.
Suggestions are welcome!

Patch:
diff --git a/src/Persisters/Entity/BasicEntityPersister.php b/src/Persisters/Entity/BasicEntityPersister.php
index 2e905bca7..4073c606e 100644
--- a/src/Persisters/Entity/BasicEntityPersister.php
+++ b/src/Persisters/Entity/BasicEntityPersister.php
@@ -1374,12 +1374,6 @@ class BasicEntityPersister implements EntityPersister
                     $joinCondition[] = $this->getSQLTableAlias($association['sourceEntity'], $assocAlias) . '.' . $sourceCol . ' = '
                         . $this->getSQLTableAlias($association['targetEntity']) . '.' . $targetCol;
                 }
-
-                // Add filter SQL
-                $filterSql = $this->generateFilterConditionSQL($eagerEntity, $joinTableAlias);
-                if ($filterSql) {
-                    $joinCondition[] = $filterSql;
-                }
             }
 
             $this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON ';
diff --git a/src/Tools/Pagination/LimitSubqueryOutputWalker.php b/src/Tools/Pagination/LimitSubqueryOutputWalker.php
index 95b9066db..ea7ec0f63 100644
--- a/src/Tools/Pagination/LimitSubqueryOutputWalker.php
+++ b/src/Tools/Pagination/LimitSubqueryOutputWalker.php
@@ -105,17 +105,24 @@ class LimitSubqueryOutputWalker extends SqlOutputWalker
         $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
         $this->rsm      = $parserResult->getResultSetMapping();
 
-        $query = clone $query;
+        $cloneQuery = clone $query;
+
+        $cloneQuery->setParameters(clone $query->getParameters());
+        $cloneQuery->setCacheable(false);
+
+        foreach ($query->getHints() as $name => $value) {
+            $cloneQuery->setHint($name, $value);
+        }
 
         // Reset limit and offset
-        $this->firstResult = $query->getFirstResult();
-        $this->maxResults  = $query->getMaxResults();
-        $query->setFirstResult(0)->setMaxResults(null);
+        $this->firstResult = $cloneQuery->getFirstResult();
+        $this->maxResults  = $cloneQuery->getMaxResults();
+        $cloneQuery->setFirstResult(0)->setMaxResults(null);
 
-        $this->em            = $query->getEntityManager();
+        $this->em            = $cloneQuery->getEntityManager();
         $this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();
 
-        parent::__construct($query, $parserResult, $queryComponents);
+        parent::__construct($cloneQuery, $parserResult, $queryComponents);
     }
 
     /**


Test patch:
diff --git a/tests/Tests/ORM/Functional/EagerFetchCollectionTest.php b/tests/Tests/ORM/Functional/EagerFetchCollectionTest.php
index 88397c6a1..3a21a9383 100644
--- a/tests/Tests/ORM/Functional/EagerFetchCollectionTest.php
+++ b/tests/Tests/ORM/Functional/EagerFetchCollectionTest.php
@@ -7,9 +7,11 @@ namespace Doctrine\Tests\ORM\Functional;
 use Doctrine\Common\Collections\ArrayCollection;
 use Doctrine\ORM\Mapping as ORM;
 use Doctrine\ORM\Query\QueryException;
+use Doctrine\ORM\Tools\Pagination\Paginator;
 use Doctrine\Tests\OrmFunctionalTestCase;
 
 use function count;
+use function iterator_to_array;
 
 class EagerFetchCollectionTest extends OrmFunctionalTestCase
 {
@@ -96,6 +98,16 @@ class EagerFetchCollectionTest extends OrmFunctionalTestCase
         $this->assertIsString($query->getSql());
     }
 
+    public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEagerPaginator(): void
+    {
+        $query = $this->_em->createQuery('SELECT o, c FROM ' . EagerFetchOwner::class . ' o JOIN o.children c WITH c.id = 1');
+        $query->setMaxResults(1);
+        $query->setFetchMode(EagerFetchChild::class, 'owner', ORM\ClassMetadata::FETCH_LAZY);
+
+        $paginator = new Paginator($query, true);
+        $this->assertIsArray(iterator_to_array($paginator));
+    }
+
     public function testEagerFetchWithIterable(): void
     {
         $this->createOwnerWithChildren(2);
diff --git a/tests/Tests/ORM/Functional/SQLFilterTest.php b/tests/Tests/ORM/Functional/SQLFilterTest.php
index 6e8ee365a..f6b966cdd 100644
--- a/tests/Tests/ORM/Functional/SQLFilterTest.php
+++ b/tests/Tests/ORM/Functional/SQLFilterTest.php
@@ -537,21 +537,6 @@ class SQLFilterTest extends OrmFunctionalTestCase
         self::assertEquals(2, count($query->getResult()));
     }
 
-    public function testOneToOneInverseSideWithFilter(): void
-    {
-        $this->loadFixtureData();
-
-        $conf = $this->_em->getConfiguration();
-        $conf->addFilter('country', '\Doctrine\Tests\ORM\Functional\CMSCountryFilter');
-        $this->_em->getFilters()->enable('country')->setParameterList('country', ['Germany'], Types::STRING);
-
-        $user = $this->_em->find(CmsUser::class, $this->userId);
-        self::assertNotEmpty($user->address);
-
-        $user2 = $this->_em->find(CmsUser::class, $this->userId2);
-        self::assertEmpty($user2->address);
-    }
-
     public function testManyToManyFilter(): void
     {
         $this->loadFixtureData();
diff --git a/tests/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php b/tests/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php
index 95d2047d5..c11eccc89 100644
--- a/tests/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php
+++ b/tests/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php
@@ -46,6 +46,24 @@ final class LimitSubqueryOutputWalkerTest extends PaginationTestCase
         $this->entityManager->getConnection()->setDatabasePlatform($platform);
     }
 
+    public function testSubqueryClonedCompletely(): void
+    {
+        $query = $this->createQuery('SELECT p FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p');
+        $query->setParameter('dummy-param', 123);
+        $query->setHint('dummy-hint', 'dummy-value');
+        $query->setCacheable(true);
+
+        $walker = new LimitSubqueryOutputWalker($query, new Query\ParserResult(), []);
+
+        self::assertNotSame($query, $walker->getQuery());
+        self::assertTrue($walker->getQuery()->hasHint('dummy-hint'));
+        self::assertSame('dummy-value', $walker->getQuery()->getHint('dummy-hint'));
+        self::assertNotSame($query->getParameters(), $walker->getQuery()->getParameters());
+        self::assertInstanceOf(Query\Parameter::class, $param = $walker->getQuery()->getParameter('dummy-param'));
+        self::assertSame(123, $param->getValue());
+        self::assertFalse($walker->getQuery()->isCacheable());
+    }
+
     public function testLimitSubquery(): void
     {
         $query = $this->createQuery('SELECT p, c, a FROM Doctrine\Tests\ORM\Tools\Pagination\MyBlogPost p JOIN p.category c JOIN p.author a');