Completed
Pull Request — 2.7 (#7376)
by Guilherme
09:20
created

Paginator::unbindUnusedQueryParams()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 3
nop 1
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 4
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 108
    public function __construct($query, $fetchJoinCollection = true)
64
    {
65 108
        if ($query instanceof QueryBuilder) {
66
            $query = $query->getQuery();
67
        }
68
69 108
        $this->query = $query;
70 108
        $this->fetchJoinCollection = (bool) $fetchJoinCollection;
71 108
    }
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 97
    public function setUseOutputWalkers($useOutputWalkers)
111
    {
112 97
        $this->useOutputWalkers = $useOutputWalkers;
113
114 97
        return $this;
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 18
    public function count()
121
    {
122 18
        if ($this->count === null) {
123
            try {
124 18
                $this->count = array_sum(array_map('current', $this->getCountQuery()->getScalarResult()));
125 2
            } catch (NoResultException $e) {
126
                $this->count = 0;
127
            }
128
        }
129
130 16
        return $this->count;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 95
    public function getIterator()
137
    {
138 95
        $offset = $this->query->getFirstResult();
139 95
        $length = $this->query->getMaxResults();
140
141 95
        if ($this->fetchJoinCollection) {
142 68
            $subQuery = $this->cloneQuery($this->query);
143
144 68
            if ($this->useOutputWalker($subQuery)) {
145 41
                $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class);
146
            } else {
147 27
                $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class);
148 27
                $this->unbindUnusedQueryParams($subQuery);
149
            }
150
151 65
            $subQuery->setFirstResult($offset)->setMaxResults($length);
152
153 65
            $ids = array_map('current', $subQuery->getScalarResult());
154
155 65
            $whereInQuery = $this->cloneQuery($this->query);
156
            // don't do this for an empty id array
157 65
            if (count($ids) === 0) {
158 2
                return new \ArrayIterator([]);
159
            }
160
161 64
            $this->appendTreeWalker($whereInQuery, WhereInWalker::class);
162 64
            $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
163 64
            $whereInQuery->setFirstResult(null)->setMaxResults(null);
164 64
            $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
165 64
            $whereInQuery->setCacheable($this->query->isCacheable());
166
167 64
            $result = $whereInQuery->getResult($this->query->getHydrationMode());
168
        } else {
169 27
            $result = $this->cloneQuery($this->query)
170 27
                ->setMaxResults($length)
171 27
                ->setFirstResult($offset)
172 27
                ->setCacheable($this->query->isCacheable())
173 27
                ->getResult($this->query->getHydrationMode())
174
            ;
175
        }
176
177 90
        return new \ArrayIterator($result);
178
    }
179
180
    /**
181
     * Clones a query.
182
     *
183
     * @param Query $query The query.
184
     *
185
     * @return Query The cloned query.
186
     */
187 108
    private function cloneQuery(Query $query)
188
    {
189
        /* @var $cloneQuery Query */
190 108
        $cloneQuery = clone $query;
191
192 108
        $cloneQuery->setParameters(clone $query->getParameters());
193 108
        $cloneQuery->setCacheable(false);
194
195 108
        foreach ($query->getHints() as $name => $value) {
196 3
            $cloneQuery->setHint($name, $value);
197
        }
198
199 108
        return $cloneQuery;
200
    }
201
202
    /**
203
     * Determines whether to use an output walker for the query.
204
     *
205
     * @param Query $query The query.
206
     *
207
     * @return bool
208
     */
209 82
    private function useOutputWalker(Query $query)
210
    {
211 82
        if ($this->useOutputWalkers === null) {
212 11
            return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false;
213
        }
214
215 71
        return $this->useOutputWalkers;
216
    }
217
218
    /**
219
     * Appends a custom tree walker to the tree walkers hint.
220
     *
221
     * @param Query  $query
222
     * @param string $walkerClass
223
     */
224 75
    private function appendTreeWalker(Query $query, $walkerClass)
225
    {
226 75
        $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
227
228 75
        if ($hints === false) {
229 74
            $hints = [];
230
        }
231
232 75
        $hints[] = $walkerClass;
233 75
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints);
234 75
    }
235
236
    /**
237
     * Returns Query prepared to count.
238
     *
239
     * @return Query
240
     */
241 18
    private function getCountQuery()
242
    {
243
        /* @var $countQuery Query */
244 18
        $countQuery = $this->cloneQuery($this->query);
245
246 18
        if ( ! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
247 18
            $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
248
        }
249
250 18
        if ($this->useOutputWalker($countQuery)) {
251 9
            $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
252
253 9
            $rsm = new ResultSetMapping();
254 9
            $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
255
256 9
            $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class);
257 9
            $countQuery->setResultSetMapping($rsm);
258
        } else {
259 10
            $this->appendTreeWalker($countQuery, CountWalker::class);
260 10
            $this->unbindUnusedQueryParams($countQuery);
261
        }
262
263 16
        $countQuery->setFirstResult(null)->setMaxResults(null);
264
265 16
        return $countQuery;
266
    }
267
268 34
    private function unbindUnusedQueryParams(Query $query): void
269
    {
270 34
        $parser            = new Parser($query);
271 34
        $parameterMappings = $parser->parse()->getParameterMappings();
272
        /* @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...
273 29
        $parameters        = $query->getParameters();
274
275 29
        foreach ($parameters as $key => $parameter) {
276 4
            $parameterName = $parameter->getName();
277
278 4
            if ( ! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) {
279 4
                unset($parameters[$key]);
280
            }
281
        }
282
283 29
        $query->setParameters($parameters);
284 29
    }
285
}
286