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

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 Doctrine\ORM\Query\Parser;
  21. use Doctrine\ORM\QueryBuilder;
  22. use Doctrine\ORM\Query;
  23. use Doctrine\ORM\Query\ResultSetMapping;
  24. use Doctrine\ORM\NoResultException;
  25. /**
  26.  * The paginator can handle various complex scenarios with DQL.
  27.  *
  28.  * @author Pablo Díez <pablodip@gmail.com>
  29.  * @author Benjamin Eberlei <kontakt@beberlei.de>
  30.  * @license New BSD
  31.  */
  32. class Paginator implements \Countable, \IteratorAggregate
  33. {
  34.     /**
  35.      * @var Query
  36.      */
  37.     private $query;
  38.     /**
  39.      * @var bool
  40.      */
  41.     private $fetchJoinCollection;
  42.     /**
  43.      * @var bool|null
  44.      */
  45.     private $useOutputWalkers;
  46.     /**
  47.      * @var int
  48.      */
  49.     private $count;
  50.     /**
  51.      * Constructor.
  52.      *
  53.      * @param Query|QueryBuilder $query               A Doctrine ORM query or query builder.
  54.      * @param boolean            $fetchJoinCollection Whether the query joins a collection (true by default).
  55.      */
  56.     public function __construct($query$fetchJoinCollection true)
  57.     {
  58.         if ($query instanceof QueryBuilder) {
  59.             $query $query->getQuery();
  60.         }
  61.         $this->query $query;
  62.         $this->fetchJoinCollection = (bool) $fetchJoinCollection;
  63.     }
  64.     /**
  65.      * Returns the query.
  66.      *
  67.      * @return Query
  68.      */
  69.     public function getQuery()
  70.     {
  71.         return $this->query;
  72.     }
  73.     /**
  74.      * Returns whether the query joins a collection.
  75.      *
  76.      * @return boolean Whether the query joins a collection.
  77.      */
  78.     public function getFetchJoinCollection()
  79.     {
  80.         return $this->fetchJoinCollection;
  81.     }
  82.     /**
  83.      * Returns whether the paginator will use an output walker.
  84.      *
  85.      * @return bool|null
  86.      */
  87.     public function getUseOutputWalkers()
  88.     {
  89.         return $this->useOutputWalkers;
  90.     }
  91.     /**
  92.      * Sets whether the paginator will use an output walker.
  93.      *
  94.      * @param bool|null $useOutputWalkers
  95.      *
  96.      * @return $this
  97.      */
  98.     public function setUseOutputWalkers($useOutputWalkers)
  99.     {
  100.         $this->useOutputWalkers $useOutputWalkers;
  101.         return $this;
  102.     }
  103.     /**
  104.      * {@inheritdoc}
  105.      */
  106.     public function count()
  107.     {
  108.         if ($this->count === null) {
  109.             try {
  110.                 $this->count array_sum(array_map('current'$this->getCountQuery()->getScalarResult()));
  111.             } catch (NoResultException $e) {
  112.                 $this->count 0;
  113.             }
  114.         }
  115.         return $this->count;
  116.     }
  117.     /**
  118.      * {@inheritdoc}
  119.      */
  120.     public function getIterator()
  121.     {
  122.         $offset $this->query->getFirstResult();
  123.         $length $this->query->getMaxResults();
  124.         if ($this->fetchJoinCollection) {
  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.             $ids array_map('current'$subQuery->getScalarResult());
  134.             $whereInQuery $this->cloneQuery($this->query);
  135.             // don't do this for an empty id array
  136.             if (count($ids) === 0) {
  137.                 return new \ArrayIterator([]);
  138.             }
  139.             $this->appendTreeWalker($whereInQueryWhereInWalker::class);
  140.             $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNTcount($ids));
  141.             $whereInQuery->setFirstResult(null)->setMaxResults(null);
  142.             $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS$ids);
  143.             $whereInQuery->setCacheable($this->query->isCacheable());
  144.             $result $whereInQuery->getResult($this->query->getHydrationMode());
  145.         } else {
  146.             $result $this->cloneQuery($this->query)
  147.                 ->setMaxResults($length)
  148.                 ->setFirstResult($offset)
  149.                 ->setCacheable($this->query->isCacheable())
  150.                 ->getResult($this->query->getHydrationMode())
  151.             ;
  152.         }
  153.         return new \ArrayIterator($result);
  154.     }
  155.     /**
  156.      * Clones a query.
  157.      *
  158.      * @param Query $query The query.
  159.      *
  160.      * @return Query The cloned query.
  161.      */
  162.     private function cloneQuery(Query $query)
  163.     {
  164.         /* @var $cloneQuery Query */
  165.         $cloneQuery = clone $query;
  166.         $cloneQuery->setParameters(clone $query->getParameters());
  167.         $cloneQuery->setCacheable(false);
  168.         foreach ($query->getHints() as $name => $value) {
  169.             $cloneQuery->setHint($name$value);
  170.         }
  171.         return $cloneQuery;
  172.     }
  173.     /**
  174.      * Determines whether to use an output walker for the query.
  175.      *
  176.      * @param Query $query The query.
  177.      *
  178.      * @return bool
  179.      */
  180.     private function useOutputWalker(Query $query)
  181.     {
  182.         if ($this->useOutputWalkers === null) {
  183.             return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false;
  184.         }
  185.         return $this->useOutputWalkers;
  186.     }
  187.     /**
  188.      * Appends a custom tree walker to the tree walkers hint.
  189.      *
  190.      * @param Query  $query
  191.      * @param string $walkerClass
  192.      */
  193.     private function appendTreeWalker(Query $query$walkerClass)
  194.     {
  195.         $hints $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
  196.         if ($hints === false) {
  197.             $hints = [];
  198.         }
  199.         $hints[] = $walkerClass;
  200.         $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS$hints);
  201.     }
  202.     /**
  203.      * Returns Query prepared to count.
  204.      *
  205.      * @return Query
  206.      */
  207.     private function getCountQuery()
  208.     {
  209.         /* @var $countQuery Query */
  210.         $countQuery $this->cloneQuery($this->query);
  211.         if ( ! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
  212.             $countQuery->setHint(CountWalker::HINT_DISTINCTtrue);
  213.         }
  214.         if ($this->useOutputWalker($countQuery)) {
  215.             $platform $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
  216.             $rsm = new ResultSetMapping();
  217.             $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
  218.             $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKERCountOutputWalker::class);
  219.             $countQuery->setResultSetMapping($rsm);
  220.         } else {
  221.             $this->appendTreeWalker($countQueryCountWalker::class);
  222.             $this->unbindUnusedQueryParams($countQuery);
  223.         }
  224.         $countQuery->setFirstResult(null)->setMaxResults(null);
  225.         return $countQuery;
  226.     }
  227.     private function unbindUnusedQueryParams(Query $query): void
  228.     {
  229.         $parser            = new Parser($query);
  230.         $parameterMappings $parser->parse()->getParameterMappings();
  231.         /* @var $parameters \Doctrine\Common\Collections\Collection|\Doctrine\ORM\Query\Parameter[] */
  232.         $parameters        $query->getParameters();
  233.         foreach ($parameters as $key => $parameter) {
  234.             $parameterName $parameter->getName();
  235.             if ( ! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName$parameterMappings))) {
  236.                 unset($parameters[$key]);
  237.             }
  238.         }
  239.         $query->setParameters($parameters);
  240.     }
  241. }