Passed
Pull Request — 2.6 (#7328)
by
unknown
09:27
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 109
    public function __construct($query, $fetchJoinCollection = true)
64
    {
65 109
        if ($query instanceof QueryBuilder) {
66
            $query = $query->getQuery();
67
        }
68
69 109
        $this->query = $query;
70 109
        $this->fetchJoinCollection = (bool) $fetchJoinCollection;
71 109
    }
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 98
    public function setUseOutputWalkers($useOutputWalkers)
111
    {
112 98
        $this->useOutputWalkers = $useOutputWalkers;
113
114 98
        return $this;
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 16
    public function count()
121
    {
122 16
        if ($this->count === null) {
123
            try {
124 16
                $this->count = array_sum(array_map('current', $this->getCountQuery()->getScalarResult()));
125 2
            } catch (NoResultException $e) {
126
                $this->count = 0;
127
            }
128
        }
129
130 14
        return $this->count;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 96
    public function getIterator()
137
    {
138 96
        $offset = $this->query->getFirstResult();
139 96
        $length = $this->query->getMaxResults();
140
141 96
        if ($this->fetchJoinCollection) {
142 67
            $subQuery = $this->cloneQuery($this->query);
143
144 67
            if ($this->useOutputWalker($subQuery)) {
145 42
                $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class);
146
            } else {
147 25
                $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class);
148
            }
149
150 67
            $this->unbindUnusedQueryParams($subQuery);
151
152 64
            $subQuery->setFirstResult($offset)->setMaxResults($length);
153
154 64
            $ids = array_map('current', $subQuery->getScalarResult());
155
156 64
            $whereInQuery = $this->cloneQuery($this->query);
157
            // don't do this for an empty id array
158 64
            if (count($ids) === 0) {
159 1
                return new \ArrayIterator([]);
160
            }
161
162 64
            $this->appendTreeWalker($whereInQuery, WhereInWalker::class);
163 64
            $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
164 64
            $whereInQuery->setFirstResult(null)->setMaxResults(null);
165 64
            $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
166 64
            $whereInQuery->setCacheable($this->query->isCacheable());
167
168 64
            $result = $whereInQuery->getResult($this->query->getHydrationMode());
169
        } else {
170 29
            $result = $this->cloneQuery($this->query)
171 29
                ->setMaxResults($length)
172 29
                ->setFirstResult($offset)
173 29
                ->setCacheable($this->query->isCacheable())
174 29
                ->getResult($this->query->getHydrationMode())
175
            ;
176
        }
177
178 93
        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 109
    private function cloneQuery(Query $query)
189
    {
190
        /* @var $cloneQuery Query */
191 109
        $cloneQuery = clone $query;
192
193 109
        $cloneQuery->setParameters(clone $query->getParameters());
194 109
        $cloneQuery->setCacheable(false);
195
196 109
        foreach ($query->getHints() as $name => $value) {
197 3
            $cloneQuery->setHint($name, $value);
198
        }
199
200 109
        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 81
    private function useOutputWalker(Query $query)
211
    {
212 81
        if ($this->useOutputWalkers === null) {
213 11
            return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false;
214
        }
215
216 70
        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 74
    private function appendTreeWalker(Query $query, $walkerClass)
226
    {
227 74
        $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
228
229 74
        if ($hints === false) {
230 73
            $hints = [];
231
        }
232
233 74
        $hints[] = $walkerClass;
234 74
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints);
235 74
    }
236
237
    /**
238
     * Returns Query prepared to count.
239
     *
240
     * @return Query
241
     */
242 16
    private function getCountQuery()
243
    {
244
        /* @var $countQuery Query */
245 16
        $countQuery = $this->cloneQuery($this->query);
246
247 16
        if ( ! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
248 16
            $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
249
        }
250
251 16
        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 8
            $this->appendTreeWalker($countQuery, CountWalker::class);
261
        }
262
263 16
        $countQuery->setFirstResult(null)->setMaxResults(null);
264
265 16
        $this->unbindUnusedQueryParams($countQuery);
266
267 14
        return $countQuery;
268
    }
269
270
    /**
271
     * @param Query $query
272
     */
273 81
    private function unbindUnusedQueryParams(Query $query)
274
    {
275 81
        $parser            = new Parser($query);
276 81
        $parameterMappings = $parser->parse()->getParameterMappings();
277
        /* @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...
278 76
        $parameters        = $query->getParameters();
279
280 76
        foreach ($parameters as $key => $parameter) {
281 3
            $parameterName = $parameter->getName();
282
283 3
            if ( ! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) {
284 3
                unset($parameters[$key]);
285
            }
286
        }
287
288 76
        $query->setParameters($parameters);
289 76
    }
290
}
291