Failed Conditions
Pull Request — master (#7222)
by
unknown
12:18
created

Paginator::lastPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Tools\Pagination;
6
7
use Doctrine\Common\Collections\Collection;
8
use Doctrine\DBAL\Types\Type;
9
use Doctrine\ORM\NoResultException;
10
use Doctrine\ORM\Query;
11
use Doctrine\ORM\Query\Parameter;
12
use Doctrine\ORM\Query\Parser;
13
use Doctrine\ORM\Query\ResultSetMapping;
14
use Doctrine\ORM\QueryBuilder;
15
use function array_key_exists;
16
use function array_map;
17
use function array_sum;
18
use function count;
19
20
/**
21
 * The paginator can handle various complex scenarios with DQL.
22
 */
23
class Paginator implements \Countable, \IteratorAggregate
24
{
25
    /** @var Query */
26
    private $query;
27
28
    /** @var bool */
29
    private $fetchJoinCollection;
30
31
    /** @var bool|null */
32
    private $useOutputWalkers;
33
34
    /** @var int */
35
    private $count;
36
37
    /**
38
     * @param Query|QueryBuilder $query               A Doctrine ORM query or query builder.
39
     * @param bool               $fetchJoinCollection Whether the query joins a collection (true by default).
40
     */
41 105
    public function __construct($query, $fetchJoinCollection = true)
42
    {
43 105
        if ($query instanceof QueryBuilder) {
44
            $query = $query->getQuery();
45
        }
46
47 105
        $this->query               = $query;
48 105
        $this->fetchJoinCollection = (bool) $fetchJoinCollection;
49 105
    }
50
51
    /**
52
     * Returns the query.
53
     *
54
     * @return Query
55
     */
56
    public function getQuery()
57
    {
58
        return $this->query;
59
    }
60
61
    /**
62
     * Returns whether the query joins a collection.
63
     *
64
     * @return bool Whether the query joins a collection.
65
     */
66
    public function getFetchJoinCollection()
67
    {
68
        return $this->fetchJoinCollection;
69
    }
70
71
    /**
72
     * Returns whether the paginator will use an output walker.
73
     *
74
     * @return bool|null
75
     */
76
    public function getUseOutputWalkers()
77
    {
78
        return $this->useOutputWalkers;
79
    }
80
81
    /**
82
     * Sets whether the paginator will use an output walker.
83
     *
84
     * @param bool|null $useOutputWalkers
85
     *
86
     * @return $this
87
     */
88 94
    public function setUseOutputWalkers($useOutputWalkers)
89
    {
90 94
        $this->useOutputWalkers = $useOutputWalkers;
91
92 94
        return $this;
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 16
    public function count()
99
    {
100 16
        if ($this->count === null) {
101
            try {
102 16
                $this->count = array_sum(array_map('current', $this->getCountQuery()->getScalarResult()));
103 2
            } catch (NoResultException $e) {
104
                $this->count = 0;
105
            }
106
        }
107
108 14
        return $this->count;
109
    }
110
    
111
    /**
112
     * Returns last page number
113
     *
114
     * @return int
115
     */
116
    public function lastPage()
117
    {
118
        return ceil($this->count() / $this->query->getMaxResults());
0 ignored issues
show
introduced by
Function ceil() should not be referenced via a fallback global name, but via a use statement.
Loading history...
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124 92
    public function getIterator()
125
    {
126 92
        $offset = $this->query->getFirstResult();
127 92
        $length = $this->query->getMaxResults();
128
129 92
        if ($this->fetchJoinCollection) {
130 65
            $subQuery = $this->cloneQuery($this->query);
131
132 65
            if ($this->useOutputWalker($subQuery)) {
133 41
                $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class);
134
            } else {
135 24
                $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class);
136
            }
137
138 65
            $subQuery->setFirstResult($offset)->setMaxResults($length);
139
140 65
            $ids = array_map('current', $subQuery->getScalarResult());
141
142 62
            $whereInQuery = $this->cloneQuery($this->query);
143
144
            // don't do this for an empty id array
145 62
            if (count($ids) === 0) {
146 1
                return new \ArrayIterator([]);
147
            }
148
149 62
            $this->appendTreeWalker($whereInQuery, WhereInWalker::class);
150
151 62
            $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
152 62
            $whereInQuery->setFirstResult(null)->setMaxResults(null);
153 62
            $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
154 62
            $whereInQuery->setCacheable($this->query->isCacheable());
155
156 62
            $result = $whereInQuery->getResult($this->query->getHydrationMode());
157
        } else {
158 27
            $result = $this->cloneQuery($this->query)
159 27
                ->setMaxResults($length)
160 27
                ->setFirstResult($offset)
161 27
                ->setCacheable($this->query->isCacheable())
162 27
                ->getResult($this->query->getHydrationMode())
163
            ;
164
        }
165
166 89
        return new \ArrayIterator($result);
167
    }
168
169
    /**
170
     * Clones a query.
171
     *
172
     * @param Query $query The query.
173
     *
174
     * @return Query The cloned query.
175
     */
176 105
    private function cloneQuery(Query $query)
177
    {
178
        /** @var Query $cloneQuery */
179 105
        $cloneQuery = clone $query;
180
181 105
        $cloneQuery->setParameters(clone $query->getParameters());
182 105
        $cloneQuery->setCacheable(false);
183
184 105
        foreach ($query->getHints() as $name => $value) {
185 3
            $cloneQuery->setHint($name, $value);
186
        }
187
188 105
        return $cloneQuery;
189
    }
190
191
    /**
192
     * Determines whether to use an output walker for the query.
193
     *
194
     * @param Query $query The query.
195
     *
196
     * @return bool
197
     */
198 79
    private function useOutputWalker(Query $query)
199
    {
200 79
        if ($this->useOutputWalkers === null) {
201 11
            return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false;
202
        }
203
204 68
        return $this->useOutputWalkers;
205
    }
206
207
    /**
208
     * Appends a custom tree walker to the tree walkers hint.
209
     *
210
     * @param string $walkerClass
211
     */
212 72
    private function appendTreeWalker(Query $query, $walkerClass)
213
    {
214 72
        $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
215
216 72
        if ($hints === false) {
217 71
            $hints = [];
218
        }
219
220 72
        $hints[] = $walkerClass;
221 72
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints);
222 72
    }
223
224
    /**
225
     * Returns Query prepared to count.
226
     *
227
     * @return Query
228
     */
229 16
    private function getCountQuery()
230
    {
231
        /** @var Query $countQuery */
232 16
        $countQuery = $this->cloneQuery($this->query);
233
234 16
        if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
235 16
            $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
236
        }
237
238 16
        if ($this->useOutputWalker($countQuery)) {
239 9
            $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
240
241 9
            $rsm = new ResultSetMapping();
242
243 9
            $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count', Type::getType('integer'));
244
245 9
            $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class);
246 9
            $countQuery->setResultSetMapping($rsm);
247
        } else {
248 8
            $this->appendTreeWalker($countQuery, CountWalker::class);
249
        }
250
251 16
        $countQuery->setFirstResult(null)->setMaxResults(null);
252
253 16
        $parser            = new Parser($countQuery);
254 16
        $parameterMappings = $parser->parse()->getParameterMappings();
255
        /** @var Collection|Parameter[] $parameters */
256 14
        $parameters = $countQuery->getParameters();
257
258 14
        foreach ($parameters as $key => $parameter) {
259 1
            $parameterName = $parameter->getName();
260
261 1
            if (! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) {
262 1
                unset($parameters[$key]);
263
            }
264
        }
265
266 14
        $countQuery->setParameters($parameters);
267
268 14
        return $countQuery;
269
    }
270
}
271