diff --git a/docs/en/cookbook/dql-custom-walkers.rst b/docs/en/cookbook/dql-custom-walkers.rst index 4320856eb49..578a32c7c68 100644 --- a/docs/en/cookbook/dql-custom-walkers.rst +++ b/docs/en/cookbook/dql-custom-walkers.rst @@ -101,8 +101,16 @@ The ``Paginate::count(Query $query)`` looks like: { static public function count(Query $query) { - /** @var Query $countQuery */ - $countQuery = clone $query; + /* + To avoid changing the $query passed into the method and to make sure a possibly existing + ResultSetMapping is discarded, we create a new query object any copy relevant data over. + */ + $countQuery = new Query($query->getEntityManager()); + $countQuery->setDQL($query->getDQL()); + $countQuery->setParameters(clone $query->getParameters()); + foreach ($query->getHints() as $name => $value) { + $countQuery->setHint($name, $value); + } $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); $countQuery->setFirstResult(null)->setMaxResults(null); @@ -111,7 +119,7 @@ The ``Paginate::count(Query $query)`` looks like: } } -It clones the query, resets the limit clause first and max results +This resets the limit clause first and max results and registers the ``CountSqlWalker`` custom tree walker which will modify the AST to execute a count query. The walkers implementation is: diff --git a/src/Tools/Pagination/Paginator.php b/src/Tools/Pagination/Paginator.php index a0ac0bfef4d..3d96ac19c20 100644 --- a/src/Tools/Pagination/Paginator.php +++ b/src/Tools/Pagination/Paginator.php @@ -228,7 +228,22 @@ private function appendTreeWalker(Query $query, string $walkerClass): void */ private function getCountQuery(): Query { - $countQuery = $this->cloneQuery($this->query); + /* + As opposed to using self::cloneQuery, the following code does not transfer + a potentially existing result set mapping (either set directly by the user, + or taken from the parser result from a previous invocation of Query::parse()) + to the new query object. This is fine, since we are going to completely change the + select clause, so a previously existing result set mapping (RSM) is probably wrong anyway. + In the case of using output walkers, we are even creating a new RSM down below. + In the case of using a tree walker, we want to have a new RSM created by the parser. + */ + $countQuery = new Query($this->query->getEntityManager()); + $countQuery->setDQL($this->query->getDQL()); + $countQuery->setParameters(clone $this->query->getParameters()); + $countQuery->setCacheable(false); + foreach ($this->query->getHints() as $name => $value) { + $countQuery->setHint($name, $value); + } if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) { $countQuery->setHint(CountWalker::HINT_DISTINCT, true); diff --git a/tests/Tests/ORM/Functional/Ticket/GH12183Test.php b/tests/Tests/ORM/Functional/Ticket/GH12183Test.php new file mode 100644 index 00000000000..15c7846ecde --- /dev/null +++ b/tests/Tests/ORM/Functional/Ticket/GH12183Test.php @@ -0,0 +1,46 @@ +useModelSet('cms'); + + parent::setUp(); + + $article = new CmsArticle(); + + $article->topic = 'Loomings'; + $article->text = 'Call me Ishmael.'; + + $this->_em->persist($article); + $this->_em->flush(); + $this->_em->clear(); + } + + public function testPaginatorCountWithTreeWalkerAfterQueryHasBeenExecuted(): void + { + $query = $this->_em->createQuery('SELECT a FROM Doctrine\Tests\Models\CMS\CmsArticle a'); + + // Paginator::count is right when the query has not yet been executed + $paginator = new Paginator($query); + $paginator->setUseOutputWalkers(false); + self::assertSame(1, $paginator->count()); + + // Execute the query + $result = $query->getResult(); + self::assertCount(1, $result); + + $paginator = new Paginator($query); + $paginator->setUseOutputWalkers(false); + self::assertSame(1, $paginator->count()); + } +}