Doctrine   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 90%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 2
dl 0
loc 281
ccs 90
cts 100
cp 0.9
rs 9.1199
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 3
A setUseOutputWalkers() 0 5 1
A setFetchJoinCollection() 0 5 1
A getQuery() 0 4 1
A _getQuery() 0 9 2
A getQb() 0 4 1
A getFilterMapping() 0 4 1
A getSortMapping() 0 4 1
B makeWhere() 0 37 8
A getRand() 0 9 2
A getCount() 0 7 1
A getData() 0 18 5
A filter() 0 6 2
A limit() 0 5 1
A sort() 0 10 3
B suggest() 0 35 8

How to fix   Complexity   

Complex Class

Complex classes like Doctrine often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Doctrine, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the Grido (https://github.com/o5/grido)
5
 *
6
 * Copyright (c) 2011 Petr Bugyík (http://petr.bugyik.cz)
7
 *
8
 * For the full copyright and license information, please view
9
 * the file LICENSE.md that was distributed with this source code.
10
 */
11
12
namespace Grido\DataSources;
13
14
use Grido\Exception;
15
use Grido\Components\Filters\Condition;
16
17
use Nette\Utils\Strings;
18
use Nette\Utils\Random;
19
use Doctrine\ORM\Tools\Pagination\Paginator;
20
21
/**
22
 * Doctrine data source.
23
 *
24
 * @package     Grido
25
 * @subpackage  DataSources
26
 * @author      Martin Jantosovic <[email protected]>
27
 * @author      Petr Bugyík
28
 *
29
 * @property-read \Doctrine\ORM\QueryBuilder $qb
30
 * @property-read array $filterMapping
31
 * @property-read array $sortMapping
32
 * @property-read int $count
33
 * @property-read array $data
34
 */
35 1
class Doctrine implements IDataSource
36
{
37
    use \Nette\SmartObject;
38
39
    /** @var \Doctrine\ORM\QueryBuilder */
40
    protected $qb;
41
42
    /** @var array Map column to the query builder */
43
    protected $filterMapping;
44
45
    /** @var array Map column to the query builder */
46
    protected $sortMapping;
47
48
    /** @var bool use OutputWalker in Doctrine Paginator */
49
    protected $useOutputWalkers;
50
51
    /** @var bool fetch join collection in Doctrine Paginator */
52
    protected $fetchJoinCollection = TRUE;
53
54
    /** @var array */
55
    protected $rand;
56
57
    /**
58
     * If $sortMapping is not set and $filterMapping is set,
59
     * $filterMapping will be used also as $sortMapping.
60
     * @param \Doctrine\ORM\QueryBuilder $qb
61
     * @param array $filterMapping Maps columns to the DQL columns
62
     * @param array $sortMapping Maps columns to the DQL columns
63
     */
64 1
    public function __construct(\Doctrine\ORM\QueryBuilder $qb, array $filterMapping = NULL, array $sortMapping = NULL)
65 1
    {
66 1
        $this->qb = $qb;
67
        $this->filterMapping = $filterMapping;
0 ignored issues
show
Documentation Bug introduced by
It seems like $filterMapping can be null. However, the property $filterMapping is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
68 1
        $this->sortMapping = $sortMapping;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sortMapping can be null. However, the property $sortMapping is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
69 1
70 1
        if (!$this->sortMapping && $this->filterMapping) {
71 1
            $this->sortMapping = $this->filterMapping;
72
        }
73
    }
74
75
    /**
76
     * @param bool $useOutputWalkers
77
     * @return \Grido\DataSources\Doctrine
78
     */
79
    public function setUseOutputWalkers($useOutputWalkers)
80
    {
81
        $this->useOutputWalkers = $useOutputWalkers;
82
        return $this;
83
    }
84
85
    /**
86
     * @param bool $fetchJoinCollection
87
     * @return \Grido\DataSources\Doctrine
88
     */
89
    public function setFetchJoinCollection($fetchJoinCollection)
90
    {
91
        $this->fetchJoinCollection = $fetchJoinCollection;
92
        return $this;
93
    }
94
95
    /**
96
     * @return \Doctrine\ORM\Query
97
     */
98 1
    public function getQuery()
99
    {
100
        return $this->_getQuery($this->qb);
101
    }
102
103
    /**
104
     * Workaround for https://github.com/Kdyby/DoctrineCache/issues/23
105
     *
106
     * @param \Doctrine\ORM\QueryBuilder $qb
107
     * @return \Doctrine\ORM\Query
108
     */
109
    private function _getQuery(\Doctrine\ORM\QueryBuilder $qb)
110
    {
111
        $query = $qb->getQuery();
112
        $ttl = $query->getQueryCacheLifetime();
113
        if (!\is_int($ttl)) {
114
            $query->setQueryCacheLifetime(0);
115
        }
116
        return $query;
117
    }
118
119
    /**
120
     * @return \Doctrine\ORM\QueryBuilder
121
     */
122
    public function getQb()
123
    {
124
        return $this->qb;
125
    }
126
127
    /**
128
     * @return array|NULL
129
     */
130
    public function getFilterMapping()
131
    {
132 1
        return $this->filterMapping;
133 1
    }
134
135 1
    /**
136 1
     * @return array|NULL
137
     */
138
    public function getSortMapping()
139 1
    {
140 1
        return $this->sortMapping;
141 1
    }
142 1
143 1
    /**
144 1
     * @param Condition $condition
145 1
     * @param \Doctrine\ORM\QueryBuilder $qb
146 1
     */
147 1
    protected function makeWhere(Condition $condition, \Doctrine\ORM\QueryBuilder $qb = NULL)
148 1
    {
149
        $qb = $qb === NULL
150 1
            ? $this->qb
151 1
            : $qb;
152
153 1
        if ($condition->callback) {
154 1
            return call_user_func_array($condition->callback, [$condition->value, $qb]);
155 1
        }
156 1
157 1
        $columns = $condition->column;
158 1
        foreach ($columns as $key => $column) {
159
            if (!Condition::isOperator($column)) {
160 1
                $columns[$key] = (isset($this->filterMapping[$column])
161
                    ? $this->filterMapping[$column]
162 1
                    : (Strings::contains($column, ".")
163 1
                        ? $column
164 1
                        : current($this->qb->getRootAliases()) . '.' . $column));
165 1
            }
166
        }
167
168
        $condition->setColumn($columns);
169
        list($where) = $condition->__toArray(NULL, NULL, FALSE);
170
171
        $rand = $this->getRand();
172
        $where = preg_replace_callback('/\?/', function() use ($rand) {
173 1
            static $i = -1;
174 1
            $i++;
175
            return ":$rand{$i}";
176 1
        }, $where);
177 1
178
        $qb->andWhere($where);
179
180
        foreach ($condition->getValueForColumn() as $i => $val) {
181
            $qb->setParameter("$rand{$i}", $val);
182
        }
183
    }
184
185
    /**
186
     * @return string
187 1
     */
188 1
    protected function getRand()
189
    {
190 1
        do {
191
            $rand = Random::generate(4, 'a-z');
192
        } while (isset($this->rand[$rand]));
193
194
        $this->rand[$rand] = $rand;
195
        return $rand;
196
    }
197
198
    /*********************************** interface IDataSource ************************************/
199
200
    /**
201 1
     * @return int
202
     */
203
    public function getCount()
204 1
    {
205 1
        $paginator = new Paginator($this->getQuery(), $this->fetchJoinCollection);
206 1
        $paginator->setUseOutputWalkers($this->useOutputWalkers);
207
208 1
        return $paginator->count();
209
    }
210 1
211 1
    /**
212
     * It is possible to use query builder with additional columns.
213 1
     * In this case, only item at index [0] is returned, because
214
     * it should be an entity object.
215 1
     * @return array
216
     */
217
    public function getData()
218
    {
219
        $data = [];
220
221
        // Paginator is better if the query uses ManyToMany associations
222
        $result = $this->qb->getMaxResults() !== NULL || $this->qb->getFirstResult() !== NULL
223
            ? new Paginator($this->getQuery())
224 1
            : $this->getQuery()->getResult();
225 1
226 1
        foreach ($result as $item) {
227 1
            // Return only entity itself
228
            $data[] = is_array($item)
229
                ? $item[0]
230
                : $item;
231
        }
232
233
        return $data;
234
    }
235
236 1
    /**
237 1
     * Sets filter.
238 1
     * @param array $conditions
239
     */
240
    public function filter(array $conditions)
241
    {
242
        foreach ($conditions as $condition) {
243
            $this->makeWhere($condition);
244
        }
245
    }
246 1
247 1
    /**
248 1
     * Sets offset and limit.
249 1
     * @param int $offset
250
     * @param int $limit
251 1
     */
252 1
    public function limit($offset, $limit)
253 1
    {
254
        $this->qb->setFirstResult($offset)
255
            ->setMaxResults($limit);
256
    }
257
258
    /**
259
     * Sets sorting.
260
     * @param array $sorting
261
     */
262
    public function sort(array $sorting)
263
    {
264 1
        foreach ($sorting as $key => $value) {
265 1
            $column = isset($this->sortMapping[$key])
266
                ? $this->sortMapping[$key]
267 1
                : current($this->qb->getRootAliases()) . '.' . $key;
268 1
269 1
            $this->qb->addOrderBy($column, $value);
270 1
        }
271
    }
272 1
273 1
    /**
274
     * @param mixed $column
275 1
     * @param array $conditions
276 1
     * @param int $limit
277 1
     * @return array
278
     * @throws Exception
279 1
     */
280 1
    public function suggest($column, array $conditions, $limit)
281 1
    {
282 1
        $qb = clone $this->qb;
283 1
        $qb->setMaxResults($limit);
284 1
285 1
        if (is_string($column)) {
286 1
            $mapping = isset($this->filterMapping[$column])
287
                ? $this->filterMapping[$column]
288
                : current($qb->getRootAliases()) . '.' . $column;
289
290
            $qb->select($mapping)->distinct()->orderBy($mapping);
291 1
        }
292 1
293
        foreach ($conditions as $condition) {
294 1
            $this->makeWhere($condition, $qb);
295 1
        }
296
297
        $items = [];
298 1
        $data = $this->_getQuery($qb)->getScalarResult();
299
        foreach ($data as $row) {
300
            if (is_string($column)) {
301
                $value = (string) current($row);
302
            } elseif (is_callable($column)) {
303
                $value = (string) $column($row);
304
            } else {
305
                $type = gettype($column);
306
                throw new Exception("Column of suggestion must be string or callback, $type given.");
307
            }
308
309
            $items[$value] = \Latte\Runtime\Filters::escapeHtml($value);
310
        }
311
312
        is_callable($column) && sort($items);
313
        return array_values($items);
314
    }
315
}
316