diff --git a/lib/Doctrine/ORM/Tools/Pagination/Paginator.php b/lib/Doctrine/ORM/Tools/Pagination/Paginator.php index 250df071f..0987dfc34 100644 --- a/lib/Doctrine/ORM/Tools/Pagination/Paginator.php +++ b/lib/Doctrine/ORM/Tools/Pagination/Paginator.php @@ -145,6 +145,7 @@ class Paginator implements \Countable, \IteratorAggregate $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class); } else { $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class); + $this->unbindUnusedQueryParams($subQuery); } $subQuery->setFirstResult($offset)->setMaxResults($length); @@ -256,14 +257,20 @@ class Paginator implements \Countable, \IteratorAggregate $countQuery->setResultSetMapping($rsm); } else { $this->appendTreeWalker($countQuery, CountWalker::class); + $this->unbindUnusedQueryParams($countQuery); } $countQuery->setFirstResult(null)->setMaxResults(null); - $parser = new Parser($countQuery); + return $countQuery; + } + + private function unbindUnusedQueryParams(Query $query): void + { + $parser = new Parser($query); $parameterMappings = $parser->parse()->getParameterMappings(); /* @var $parameters \Doctrine\Common\Collections\Collection|\Doctrine\ORM\Query\Parameter[] */ - $parameters = $countQuery->getParameters(); + $parameters = $query->getParameters(); foreach ($parameters as $key => $parameter) { $parameterName = $parameter->getName(); @@ -273,8 +280,6 @@ class Paginator implements \Countable, \IteratorAggregate } } - $countQuery->setParameters($parameters); - - return $countQuery; + $query->setParameters($parameters); } } diff --git a/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginatorTest.php b/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginatorTest.php new file mode 100644 index 000000000..4b4fc5202 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/Pagination/PaginatorTest.php @@ -0,0 +1,119 @@ +connection = $this->getMockBuilder(ConnectionMock::class) + ->setConstructorArgs([[], new DriverMock()]) + ->setMethods(['executeQuery']) + ->getMock() + ; + + $this->em = $this->getMockBuilder(EntityManagerDecorator::class) + ->setConstructorArgs([$this->_getTestEntityManager($this->connection)]) + ->setMethods(['newHydrator']) + ->getMock() + ; + + $this->hydrator = $this->createMock(AbstractHydrator::class); + $this->em->method('newHydrator')->willReturn($this->hydrator); + } + + public function testExtraParametersAreStrippedWhenWalkerRemovingOriginalSelectElementsIsUsed() : void + { + $paramInWhere = 1; + $paramInSubSelect = 2; + $returnedIds = [10]; + + $this->hydrator->method('hydrateAll')->willReturn([$returnedIds]); + + $query = new Query($this->em); + $query->setDQL( + 'SELECT u, + ( + SELECT MAX(a.version) + FROM Doctrine\\Tests\\Models\\CMS\\CmsArticle a + WHERE a.user = u AND 1 = :paramInSubSelect + ) AS HIDDEN max_version + FROM Doctrine\\Tests\\Models\\CMS\\CmsUser u + WHERE u.id = :paramInWhere' + ); + $query->setParameters(['paramInWhere' => $paramInWhere, 'paramInSubSelect' => $paramInSubSelect]); + $paginator = (new Paginator($query, true))->setUseOutputWalkers(false); + + $this->connection->expects($this->exactly(3))->method('executeQuery'); + + $this->connection->expects($this->at(0)) + ->method('executeQuery') + ->with($this->anything(), [$paramInWhere]) + ; + + $this->connection->expects($this->at(1)) + ->method('executeQuery') + ->with($this->anything(), [$paramInWhere]) + ; + + $this->connection->expects($this->at(2)) + ->method('executeQuery') + ->with($this->anything(), [$paramInSubSelect, $paramInWhere, $returnedIds]) + ; + + $paginator->count(); + $paginator->getIterator(); + } + + public function testPaginatorNotCaringAboutExtraParametersWithoutOutputWalkers() : void + { + $this->connection->expects($this->exactly(3))->method('executeQuery'); + + $this->createPaginatorWithExtraParametersWithoutOutputWalkers([])->count(); + $this->createPaginatorWithExtraParametersWithoutOutputWalkers([[10]])->count(); + $this->createPaginatorWithExtraParametersWithoutOutputWalkers([])->getIterator(); + } + + public function testgetIteratorDoesCareAboutExtraParametersWithoutOutputWalkersWhenResultIsNotEmpty() : void + { + $this->connection->expects($this->exactly(1))->method('executeQuery'); + $this->expectException(Query\QueryException::class); + $this->expectExceptionMessage('Too many parameters: the query defines 1 parameters and you bound 2'); + + $this->createPaginatorWithExtraParametersWithoutOutputWalkers([[10]])->getIterator(); + } + + /** + * @param int[][] $willReturnRows + */ + private function createPaginatorWithExtraParametersWithoutOutputWalkers(array $willReturnRows) : Paginator + { + $this->hydrator->method('hydrateAll')->willReturn($willReturnRows); + $this->connection->method('executeQuery')->with($this->anything(), []); + + $query = new Query($this->em); + $query->setDQL('SELECT u FROM Doctrine\\Tests\\Models\\CMS\\CmsUser u'); + $query->setParameters(['paramInWhere' => 1]); + + return (new Paginator($query, true))->setUseOutputWalkers(false); + } +}