vendor/doctrine/orm/lib/Doctrine/ORM/Tools/Pagination/Paginator.php line 253
<?phpdeclare(strict_types=1);namespace Doctrine\ORM\Tools\Pagination;use ArrayIterator;use Countable;use Doctrine\Common\Collections\Collection;use Doctrine\ORM\Internal\SQLResultCasing;use Doctrine\ORM\NoResultException;use Doctrine\ORM\Query;use Doctrine\ORM\Query\Parameter;use Doctrine\ORM\Query\Parser;use Doctrine\ORM\Query\ResultSetMapping;use Doctrine\ORM\QueryBuilder;use IteratorAggregate;use ReturnTypeWillChange;use Traversable;use function array_key_exists;use function array_map;use function array_sum;use function assert;use function is_string;/*** The paginator can handle various complex scenarios with DQL.** @template-covariant T* @implements IteratorAggregate<array-key, T>*/class Paginator implements Countable, IteratorAggregate{use SQLResultCasing;/** @var Query */private $query;/** @var bool */private $fetchJoinCollection;/** @var bool|null */private $useOutputWalkers;/** @var int|null */private $count;/*** @param Query|QueryBuilder $query A Doctrine ORM query or query builder.* @param bool $fetchJoinCollection Whether the query joins a collection (true by default).*/public function __construct($query, $fetchJoinCollection = true){if ($query instanceof QueryBuilder) {$query = $query->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<T>*/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 Traversable* @psalm-return Traversable<array-key, T>*/#[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 arrayif ($foundIdRows === []) {return new ArrayIterator([]);}$whereInQuery = $this->cloneQuery($this->query);$ids = array_map('current', $foundIdRows);$this->appendTreeWalker($whereInQuery, WhereInWalker::class);$whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_HAS_IDS, true);$whereInQuery->setFirstResult(0)->setMaxResults(null);$whereInQuery->setCacheable($this->query->isCacheable());$databaseIds = $this->convertWhereInIdentifiersToDatabaseValues($ids);$whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $databaseIds);$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(0)->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);}/*** @param mixed[] $identifiers** @return mixed[]*/private function convertWhereInIdentifiersToDatabaseValues(array $identifiers): array{$query = $this->cloneQuery($this->query);$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, RootTypeWalker::class);$connection = $this->query->getEntityManager()->getConnection();$type = $query->getSQL();assert(is_string($type));return array_map(static function ($id) use ($connection, $type) {return $connection->convertToDatabaseValue($id, $type);}, $identifiers);}}