Completed
Pull Request — master (#6354)
by COLE
15:21
created

Paginator::count()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
dl 0
loc 12
c 0
b 0
f 0
ccs 5
cts 6
cp 0.8333
rs 9.4285
cc 3
eloc 7
nc 3
nop 0
crap 3.0416
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 105
    public function __construct($query, $fetchJoinCollection = true)
64
    {
65 105
        if ($query instanceof QueryBuilder) {
66
            $query = $query->getQuery();
67
        }
68
69 105
        $this->query = $query;
70 105
        $this->fetchJoinCollection = (bool) $fetchJoinCollection;
71 105
    }
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 94
    public function setUseOutputWalkers($useOutputWalkers)
111
    {
112 94
        $this->useOutputWalkers = $useOutputWalkers;
113
114 94
        return $this;
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 15
    public function count()
121
    {
122 15
        if ($this->count === null) {
123
            try {
124 15
                $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 2
            } catch (NoResultException $e) {
126
                $this->count = 0;
127
            }
128
        }
129
130 13
        return $this->count;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 92
    public function getIterator()
137
    {
138 92
        $offset = $this->query->getFirstResult();
139 92
        $length = $this->query->getMaxResults();
140
141 92
        if ($this->fetchJoinCollection) {
142 65
            $subQuery = $this->cloneQuery($this->query);
143
144 65
            if ($this->useOutputWalker($subQuery)) {
145 41
                $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, LimitSubqueryOutputWalker::class);
146
            } else {
147 24
                $this->appendTreeWalker($subQuery, LimitSubqueryWalker::class);
148
            }
149
150 65
            $subQuery->setFirstResult($offset)->setMaxResults($length);
151
152 65
            $ids = array_map('current', $subQuery->getScalarResult());
153
154 62
            $whereInQuery = $this->cloneQuery($this->query);
155
            // don't do this for an empty id array
156 62
            if (count($ids) === 0) {
157
                return new \ArrayIterator([]);
158
            }
159
160 62
            $this->appendTreeWalker($whereInQuery, WhereInWalker::class);
161 62
            $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
162 62
            $whereInQuery->setFirstResult(null)->setMaxResults(null);
163 62
            $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
164 62
            $whereInQuery->setCacheable($this->query->isCacheable());
165
166 62
            $result = $whereInQuery->getResult($this->query->getHydrationMode());
167
        } else {
168 27
            $result = $this->cloneQuery($this->query)
169 27
                ->setMaxResults($length)
170 27
                ->setFirstResult($offset)
171 27
                ->setCacheable($this->query->isCacheable())
172 27
                ->getResult($this->query->getHydrationMode())
173
            ;
174
        }
175
176 27
        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 105
    private function cloneQuery(Query $query)
187
    {
188
        /* @var $cloneQuery Query */
189 105
        $cloneQuery = clone $query;
190
191 105
        $cloneQuery->setParameters(clone $query->getParameters());
192 105
        $cloneQuery->setCacheable(false);
193
194 105
        foreach ($query->getHints() as $name => $value) {
195 3
            $cloneQuery->setHint($name, $value);
196
        }
197
198 105
        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 79
    private function useOutputWalker(Query $query)
209
    {
210 79
        if ($this->useOutputWalkers === null) {
211 11
            return (bool) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false;
212
        }
213
214 68
        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 72
    private function appendTreeWalker(Query $query, $walkerClass)
224
    {
225 72
        $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
226
227 72
        if ($hints === false) {
228 71
            $hints = [];
229
        }
230
231 72
        $hints[] = $walkerClass;
232 72
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints);
233 72
    }
234
235
    /**
236
     * Returns Query prepared to count.
237
     *
238
     * @return Query
239
     */
240 15
    private function getCountQuery()
241
    {
242
        /* @var $countQuery Query */
243 15
        $countQuery = $this->cloneQuery($this->query);
244
245 15
        if ( ! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
246 15
            $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
247
        }
248
249 15
        if ($this->useOutputWalker($countQuery)) {
250 9
            $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
251
252 9
            $rsm = new ResultSetMapping();
253 9
            $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
254
255 9
            $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class);
256 9
            $countQuery->setResultSetMapping($rsm);
257
        } else {
258 7
            $this->appendTreeWalker($countQuery, CountWalker::class);
259
        }
260
261 15
        $countQuery->setFirstResult(null)->setMaxResults(null);
262
263 15
        $parser            = new Parser($countQuery);
264 15
        $parameterMappings = $parser->parse()->getParameterMappings();
265
        /* @var $parameters \Doctrine\Common\Collections\Collection|\Doctrine\ORM\Query\Parameter[] */
266 13
        $parameters        = $countQuery->getParameters();
267
268 13
        foreach ($parameters as $key => $parameter) {
269 1
            $parameterName = $parameter->getName();
270
271 1
            if ( ! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) {
272 1
                unset($parameters[$key]);
273
            }
274
        }
275
276 13
        $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 13
        return $countQuery;
279
    }
280
}
281