Passed
Pull Request — 2.6 (#7328)
by
unknown
07:42
created

Paginator::cloneQuery()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
275 75
        $parameters        = $query->getParameters();
276
277 75
        foreach ($parameters as $key => $parameter) {
278 2
            $parameterName = $parameter->getName();
279
280 2
            if ( ! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) {
281 2
                unset($parameters[$key]);
282
            }
283
        }
284
285 75
        $query->setParameters($parameters);
286 75
    }
287
}
288