Completed
Pull Request — master (#6365)
by Daniel Tome
10:44
created

Paginator::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 8
cp 0
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 2
crap 6
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
    public function __construct($query, $fetchJoinCollection = true)
64
    {
65
        if ($query instanceof QueryBuilder) {
66
            $query = $query->getQuery();
67
        }
68
69
        $this->query = $query;
70
        $this->fetchJoinCollection = (bool) $fetchJoinCollection;
71
    }
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
    public function setUseOutputWalkers($useOutputWalkers)
111
    {
112
        $this->useOutputWalkers = $useOutputWalkers;
113
114
        return $this;
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function count()
121
    {
122
        if ($this->count === null) {
123
            try {
124
                $this->count = array_sum(array_map('current', $this->getCountQuery()->getScalarResult()));
0 ignored issues
show
Documentation Bug introduced by
It seems like array_sum(array_map('cur...()->getScalarResult())) can also be of type double. However, the property $count is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
125
            } catch (NoResultException $e) {
126
                $this->count = 0;
127
            }
128
        }
129
130
        return $this->count;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function getIterator()
137
    {
138
        $offset = $this->query->getFirstResult();
139
        $length = $this->query->getMaxResults();
140
141
        if ($this->fetchJoinCollection) {
142
            $subQuery = $this->cloneQuery($this->query);
143
144
            if ($this->useOutputWalker($subQuery)) {
145
                $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class);
146
            } else {
147
                $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class);
148
            }
149
150
            $subQuery->setFirstResult($offset)->setMaxResults($length);
151
152
            $ids = array_map('current', $subQuery->getScalarResult());
153
154
            $whereInQuery = $this->cloneQuery($this->query);
155
            // don't do this for an empty id array
156
            if (count($ids) === 0) {
157
                return new \ArrayIterator([]);
158
            }
159
160
            $this->appendTreeWalker($whereInQuery, WhereInWalker::class);
161
            $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
162
            $whereInQuery->setFirstResult(null)->setMaxResults(null);
163
            $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
164
            $whereInQuery->setCacheable($this->query->isCacheable());
165
166
            $result = $whereInQuery->getResult($this->query->getHydrationMode());
167
        } else {
168
            $result = $this->cloneQuery($this->query)
169
                ->setMaxResults($length)
170
                ->setFirstResult($offset)
171
                ->setCacheable($this->query->isCacheable())
172
                ->getResult($this->query->getHydrationMode())
173
            ;
174
        }
175
176
        return new \ArrayIterator($result);
177
    }
178
179
    /**
180
     * Clones a query.
181
     *
182
     * @param Query $query The query.
183
     *
184
     * @return Query The cloned query.
185
     */
186
    private function cloneQuery(Query $query)
187
    {
188
        /* @var $cloneQuery Query */
189
        $cloneQuery = clone $query;
190
191
        $cloneQuery->setParameters(clone $query->getParameters());
192
        $cloneQuery->setCacheable(false);
193
194
        foreach ($query->getHints() as $name => $value) {
195
            $cloneQuery->setHint($name, $value);
196
        }
197
198
        return $cloneQuery;
199
    }
200
201
    /**
202
     * Determines whether to use an output walker for the query.
203
     *
204
     * @param Query $query The query.
205
     *
206
     * @return bool
207
     */
208
    private function useOutputWalker(Query $query)
209
    {
210
        if ($this->useOutputWalkers === null) {
211
            return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false;
212
        }
213
214
        return $this->useOutputWalkers;
215
    }
216
217
    /**
218
     * Appends a custom tree walker to the tree walkers hint.
219
     *
220
     * @param Query  $query
221
     * @param string $walkerClass
222
     */
223
    private function appendTreeWalker(Query $query, $walkerClass)
224
    {
225
        $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
226
227
        if ($hints === false) {
228
            $hints = [];
229
        }
230
231
        $hints[] = $walkerClass;
232
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints);
233
    }
234
235
    /**
236
     * Returns Query prepared to count.
237
     *
238
     * @return Query
239
     */
240
    private function getCountQuery()
241
    {
242
        /* @var $countQuery Query */
243
        $countQuery = $this->cloneQuery($this->query);
244
245
        if ( ! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
246
            $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
247
        }
248
249
        if ($this->useOutputWalker($countQuery)) {
250
            $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
251
252
            $rsm = new ResultSetMapping();
253
            $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
254
255
            $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class);
256
            $countQuery->setResultSetMapping($rsm);
257
        } else {
258
            $this->appendTreeWalker($countQuery, CountWalker::class);
259
        }
260
261
        $countQuery->setFirstResult(null)->setMaxResults(null);
262
263
        $parser            = new Parser($countQuery);
264
        $parameterMappings = $parser->parse()->getParameterMappings();
265
        /* @var $parameters \Doctrine\Common\Collections\Collection|\Doctrine\ORM\Query\Parameter[] */
266
        $parameters        = $countQuery->getParameters();
267
268
        foreach ($parameters as $key => $parameter) {
269
            $parameterName = $parameter->getName();
270
271
            if ( ! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) {
272
                unset($parameters[$key]);
273
            }
274
        }
275
276
        $countQuery->setParameters($parameters);
0 ignored issues
show
Bug introduced by
It seems like $parameters can also be of type object<Doctrine\Common\Collections\Collection>; however, Doctrine\ORM\AbstractQuery::setParameters() does only seem to accept object<Doctrine\Common\C...\ArrayCollection>|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
277
278
        return $countQuery;
279
    }
280
}
281