Completed
Pull Request — master (#375)
by Dalibor
05:23
created

NextrasDataSource   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 335
Duplicated Lines 1.49 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 14
dl 5
loc 335
rs 8.8
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getCount() 0 4 1
A getData() 0 7 2
A filterOne() 0 10 2
A applyFilterDate() 0 14 2
B applyFilterDateRange() 0 22 4
A applyFilterRange() 0 21 4
B applyFilterText() 5 33 5
A applyFilterMultiSelect() 0 18 2
A applyFilterSelect() 0 4 1
A limit() 0 6 1
B sort() 0 31 5
A prepareColumn() 0 7 2
A addAggregationColumn() 0 4 1
A getAggregationData() 0 23 3

How to fix   Duplicated Code   

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:

1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid\DataSource;
10
11
use Nextras\Dbal\SqlProcessor;
12
use Ublaboo\DataGrid\Filter;
13
use Nextras\Orm\Mapper\Dbal\DbalCollection;
14
use Nette\Utils\Strings;
15
use Ublaboo\DataGrid\Utils\Sorting;
16
use Nextras\Orm\Collection\ICollection;
17
use Ublaboo\DataGrid\Utils\ArraysHelper;
18
19
class NextrasDataSource extends FilterableDataSource implements IDataSource
20
{
21
22
	/**
23
	 * @var DbalCollection
24
	 */
25
	protected $data_source;
26
27
	/**
28
	 * @var array
29
	 */
30
	protected $data = [];
31
32
	/**
33
	 * @var array
34
	 */
35
	protected $aggregations = [];
36
37
	/**
38
	 * @var string
39
	 */
40
	protected $primary_key;
41
42
43
	/**
44
	 * @param ICollection  $data_source
45
	 * @param string       $primary_key
46
	 */
47
	public function __construct(ICollection $data_source, $primary_key)
48
	{
49
		$this->data_source = $data_source;
0 ignored issues
show
Documentation Bug introduced by
$data_source is of type object<Nextras\Orm\Collection\ICollection>, but the property $data_source was declared to be of type object<Nextras\Orm\Mapper\Dbal\DbalCollection>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
50
		$this->primary_key = $primary_key;
51
	}
52
53
54
	/********************************************************************************
55
	 *                          IDataSource implementation                          *
56
	 ********************************************************************************/
57
58
59
	/**
60
	 * Get count of data
61
	 * @return int
62
	 */
63
	public function getCount()
64
	{
65
		return $this->data_source->countStored();
66
	}
67
68
	/**
69
	 * Get the data
70
	 * @return array
71
	 */
72
	public function getData()
73
	{
74
		/**
75
		 * Paginator is better if the query uses ManyToMany associations
76
		 */
77
		return $this->data ?: $this->data_source->fetchAll();
78
	}
79
80
81
	/**
82
	 * Filter data - get one row
83
	 * @param array $condition
84
	 * @return static
85
	 */
86
	public function filterOne(array $condition)
87
	{
88
		$cond = [];
89
		foreach ($condition as $key => $value) {
90
			$cond[$this->prepareColumn($key)] = $value;
91
		}
92
		$this->data_source = $this->data_source->findBy($cond);
93
94
		return $this;
95
	}
96
97
98
	/**
99
	 * Filter by date
100
	 * @param  Filter\FilterDate $filter
101
	 * @return static
102
	 */
103
	public function applyFilterDate(Filter\FilterDate $filter)
104
	{
105
		foreach ($filter->getCondition() as $column => $value) {
106
			$date = \DateTime::createFromFormat($filter->getPhpFormat(), $value);
107
			$date_end = clone $date;
108
109
			$this->data_source = $this->data_source->findBy([
110
				$this->prepareColumn($column) . '>=' => $date->setTime(0, 0, 0),
111
				$this->prepareColumn($column) . '<=' => $date_end->setTime(23, 59, 59)
112
			]);
113
		}
114
115
		return $this;
116
	}
117
118
119
	/**
120
	 * Filter by date range
121
	 * @param  Filter\FilterDateRange $filter
122
	 * @return void
123
	 */
124
	public function applyFilterDateRange(Filter\FilterDateRange $filter)
125
	{
126
		$conditions = $filter->getCondition();
127
128
		$value_from = $conditions[$filter->getColumn()]['from'];
129
		$value_to = $conditions[$filter->getColumn()]['to'];
130
131
		$dataCondition = [];
132
		if ($value_from) {
133
			$date_from = \DateTime::createFromFormat($filter->getPhpFormat(), $value_from);
134
			$dataCondition[$this->prepareColumn($filter->getColumn()) . '>='] = $date_from->setTime(0, 0, 0);
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...Source::prepareColumn() 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...
135
		}
136
137
		if ($value_to) {
138
			$date_to = \DateTime::createFromFormat($filter->getPhpFormat(), $value_to);
139
			$dataCondition[$this->prepareColumn($filter->getColumn()) . '<='] = $date_to->setTime(23, 59, 59);
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...Source::prepareColumn() 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...
140
		}
141
142
		if (!empty($dataCondition)) {
143
			$this->data_source = $this->data_source->findBy($dataCondition);
144
		}
145
	}
146
147
148
	/**
149
	 * Filter by range
150
	 * @param  Filter\FilterRange $filter
151
	 * @return void
152
	 */
153
	public function applyFilterRange(Filter\FilterRange $filter)
154
	{
155
		$conditions = $filter->getCondition();
156
157
		$value_from = $conditions[$filter->getColumn()]['from'];
158
		$value_to = $conditions[$filter->getColumn()]['to'];
159
160
		$dataCondition = [];
161
162
		if ($value_from) {
163
			$dataCondition[$this->prepareColumn($filter->getColumn()) . '>='] = $value_from;
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...Source::prepareColumn() 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...
164
		}
165
166
		if ($value_to) {
167
			$dataCondition[$this->prepareColumn($filter->getColumn()) . '<='] = $value_to;
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...Source::prepareColumn() 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...
168
		}
169
170
		if (!empty($dataCondition)) {
171
			$this->data_source = $this->data_source->findBy($dataCondition);
172
		}
173
	}
174
175
176
	/**
177
	 * Filter by keyword
178
	 * @param  Filter\FilterText $filter
179
	 * @return void
180
	 */
181
	public function applyFilterText(Filter\FilterText $filter)
182
	{
183
		$condition = $filter->getCondition();
184
		$expr = '(';
185
		$params = [];
186
187
		foreach ($condition as $column => $value) {
188
			if ($filter->isExactSearch()) {
189
				$expr .= "%column = %s OR ";
190
				$params[] = $column;
191
				$params[] = "$value";
192
				continue;
193
			}
194
195 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...
196
				$words = [$value];
197
			} else {
198
				$words = explode(' ', $value);
199
			}
200
201
			foreach ($words as $word) {
202
				$expr .= "%column LIKE %s OR ";
203
				$params[] = $column;
204
				$params[] = "%$word%";
205
			}
206
		}
207
208
		$expr = preg_replace('/ OR $/', ')', $expr);
209
210
		array_unshift($params, $expr);
211
212
		call_user_func_array([$this->data_source->getQueryBuilder(), 'andWhere'], $params);
213
	}
214
215
216
	/**
217
	 * Filter by multi select value
218
	 * @param  Filter\FilterMultiSelect $filter
219
	 * @return void
220
	 */
221
	public function applyFilterMultiSelect(Filter\FilterMultiSelect $filter)
222
	{
223
		$condition = $filter->getCondition();
224
		$values = $condition[$filter->getColumn()];
225
		$expr = '(';
226
227
		foreach ($values as $value) {
228
			$expr .= "%column = %any OR ";
229
			$params[] = $filter->getColumn();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
230
			$params[] = "$value";
231
		}
232
233
		$expr = preg_replace('/ OR $/', ')', $expr);
234
235
		array_unshift($params, $expr);
236
237
		call_user_func_array([$this->data_source->getQueryBuilder(), 'andWhere'], $params);
238
	}
239
240
241
	/**
242
	 * Filter by select value
243
	 * @param  Filter\FilterSelect $filter
244
	 * @return void
245
	 */
246
	public function applyFilterSelect(Filter\FilterSelect $filter)
247
	{
248
		$this->data_source = $this->data_source->findBy([$this->prepareColumn($filter->getColumn()) => $filter->getValue()]);
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...Source::prepareColumn() 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...
249
	}
250
251
252
	/**
253
	 * Apply limit and offset on data
254
	 * @param int $offset
255
	 * @param int $limit
256
	 * @return static
257
	 */
258
	public function limit($offset, $limit)
259
	{
260
		$this->data_source = $this->data_source->limitBy($limit, $offset);
261
262
		return $this;
263
	}
264
265
266
	/**
267
	 * Sort data
268
	 * @param  Sorting $sorting
269
	 * @return static
270
	 */
271
	public function sort(Sorting $sorting)
272
	{
273
		if (is_callable($sorting->getSortCallback())) {
274
			call_user_func(
275
				$sorting->getSortCallback(),
276
				$this->data_source,
277
				$sorting->getSort()
278
			);
279
280
			return $this;
281
		}
282
283
		$sort = $sorting->getSort();
284
285
		if (!empty($sort)) {
286
			foreach ($sort as $column => $order) {
287
				$this->data_source = $this->data_source->orderBy($column, $order);
288
			}
289
		} else {
290
			/**
291
			 * Has the statement already a order by clause?
292
			 */
293
			$order = $this->data_source->getQueryBuilder()->getClause('order');
294
295
			if (ArraysHelper::testEmpty($order)) {
296
				$this->data_source = $this->data_source->orderBy($this->primary_key);
297
			}
298
		}
299
300
		return $this;
301
	}
302
303
	/**
304
	 * Adjust column from DataGrid 'foreignKey.column' to Nextras 'this->foreignKey->column'
305
	 * @param string $column
306
	 * @return string
307
	 */
308
	private function prepareColumn($column)
309
	{
310
	if (Strings::contains($column, '.')) {
311
		return 'this->' . str_replace('.', '->', $column);
312
	}
313
	return $column;
314
	}
315
316
	/**
317
	 * @param string $aggregation_type
318
	 * @param string $column
319
	 * @return mixed
320
	 */
321
	public function addAggregationColumn($aggregation_type, $column)
322
	{
323
		$this->aggregations[$column] = $aggregation_type;
324
	}
325
326
	/**
327
	 * get aggregation row
328
	 * @return array
329
	 */
330
	public function getAggregationData()
331
	{
332
		if (count($this->aggregations) == 0) {
333
			return;
334
		}
335
		$sql = 'SELECT ';
336
		foreach ($this->aggregations as $column => $aggregation_type) {
337
			$sql .= $aggregation_type . '(' . $column . ') as ' . $column . ',';
338
		}
339
340
		$sql = rtrim($sql, ',') . ' FROM ( ' . $this->data_source->getQueryBuilder()->getQuerySql() . ' ) as data';
341
342
		$params = $this->data_source->getQueryBuilder()->getQueryParameters();
343
		array_unshift($params, $sql);
344
		$sqlPreprocessor = new SqlProcessor($this->data_source->getQueryBuilder()->driver);
0 ignored issues
show
Bug introduced by
The property driver does not seem to exist in Nextras\Dbal\QueryBuilder\QueryBuilder.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
345
		$sql = $sqlPreprocessor->process($params);
346
347
348
		$result = $this->data_source->getQueryBuilder()->driver->query($sql)->fetch()->toArray();
349
350
351
		return $result;
352
	}
353
}
354