getQuery(); } $this->query = $query; $this->fetchJoinCollection = (bool) $fetchJoinCollection; } /** * Returns the query. * * @return Query */ public function getQuery() { return $this->query; } /** * Returns whether the query joins a collection. * * @return bool Whether the query joins a collection. */ public function getFetchJoinCollection() { return $this->fetchJoinCollection; } /** * Returns whether the paginator will use an output walker. * * @return bool|null */ public function getUseOutputWalkers() { return $this->useOutputWalkers; } /** * Sets whether the paginator will use an output walker. * * @param bool|null $useOutputWalkers * * @return $this * @psalm-return static */ public function setUseOutputWalkers($useOutputWalkers) { $this->useOutputWalkers = $useOutputWalkers; return $this; } /** * {@inheritdoc} * * @return int */ #[ReturnTypeWillChange] public function count() { if ($this->count === null) { try { $this->count = (int) array_sum(array_map('current', $this->getCountQuery()->getScalarResult())); } catch (NoResultException $e) { $this->count = 0; } } return $this->count; } /** * {@inheritdoc} * * @return ArrayIterator * @psalm-return ArrayIterator */ #[ReturnTypeWillChange] public function getIterator() { $offset = $this->query->getFirstResult(); $length = $this->query->getMaxResults(); if ($this->fetchJoinCollection && $length !== null) { $subQuery = $this->cloneQuery($this->query); if ($this->useOutputWalker($subQuery)) { $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class); } else { $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class); $this->unbindUnusedQueryParams($subQuery); } $subQuery->setFirstResult($offset)->setMaxResults($length); $foundIdRows = $subQuery->getScalarResult(); // don't do this for an empty id array if ($foundIdRows === []) { return new ArrayIterator([]); } $whereInQuery = $this->cloneQuery($this->query); $ids = array_map('current', $foundIdRows); $this->appendTreeWalker($whereInQuery, WhereInWalker::class); $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids)); $whereInQuery->setFirstResult(null)->setMaxResults(null); $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids); $whereInQuery->setCacheable($this->query->isCacheable()); $whereInQuery->expireQueryCache(); $result = $whereInQuery->getResult($this->query->getHydrationMode()); } else { $result = $this->cloneQuery($this->query) ->setMaxResults($length) ->setFirstResult($offset) ->setCacheable($this->query->isCacheable()) ->getResult($this->query->getHydrationMode()); } return new ArrayIterator($result); } private function cloneQuery(Query $query): Query { $cloneQuery = clone $query; $cloneQuery->setParameters(clone $query->getParameters()); $cloneQuery->setCacheable(false); foreach ($query->getHints() as $name => $value) { $cloneQuery->setHint($name, $value); } return $cloneQuery; } /** * Determines whether to use an output walker for the query. */ private function useOutputWalker(Query $query): bool { if ($this->useOutputWalkers === null) { return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false; } return $this->useOutputWalkers; } /** * Appends a custom tree walker to the tree walkers hint. * * @psalm-param class-string $walkerClass */ private function appendTreeWalker(Query $query, string $walkerClass): void { $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS); if ($hints === false) { $hints = []; } $hints[] = $walkerClass; $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints); } /** * Returns Query prepared to count. */ private function getCountQuery(): Query { $countQuery = $this->cloneQuery($this->query); if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) { $countQuery->setHint(CountWalker::HINT_DISTINCT, true); } if ($this->useOutputWalker($countQuery)) { $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win $rsm = new ResultSetMapping(); $rsm->addScalarResult($this->getSQLResultCasing($platform, 'dctrn_count'), 'count'); $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class); $countQuery->setResultSetMapping($rsm); } else { $this->appendTreeWalker($countQuery, CountWalker::class); $this->unbindUnusedQueryParams($countQuery); } $countQuery->setFirstResult(null)->setMaxResults(null); return $countQuery; } private function unbindUnusedQueryParams(Query $query): void { $parser = new Parser($query); $parameterMappings = $parser->parse()->getParameterMappings(); /** @var Collection|Parameter[] $parameters */ $parameters = $query->getParameters(); foreach ($parameters as $key => $parameter) { $parameterName = $parameter->getName(); if (! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) { unset($parameters[$key]); } } $query->setParameters($parameters); } }