Passed
Pull Request — master (#7222)
by
unknown
11:41
created

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