Completed
Pull Request — master (#447)
by Tomáš
02:53
created

DoctrineDataSource   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 368
Duplicated Lines 20.92 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 57.82%

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 14
dl 77
loc 368
ccs 85
cts 147
cp 0.5782
rs 8.3157
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getQuery() 0 4 1
A checkAliases() 0 13 3
A getCount() 0 4 1
A getData() 0 10 1
C filter() 15 35 11
A filterOne() 6 13 2
A applyFilterDate() 0 16 2
B applyFilterDateRange() 16 26 3
A applyFilterRange() 0 18 3
B applyFilterText() 5 28 5
A applyFilterMultiSelect() 0 10 1
A applyFilterSelect() 6 11 2
A limit() 0 6 1
B sort() 29 29 5
A getPlaceholder() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DoctrineDataSource 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 DoctrineDataSource, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Jakub Kontra <[email protected]>
6
 * @author      Pavel Janda <[email protected]>
7
 * @package     Ublaboo
8
 */
9
10
namespace Ublaboo\DataGrid\DataSource;
11
12
use Doctrine\ORM\QueryBuilder;
13
use Doctrine\ORM\Tools\Pagination\Paginator;
14
use Nette\Utils\Callback;
15
use Nette\Utils\Strings;
16
use Ublaboo\DataGrid\Filter;
17
use Ublaboo\DataGrid\Utils\DateTimeHelper;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
20
/**
21
 * @method void onDataLoaded(array $result)
22
 */
23 1
class DoctrineDataSource extends FilterableDataSource implements IDataSource
24
{
25
26
	/**
27
	 * Event called when datagrid data is loaded.
28
	 * @var callable[]
29
	 */
30
	public $onDataLoaded;
31
32
	/**
33
	 * @var QueryBuilder
34
	 */
35
	protected $data_source;
36
37
	/**
38
	 * @var string
39
	 */
40
	protected $primary_key;
41
42
	/**
43
	 * @var string
44
	 */
45
	protected $root_alias;
46
47
	/**
48
	 * @var int
49
	 */
50
	protected $placeholder;
51
52
53
	/**
54
	 * @param QueryBuilder $data_source
55
	 * @param string       $primary_key
56
	 */
57
	public function __construct(QueryBuilder $data_source, $primary_key)
58
	{
59 1
		$this->placeholder = count($data_source->getParameters());
60 1
		$this->data_source = $data_source;
61 1
		$this->primary_key = $primary_key;
62 1
	}
63
64
65
	/**
66
	 * @return \Doctrine\ORM\Query
67
	*/
68
	public function getQuery()
69
	{
70 1
		return $this->data_source->getQuery();
71
	}
72 1
73
74
	/**
75
	 * @param  string  $column
76
	 * @return string
77
	 */
78
	private function checkAliases($column)
79
	{
80 1
		if (Strings::contains($column, ".")) {
81
			return $column;
82
		}
83
84 1
		if (!isset($this->root_alias)) {
85 1
			$this->root_alias = $this->data_source->getRootAliases();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->data_source->getRootAliases() of type array is incompatible with the declared type string of property $root_alias.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

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

Loading history...
86 1
			$this->root_alias = current($this->root_alias);
87 1
		}
88
89 1
		return $this->root_alias.'.'.$column;
90
	}
91
92
93
	/********************************************************************************
94
	 *                          IDataSource implementation                          *
95
	 ********************************************************************************/
96
97
98
	/**
99
	 * Get count of data
100
	 * @return int
101
	 */
102
	public function getCount()
103
	{
104 1
		return (new Paginator($this->getQuery()))->count();
105
	}
106
107
108
	/**
109
	 * Get the data
110
	 * @return array
111
	 */
112
	public function getData()
113
	{
114 1
		$iterator = (new Paginator($this->getQuery()))->getIterator();
115
116 1
		$data = iterator_to_array($iterator);
117
118 1
		$this->onDataLoaded($data);
119
120 1
		return $data;
121
	}
122
123
124
	/**
125
	 * Filter data
126
	 * @param array $filters
127
	 * @return static
128
	 */
129
	public function filter(array $filters)
130
	{
131 1
		$withConditionCallback = [];
132
133 1
		foreach ($filters as $filter) {
134 1
			if ($filter->isValueSet()) {
135 1
				if ($filter->hasConditionCallback()) {
136
					$withConditionCallback[] = $filter;
137 View Code Duplication
				} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138 1
					if ($filter instanceof Filter\FilterText) {
139 1
						$this->applyFilterText($filter);
140 1
					} else if ($filter instanceof Filter\FilterMultiSelect) {
141
						$this->applyFilterMultiSelect($filter);
142 1
					} else if ($filter instanceof Filter\FilterSelect) {
143
						$this->applyFilterSelect($filter);
144 1
					} else if ($filter instanceof Filter\FilterDate) {
145
						$this->applyFilterDate($filter);
146 1
					} else if ($filter instanceof Filter\FilterDateRange) {
147
						$this->applyFilterDateRange($filter);
148 1
					} else if ($filter instanceof Filter\FilterRange) {
149 1
						$this->applyFilterRange($filter);
150 1
					}
151
				}
152 1
			}
153 1
		}
154
155 1
		foreach ($withConditionCallback as $filter) {
156
			Callback::invokeArgs(
157
				$filter->getConditionCallback(),
158
				[$this->data_source, $filter->getValue()]
159
			);
160 1
		}
161
162 1
		return $this;
163
	}
164
165
166
	/**
167
	 * Filter data - get one row
168
	 * @param  array  $condition
169
	 * @return static
170
	 */
171
	public function filterOne(array $condition)
172
	{
173 1
		$p = $this->getPlaceholder();
174
175 1 View Code Duplication
		foreach ($condition as $column => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176 1
			$c = $this->checkAliases($column);
177
178 1
			$this->data_source->andWhere("$c = ?$p")
179 1
				->setParameter($p, $value);
180 1
		}
181
182 1
		return $this;
183
	}
184
185
186
	/**
187
	 * Filter by date
188
	 * @param Filter\FilterDate  $filter
189
	 */
190
	public function applyFilterDate(Filter\FilterDate $filter)
191
	{
192
		$p1 = $this->getPlaceholder();
193
		$p2 = $this->getPlaceholder();
194
195
		foreach ($filter->getCondition() as $column => $value) {
196
			$date = DateTimeHelper::tryConvertToDateTime($value, [$filter->getPhpFormat()]);
197
			$c = $this->checkAliases($column);
198
199
			$this->data_source
200
				->andWhere("$c >= ?$p1")
201
				->andWhere("$c <= ?$p2")
202
				->setParameter($p1, $date->format('Y-m-d 00:00:00'))
203
				->setParameter($p2, $date->format('Y-m-d 23:59:59'));
204
		}
205
	}
206
207
208
	/**
209
	 * Filter by date range
210
	 * @param Filter\FilterDateRange  $filter
211
	 */
212
	public function applyFilterDateRange(Filter\FilterDateRange $filter)
213
	{
214
		$conditions = $filter->getCondition();
215
		$c = $this->checkAliases($filter->getColumn());
0 ignored issues
show
Bug introduced by
It seems like $filter->getColumn() targeting Ublaboo\DataGrid\Filter\Filter::getColumn() can also be of type array; however, Ublaboo\DataGrid\DataSou...aSource::checkAliases() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
216
217
		$value_from = $conditions[$filter->getColumn()]['from'];
218
		$value_to   = $conditions[$filter->getColumn()]['to'];
219
220 View Code Duplication
		if ($value_from) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
			$date_from = DateTimeHelper::tryConvertToDate($value_from, [$filter->getPhpFormat()]);
222
			$date_from->setTime(0, 0, 0);
223
224
			$p = $this->getPlaceholder();
225
226
			$this->data_source->andWhere("$c >= ?$p")->setParameter($p, $date_from->format('Y-m-d H:i:s'));
227
		}
228
229 View Code Duplication
		if ($value_to) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230
			$date_to = DateTimeHelper::tryConvertToDate($value_to, [$filter->getPhpFormat()]);
231
			$date_to->setTime(23, 59, 59);
232
233
			$p = $this->getPlaceholder();
234
235
			$this->data_source->andWhere("$c <= ?$p")->setParameter($p, $date_to->format('Y-m-d H:i:s'));
236
		}
237
	}
238
239
240
	/**
241
	 * Filter by range
242
	 * @param Filter\FilterRange  $filter
243
	 */
244
	public function applyFilterRange(Filter\FilterRange $filter)
245
	{
246 1
		$conditions = $filter->getCondition();
247 1
		$c = $this->checkAliases($filter->getColumn());
0 ignored issues
show
Bug introduced by
It seems like $filter->getColumn() targeting Ublaboo\DataGrid\Filter\Filter::getColumn() can also be of type array; however, Ublaboo\DataGrid\DataSou...aSource::checkAliases() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
248
249 1
		$value_from = $conditions[$filter->getColumn()]['from'];
250 1
		$value_to   = $conditions[$filter->getColumn()]['to'];
251
252 1
		if ($value_from) {
253 1
			$p = $this->getPlaceholder();
254 1
			$this->data_source->andWhere("$c >= ?$p")->setParameter($p, $value_from);
255 1
		}
256
257 1
		if ($value_to) {
258 1
			$p = $this->getPlaceholder();
259 1
			$this->data_source->andWhere("$c <= ?$p")->setParameter($p, $value_to);
260 1
		}
261 1
	}
262
263
264
	/**
265
	 * Filter by keyword
266
	 * @param Filter\FilterText  $filter
267
	 */
268
	public function applyFilterText(Filter\FilterText $filter)
269
	{
270 1
		$condition = $filter->getCondition();
271 1
		$exprs = [];
272
273 1
		foreach ($condition as $column => $value) {
274 1
			$c = $this->checkAliases($column);
275
276 1
			if($filter->isExactSearch()){
277 1
				$exprs[] = $this->data_source->expr()->eq($c, $this->data_source->expr()->literal($value));
278 1
				continue;
279
			}
280
281 1 View Code Duplication
			if ($filter->hasSplitWordsSearch() === FALSE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282 1
				$words = [$value];
283 1
			} else {
284 1
				$words = explode(' ', $value);
285
			}
286
287 1
			foreach ($words as $word) {
288 1
				$exprs[] = $this->data_source->expr()->like($c, $this->data_source->expr()->literal("%$word%"));
289 1
			}
290 1
		}
291
292 1
		$or = call_user_func_array([$this->data_source->expr(), 'orX'], $exprs);
293
294 1
		$this->data_source->andWhere($or);
295 1
	}
296
297
298
	/**
299
	 * Filter by multi select value
300
	 * @param Filter\FilterMultiSelect  $filter
301
	 */
302
	public function applyFilterMultiSelect(Filter\FilterMultiSelect $filter)
303
	{
304
		$c = $this->checkAliases($filter->getColumn());
0 ignored issues
show
Bug introduced by
It seems like $filter->getColumn() targeting Ublaboo\DataGrid\Filter\Filter::getColumn() can also be of type array; however, Ublaboo\DataGrid\DataSou...aSource::checkAliases() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
305
		$p = $this->getPlaceholder();
306
307
		$values = $filter->getCondition()[$filter->getColumn()];
308
		$expr = $this->data_source->expr()->in($c, '?'.$p);
309
310
		$this->data_source->andWhere($expr)->setParameter($p, $values);
311
	}
312
313
314
	/**
315
	 * Filter by select value
316
	 * @param Filter\FilterSelect  $filter
317
	 */
318
	public function applyFilterSelect(Filter\FilterSelect $filter)
319
	{
320
		$p = $this->getPlaceholder();
321
322 View Code Duplication
		foreach ($filter->getCondition() as $column => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
323
			$c = $this->checkAliases($column);
324
325
			$this->data_source->andWhere("$c = ?$p")
326
				->setParameter($p, $value);
327
		}
328
	}
329
330
331
	/**
332
	 * Apply limit and offset on data
333
	 * @param  int  $offset
334
	 * @param  int  $limit
335
	 * @return static
336
	 */
337
	public function limit($offset, $limit)
338
	{
339 1
		$this->data_source->setFirstResult($offset)->setMaxResults($limit);
340
341 1
		return $this;
342
	}
343
344
345
	/**
346
	 * Sort data
347
	 * @param  Sorting $sorting
348
	 * @return static
349
	 */
350 View Code Duplication
	public function sort(Sorting $sorting)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
351
	{
352 1
		if (is_callable($sorting->getSortCallback())) {
353
			call_user_func(
354
				$sorting->getSortCallback(),
355
				$this->data_source,
356
				$sorting->getSort()
357
			);
358
359
			return $this;
360
		}
361
362 1
		$sort = $sorting->getSort();
363
364 1
		if (!empty($sort)) {
365 1
			foreach ($sort as $column => $order) {
366 1
				$this->data_source->addOrderBy($this->checkAliases($column), $order);
367 1
			}
368 1
		} else {
369
			/**
370
			 * Has the statement already a order by clause?
371
			 */
372
			if (!$this->data_source->getDQLPart('orderBy')) {
373
				$this->data_source->orderBy($this->checkAliases($this->primary_key));
374
			}
375
		}
376
377 1
		return $this;
378
	}
379
380
381
	/**
382
	 * Get unique int value for each instance class (self)
383
	 * @return int
384
	 */
385
	public function getPlaceholder()
386
	{
387 1
		return $this->placeholder++;
388
	}
389
390
}
391