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.
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>.
20 namespace Doctrine\ORM\Tools\Pagination;
22 use Doctrine\ORM\QueryBuilder,
24 Doctrine\ORM\Query\ResultSetMapping,
25 Doctrine\ORM\NoResultException;
30 * The paginator can handle various complex scenarios with DQL.
32 * @author Pablo Díez <pablodip@gmail.com>
33 * @author Benjamin Eberlei <kontakt@beberlei.de>
36 class Paginator implements \Countable, \IteratorAggregate
46 private $fetchJoinCollection;
51 private $useOutputWalkers;
61 * @param Query|QueryBuilder $query A Doctrine ORM query or query builder.
62 * @param Boolean $fetchJoinCollection Whether the query joins a collection (true by default).
64 public function __construct($query, $fetchJoinCollection = true)
66 if ($query instanceof QueryBuilder) {
67 $query = $query->getQuery();
70 $this->query = $query;
71 $this->fetchJoinCollection = (Boolean) $fetchJoinCollection;
79 public function getQuery()
85 * Returns whether the query joins a collection.
87 * @return Boolean Whether the query joins a collection.
89 public function getFetchJoinCollection()
91 return $this->fetchJoinCollection;
95 * Returns whether the paginator will use an output walker
99 public function getUseOutputWalkers()
101 return $this->useOutputWalkers;
105 * Set whether the paginator will use an output walker
107 * @param bool|null $useOutputWalkers
110 public function setUseOutputWalkers($useOutputWalkers)
112 $this->useOutputWalkers = $useOutputWalkers;
119 public function count()
121 if ($this->count === null) {
122 /* @var $countQuery Query */
123 $countQuery = $this->cloneQuery($this->query);
125 if ( ! $countQuery->getHint(CountWalker::HINT_DISTINCT)) {
126 $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
129 if ($this->useOutputWalker($countQuery)) {
130 $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
132 $rsm = new ResultSetMapping();
133 $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
135 $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker');
136 $countQuery->setResultSetMapping($rsm);
138 $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\CountWalker'));
141 $countQuery->setFirstResult(null)->setMaxResults(null);
144 $data = $countQuery->getScalarResult();
145 $data = array_map('current', $data);
146 $this->count = array_sum($data);
147 } catch(NoResultException $e) {
157 public function getIterator()
159 $offset = $this->query->getFirstResult();
160 $length = $this->query->getMaxResults();
162 if ($this->fetchJoinCollection) {
163 $subQuery = $this->cloneQuery($this->query);
165 if ($this->useOutputWalker($subQuery)) {
166 $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker');
168 $subQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker'));
171 $subQuery->setFirstResult($offset)->setMaxResults($length);
173 $ids = array_map('current', $subQuery->getScalarResult());
175 $whereInQuery = $this->cloneQuery($this->query);
176 // don't do this for an empty id array
177 if (count($ids) == 0) {
178 return new \ArrayIterator(array());
181 $namespace = WhereInWalker::PAGINATOR_ID_ALIAS;
183 $whereInQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('Doctrine\ORM\Tools\Pagination\WhereInWalker'));
184 $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
185 $whereInQuery->setFirstResult(null)->setMaxResults(null);
186 foreach ($ids as $i => $id) {
188 $whereInQuery->setParameter("{$namespace}_{$i}", $id);
191 $result = $whereInQuery->getResult($this->query->getHydrationMode());
193 $result = $this->cloneQuery($this->query)
194 ->setMaxResults($length)
195 ->setFirstResult($offset)
196 ->getResult($this->query->getHydrationMode())
199 return new \ArrayIterator($result);
205 * @param Query $query The query.
207 * @return Query The cloned query.
209 private function cloneQuery(Query $query)
211 /* @var $cloneQuery Query */
212 $cloneQuery = clone $query;
214 $cloneQuery->setParameters(clone $query->getParameters());
216 foreach ($query->getHints() as $name => $value) {
217 $cloneQuery->setHint($name, $value);
224 * Determine whether to use an output walker for the query
226 * @param Query $query The query.
230 private function useOutputWalker(Query $query)
232 if ($this->useOutputWalkers === null) {
233 return (Boolean) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) == false;
236 return $this->useOutputWalkers;