vendor/doctrine/orm/lib/Doctrine/ORM/Tools/Pagination/Paginator.php line 138

Open in your IDE?
  1. <?php
  2. /*
  3.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14.  *
  15.  * This software consists of voluntary contributions made by many individuals
  16.  * and is licensed under the MIT license. For more information, see
  17.  * <http://www.doctrine-project.org>.
  18.  */
  19. namespace Doctrine\ORM\Tools\Pagination;
  20. use ArrayIterator;
  21. use Countable;
  22. use Doctrine\Common\Collections\Collection;
  23. use Doctrine\ORM\NoResultException;
  24. use Doctrine\ORM\Query;
  25. use Doctrine\ORM\Query\Parameter;
  26. use Doctrine\ORM\Query\Parser;
  27. use Doctrine\ORM\Query\ResultSetMapping;
  28. use Doctrine\ORM\QueryBuilder;
  29. use IteratorAggregate;
  30. use function array_key_exists;
  31. use function array_map;
  32. use function array_sum;
  33. use function count;
  34. /**
  35.  * The paginator can handle various complex scenarios with DQL.
  36.  *
  37.  * @template T
  38.  */
  39. class Paginator implements CountableIteratorAggregate
  40. {
  41.     /** @var Query */
  42.     private $query;
  43.     /** @var bool */
  44.     private $fetchJoinCollection;
  45.     /** @var bool|null */
  46.     private $useOutputWalkers;
  47.     /** @var int */
  48.     private $count;
  49.     /**
  50.      * @param Query|QueryBuilder $query               A Doctrine ORM query or query builder.
  51.      * @param bool               $fetchJoinCollection Whether the query joins a collection (true by default).
  52.      */
  53.     public function __construct($query$fetchJoinCollection true)
  54.     {
  55.         if ($query instanceof QueryBuilder) {
  56.             $query $query->getQuery();
  57.         }
  58.         $this->query               $query;
  59.         $this->fetchJoinCollection = (bool) $fetchJoinCollection;
  60.     }
  61.     /**
  62.      * Returns the query.
  63.      *
  64.      * @return Query
  65.      */
  66.     public function getQuery()
  67.     {
  68.         return $this->query;
  69.     }
  70.     /**
  71.      * Returns whether the query joins a collection.
  72.      *
  73.      * @return bool Whether the query joins a collection.
  74.      */
  75.     public function getFetchJoinCollection()
  76.     {
  77.         return $this->fetchJoinCollection;
  78.     }
  79.     /**
  80.      * Returns whether the paginator will use an output walker.
  81.      *
  82.      * @return bool|null
  83.      */
  84.     public function getUseOutputWalkers()
  85.     {
  86.         return $this->useOutputWalkers;
  87.     }
  88.     /**
  89.      * Sets whether the paginator will use an output walker.
  90.      *
  91.      * @param bool|null $useOutputWalkers
  92.      *
  93.      * @return $this
  94.      * @psalm-return static<T>
  95.      */
  96.     public function setUseOutputWalkers($useOutputWalkers)
  97.     {
  98.         $this->useOutputWalkers $useOutputWalkers;
  99.         return $this;
  100.     }
  101.     /**
  102.      * {@inheritdoc}
  103.      */
  104.     public function count()
  105.     {
  106.         if ($this->count === null) {
  107.             try {
  108.                 $this->count = (int) array_sum(array_map('current'$this->getCountQuery()->getScalarResult()));
  109.             } catch (NoResultException $e) {
  110.                 $this->count 0;
  111.             }
  112.         }
  113.         return $this->count;
  114.     }
  115.     /**
  116.      * {@inheritdoc}
  117.      *
  118.      * @psalm-return ArrayIterator<array-key, T>
  119.      */
  120.     public function getIterator()
  121.     {
  122.         $offset $this->query->getFirstResult();
  123.         $length $this->query->getMaxResults();
  124.         if ($this->fetchJoinCollection && $length !== null) {
  125.             $subQuery $this->cloneQuery($this->query);
  126.             if ($this->useOutputWalker($subQuery)) {
  127.                 $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKERLimitSubqueryOutputWalker::class);
  128.             } else {
  129.                 $this->appendTreeWalker($subQueryLimitSubqueryWalker::class);
  130.                 $this->unbindUnusedQueryParams($subQuery);
  131.             }
  132.             $subQuery->setFirstResult($offset)->setMaxResults($length);
  133.             $foundIdRows $subQuery->getScalarResult();
  134.             // don't do this for an empty id array
  135.             if ($foundIdRows === []) {
  136.                 return new ArrayIterator([]);
  137.             }
  138.             $whereInQuery $this->cloneQuery($this->query);
  139.             $ids          array_map('current'$foundIdRows);
  140.             $this->appendTreeWalker($whereInQueryWhereInWalker::class);
  141.             $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNTcount($ids));
  142.             $whereInQuery->setFirstResult(null)->setMaxResults(null);
  143.             $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS$ids);
  144.             $whereInQuery->setCacheable($this->query->isCacheable());
  145.             $whereInQuery->expireQueryCache();
  146.             $result $whereInQuery->getResult($this->query->getHydrationMode());
  147.         } else {
  148.             $result $this->cloneQuery($this->query)
  149.                 ->setMaxResults($length)
  150.                 ->setFirstResult($offset)
  151.                 ->setCacheable($this->query->isCacheable())
  152.                 ->getResult($this->query->getHydrationMode());
  153.         }
  154.         return new ArrayIterator($result);
  155.     }
  156.     private function cloneQuery(Query $query): Query
  157.     {
  158.         $cloneQuery = clone $query;
  159.         $cloneQuery->setParameters(clone $query->getParameters());
  160.         $cloneQuery->setCacheable(false);
  161.         foreach ($query->getHints() as $name => $value) {
  162.             $cloneQuery->setHint($name$value);
  163.         }
  164.         return $cloneQuery;
  165.     }
  166.     /**
  167.      * Determines whether to use an output walker for the query.
  168.      */
  169.     private function useOutputWalker(Query $query): bool
  170.     {
  171.         if ($this->useOutputWalkers === null) {
  172.             return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false;
  173.         }
  174.         return $this->useOutputWalkers;
  175.     }
  176.     /**
  177.      * Appends a custom tree walker to the tree walkers hint.
  178.      *
  179.      * @psalm-param class-string $walkerClass
  180.      */
  181.     private function appendTreeWalker(Query $querystring $walkerClass): void
  182.     {
  183.         $hints $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
  184.         if ($hints === false) {
  185.             $hints = [];
  186.         }
  187.         $hints[] = $walkerClass;
  188.         $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS$hints);
  189.     }
  190.     /**
  191.      * Returns Query prepared to count.
  192.      */
  193.     private function getCountQuery(): Query
  194.     {
  195.         $countQuery $this->cloneQuery($this->query);
  196.         if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
  197.             $countQuery->setHint(CountWalker::HINT_DISTINCTtrue);
  198.         }
  199.         if ($this->useOutputWalker($countQuery)) {
  200.             $platform $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
  201.             $rsm = new ResultSetMapping();
  202.             $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
  203.             $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKERCountOutputWalker::class);
  204.             $countQuery->setResultSetMapping($rsm);
  205.         } else {
  206.             $this->appendTreeWalker($countQueryCountWalker::class);
  207.             $this->unbindUnusedQueryParams($countQuery);
  208.         }
  209.         $countQuery->setFirstResult(null)->setMaxResults(null);
  210.         return $countQuery;
  211.     }
  212.     private function unbindUnusedQueryParams(Query $query): void
  213.     {
  214.         $parser            = new Parser($query);
  215.         $parameterMappings $parser->parse()->getParameterMappings();
  216.         /** @var Collection|Parameter[] $parameters */
  217.         $parameters $query->getParameters();
  218.         foreach ($parameters as $key => $parameter) {
  219.             $parameterName $parameter->getName();
  220.             if (! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName$parameterMappings))) {
  221.                 unset($parameters[$key]);
  222.             }
  223.         }
  224.         $query->setParameters($parameters);
  225.     }
  226. }