Failed Conditions
Pull Request — master (#1922)
by chihiro
36:51
created

Paginator::setUseOutputWalkers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
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 Eccube\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 21
    public function __construct($query, $fetchJoinCollection = true)
64
    {
65 21
        if ($query instanceof QueryBuilder) {
66
            $query = $query->getQuery();
67
        }
68
69 21
        $this->query = $query;
70 21
        $this->fetchJoinCollection = (Boolean) $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 21
    public function setUseOutputWalkers($useOutputWalkers)
111
    {
112 21
        $this->useOutputWalkers = $useOutputWalkers;
113 21
        return $this;
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 21
    public function count()
120
    {
121 21
        if ($this->count === null) {
122
            try {
123 21
                $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...
124
            } catch(NoResultException $e) {
125
                $this->count = 0;
126
            }
127
        }
128
129 21
        return $this->count;
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135 21
    public function getIterator()
136
    {
137 21
        $offset = $this->query->getFirstResult();
138 21
        $length = $this->query->getMaxResults();
139
140 21
        if ($this->fetchJoinCollection) {
141 21
            $subQuery = $this->cloneQuery($this->query);
142
143 21
            if ($this->useOutputWalker($subQuery)) {
144 10
                $subQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker');
145
            } else {
146 11
                $this->appendTreeWalker($subQuery, 'Eccube\Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker');
147
            }
148
149 21
            $subQuery->setFirstResult($offset)->setMaxResults($length);
150
151 21
            $ids = array_map('current', $subQuery->getScalarResult());
152
153 21
            $whereInQuery = $this->cloneQuery($this->query);
154
            // don't do this for an empty id array
155 21
            if (count($ids) == 0) {
156 9
                return new \ArrayIterator(array());
157
            }
158
159 14
            $this->appendTreeWalker($whereInQuery, 'Eccube\Doctrine\ORM\Tools\Pagination\WhereInWalker');
160 14
            $whereInQuery->setHint(WhereInWalker::HINT_PAGINATOR_ID_COUNT, count($ids));
161 14
            $whereInQuery->setFirstResult(null)->setMaxResults(null);
162 14
            $whereInQuery->setParameter(WhereInWalker::PAGINATOR_ID_ALIAS, $ids);
163
            //$whereInQuery->setCacheable($this->query->isCacheable());
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% 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...
164
165 14
            $result = $whereInQuery->getResult($this->query->getHydrationMode());
166
        } else {
167
            $result = $this->cloneQuery($this->query)
168
                ->setMaxResults($length)
169
                ->setFirstResult($offset)
170
                //->setCacheable($this->query->isCacheable())
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% 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...
171
                ->getResult($this->query->getHydrationMode())
172
            ;
173
        }
174
175 14
        return new \ArrayIterator($result);
176
    }
177
178
    /**
179
     * Clones a query.
180
     *
181
     * @param Query $query The query.
182
     *
183
     * @return Query The cloned query.
184
     */
185 21
    private function cloneQuery(Query $query)
186
    {
187
        /* @var $cloneQuery Query */
188 21
        $cloneQuery = clone $query;
189
190 21
        $cloneQuery->setParameters(clone $query->getParameters());
191
        //$cloneQuery->setCacheable(false);
0 ignored issues
show
Unused Code Comprehensibility introduced by
86% 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...
192
193 21
        foreach ($query->getHints() as $name => $value) {
194 21
            $cloneQuery->setHint($name, $value);
195
        }
196
197 21
        return $cloneQuery;
198
    }
199
200
    /**
201
     * Determines whether to use an output walker for the query.
202
     *
203
     * @param Query $query The query.
204
     *
205
     * @return bool
206
     */
207 21
    private function useOutputWalker(Query $query)
208
    {
209 21
        if ($this->useOutputWalkers === null) {
210
            return (Boolean) $query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) == false;
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
211
        }
212
213 21
        return $this->useOutputWalkers;
214
    }
215
216
    /**
217
     * Appends a custom tree walker to the tree walkers hint.
218
     *
219
     * @param Query $query
220
     * @param string $walkerClass
221
     */
222 16
    private function appendTreeWalker(Query $query, $walkerClass)
223
    {
224 16
        $hints = $query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
225
226 16
        if ($hints === false) {
227 16
            $hints = array();
228
        }
229
230 16
        $hints[] = $walkerClass;
231 16
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints);
232
    }
233
234
    /**
235
     * Returns Query prepared to count.
236
     *
237
     * @return Query
238
     */
239 21
    private function getCountQuery()
240
    {
241
        /* @var $countQuery Query */
242 21
        $countQuery = $this->cloneQuery($this->query);
243
244 21
        if ( ! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
245
            $countQuery->setHint(CountWalker::HINT_DISTINCT, true);
246
        }
247
248 21
        if ($this->useOutputWalker($countQuery)) {
249 10
            $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win
250
251 10
            $rsm = new ResultSetMapping();
252 10
            $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
253
254 10
            $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker');
255 10
            $countQuery->setResultSetMapping($rsm);
256
        } else {
257 11
            $this->appendTreeWalker($countQuery, 'Eccube\Doctrine\ORM\Tools\Pagination\CountWalker');
258
        }
259
260 21
        $countQuery->setFirstResult(null)->setMaxResults(null);
261
262 21
        $parser            = new Parser($countQuery);
263 21
        $parameterMappings = $parser->parse()->getParameterMappings();
264
        /* @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...
265 21
        $parameters        = $countQuery->getParameters();
266
267 21
        foreach ($parameters as $key => $parameter) {
268 15
            $parameterName = $parameter->getName();
269
270 15
            if( ! (isset($parameterMappings[$parameterName]) || array_key_exists($parameterName, $parameterMappings))) {
271 21
                unset($parameters[$key]);
272
            }
273
        }
274
275 21
        $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...
276
277 21
        return $countQuery;
278
    }
279
}
280