Completed
Push — master ( bd9942...3aa75f )
by Pavel
03:01
created

ArrayDataSource   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 330
Duplicated Lines 17.27 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 7
Bugs 0 Features 3
Metric Value
wmc 53
c 7
b 0
f 3
lcom 1
cbo 9
dl 57
loc 330
rs 7.4757

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getCount() 0 4 1
A getData() 0 4 1
A filter() 0 19 4
A filterOne() 0 16 4
A limit() 0 6 1
D applyFilter() 5 29 10
A applyFilterMultiSelect() 0 7 1
B applyFilterRange() 10 19 7
C applyFilterDateRange() 42 53 11
B applyFilterDate() 0 27 4
C sort() 0 43 8

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 ArrayDataSource 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 ArrayDataSource, and based on these observations, apply Extract Interface, too.

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 Ublaboo\DataGrid\Filter\Filter;
12
use Ublaboo\DataGrid\Filter\FilterDate;
13
use Ublaboo\DataGrid\Filter\FilterMultiSelect;
14
use Ublaboo\DataGrid\Filter\FilterRange;
15
use Ublaboo\DataGrid\Filter\FilterDateRange;
16
use Nette\Utils\Callback;
17
use Nette\Utils\Strings;
18
use Ublaboo\DataGrid\Exception\DataGridException;
19
use Ublaboo\DataGrid\Exception\DataGridArrayDataSourceException;
20
use Ublaboo\DataGrid\Utils\DateTimeHelper;
21
use Ublaboo\DataGrid\Exception\DataGridDateTimeHelperException;
22
use Ublaboo\DataGrid\Utils\Sorting;
23
24
class ArrayDataSource implements IDataSource
25
{
26
27
	/**
28
	 * @var array
29
	 */
30
	protected $data = [];
31
32
33
	/**
34
	 * @param array $data_source
35
	 */
36
	public function __construct(array $data_source)
37
	{
38
		$this->data = $data_source;
39
	}
40
41
42
	/********************************************************************************
43
	 *                          IDataSource implementation                          *
44
	 ********************************************************************************/
45
46
47
	/**
48
	 * Get count of data
49
	 * @return int
50
	 */
51
	public function getCount()
52
	{
53
		return sizeof($this->data);
54
	}
55
56
57
	/**
58
	 * Get the data
59
	 * @return array
60
	 * @return array
61
	 */
62
	public function getData()
63
	{
64
		return $this->data;
65
	}
66
67
68
	/**
69
	 * Filter data
70
	 * @param Filter[] $filters
71
	 * @return static
72
	 */
73
	public function filter(array $filters)
74
	{
75
		foreach ($filters as $filter) {
76
			if ($filter->isValueSet()) {
77
				if ($filter->hasConditionCallback()) {
78
					$this->data = (array) call_user_func_array(
79
						$filter->getConditionCallback(),
80
						[$this->data, $filter->getValue()]
81
					);
82
				} else {
83
					$this->data = array_filter($this->data, function($row) use ($filter) {
84
						return $this->applyFilter($row, $filter);
85
					});
86
				}
87
			}
88
		}
89
		
90
		return $this;
91
	}
92
93
94
	/**
95
	 * Filter data - get one row
96
	 * @param array $condition
97
	 * @return ArrayDataSource
98
	 */
99
	public function filterOne(array $condition)
100
	{
101
		foreach ($this->data as $item) {
102
			foreach ($condition as $key => $value) {
103
				if ($item[$key] == $value) {
104
					$this->data = [$item];
105
106
					return $this;
107
				}
108
			}
109
		}
110
111
		$this->data = [];
112
113
		return $this;
114
	}
115
116
117
	/**
118
	 * Apply limit and offset on data
119
	 * @param int $offset
120
	 * @param int $limit
121
	 * @return static
122
	 */
123
	public function limit($offset, $limit)
124
	{
125
		$this->data = array_slice($this->data, $offset, $limit);
126
127
		return $this;
128
	}
129
130
131
	/**
132
	 * Apply fitler and tell whether row passes conditions or not
133
	 * @param  mixed  $row
134
	 * @param  Filter $filter
135
	 * @return mixed
136
	 */
137
	protected function applyFilter($row, Filter $filter)
138
	{
139
		if (is_array($row) || $row instanceof \Traversable) {
140
			if ($filter instanceof FilterDate) {
141
				return $this->applyFilterDate($row, $filter);
142
			} else if ($filter instanceof FilterMultiSelect) {
143
				return $this->applyFilterMultiSelect($row, $filter);
144 View Code Duplication
			} else if ($filter instanceof FilterDateRange) {
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...
145
				return $this->applyFilterDateRange($row, $filter);
146
			} else if ($filter instanceof FilterRange) {
147
				return $this->applyFilterRange($row, $filter);
148
			}
149
150
			$condition = $filter->getCondition();
151
152
			foreach ($condition as $column => $value) {
153
				$words = explode(' ', $value);
154
				$row_value = strtolower(Strings::toAscii($row[$column]));
155
156
				foreach ($words as $word) {
157
					if (FALSE !== strpos($row_value, strtolower(Strings::toAscii($value)))) {
158
						return $row;
159
					}
160
				}
161
			}
162
		}
163
164
		return FALSE;
165
	}
166
167
168
	/**
169
	 * Filter by multi select value
170
	 * @param  mixed  $row
171
	 * @param  FilterMultiSelect $filter
172
	 * @return void
173
	 */
174
	public function applyFilterMultiSelect($row, FilterMultiSelect $filter)
175
	{
176
		$condition = $filter->getCondition();
177
		$values = $condition[$filter->getColumn()];
178
179
		return in_array($row[$filter->getColumn()], $values);
180
	}
181
182
183
	/**
184
	 * @param  mixed  $row
185
	 * @param  FilterRange $filter
186
	 * @return void
187
	 */
188
	public function applyFilterRange($row, FilterRange $filter)
189
	{
190
		$condition = $filter->getCondition();
191
		$values = $condition[$filter->getColumn()];
192
193 View Code Duplication
		if ($values['from'] !== NULL && $values['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...
194
			if ($values['from'] > $row[$filter->getColumn()]) {
195
				return FALSE;
196
			}
197
		}
198
199 View Code Duplication
		if ($values['to'] !== NULL && $values['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...
200
			if ($values['to'] < $row[$filter->getColumn()]) {
201
				return FALSE;
202
			}
203
		}
204
205
		return TRUE;
206
	}
207
208
209
	/**
210
	 * @param  mixed  $row
211
	 * @param  FilterDateRange $filter
212
	 * @return void
213
	 */
214
	public function applyFilterDateRange($row, FilterDateRange $filter)
215
	{
216
		$format = $filter->getPhpFormat();
217
		$condition = $filter->getCondition();
218
		$values = $condition[$filter->getColumn()];
219
		$row_value = $row[$filter->getColumn()];
220
221 View Code Duplication
		if ($values['from'] !== NULL && $values['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...
222
			$date_from = \DateTime::createFromFormat($format, $values['from']);
223
224
			if (!($row_value instanceof \DateTime)) {
225
				/**
226
				 * Try to convert string to DateTime object
227
				 */
228
				try {
229
					$row_value = DateTimeHelper::tryConvertToDate($row_value);
230
				} catch (DataGridDateTimeHelperException $e) {
231
					/**
232
					 * Otherwise just return raw string
233
					 */
234
					return FALSE;
235
				}
236
			}
237
238
			if ($row_value->getTimeStamp() < $date_from->getTimeStamp()) {
239
				return FALSE;
240
			}
241
		}
242
243 View Code Duplication
		if ($values['to'] !== NULL && $values['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...
244
			$date_from = \DateTime::createFromFormat($format, $values['to']);
245
246
			if (!($row_value instanceof \DateTime)) {
247
				/**
248
				 * Try to convert string to DateTime object
249
				 */
250
				try {
251
					$row_value = DateTimeHelper::tryConvertToDate($row_value);
252
				} catch (DataGridDateTimeHelperException $e) {
253
					/**
254
					 * Otherwise just return raw string
255
					 */
256
					return FALSE;
257
				}
258
			}
259
260
			if ($row_value->getTimeStamp() > $date_from->getTimeStamp()) {
261
				return FALSE;
262
			}
263
		}
264
265
		return TRUE;
266
	}
267
268
269
	/**
270
	 * Apply fitler date and tell whether row value matches or not
271
	 * @param  mixed  $row
272
	 * @param  Filter $filter
273
	 * @return mixed
274
	 */
275
	protected function applyFilterDate($row, FilterDate $filter)
276
	{
277
		$format = $filter->getPhpFormat();
278
		$condition = $filter->getCondition();
279
280
		foreach ($condition as $column => $value) {
281
			$row_value = $row[$column];
282
283
			$date = \DateTime::createFromFormat($format, $value);
284
285
			if (!($row_value instanceof \DateTime)) {
286
				/**
287
				 * Try to convert string to DateTime object
288
				 */
289
				try {
290
					$row_value = DateTimeHelper::tryConvertToDateTime($row_value);
291
				} catch (DataGridDateTimeHelperException $e) {
292
					/**
293
					 * Otherwise just return raw string
294
					 */
295
					return FALSE;
296
				}
297
			}
298
299
			return $row_value->format($format) == $date->format($format);
300
		}
301
	}
302
303
304
	/**
305
	 * Sort data
306
	 * @param  Sorting $sorting
307
	 * @return static
308
	 */
309
	public function sort(Sorting $sorting)
310
	{
311
		if (is_callable($sorting->getSortCallback())) {
312
			$this->data = call_user_func(
0 ignored issues
show
Documentation Bug introduced by
It seems like call_user_func($sorting-...a, $sorting->getSort()) of type * is incompatible with the declared type array of property $data.

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...
313
				$sorting->getSortCallback(),
314
				$this->data,
315
				$sorting->getSort()
316
			);
317
318
			if (!is_array($this->data)) {
319
				throw new DataGridArrayDataSourceException('Sorting callback has to return array');
320
			}
321
322
			return $this;
323
		}
324
325
		$sort = $sorting->getSort();
326
327
		foreach ($sort as $column => $order) {
328
			$data = [];
329
330
			foreach ($this->data as $item) {
331
				$sort_by = (string) $item[$column];
332
				$data[$sort_by][] = $item;
333
			}
334
335
			if ($order === 'ASC') {
336
				ksort($data);
337
			} else {
338
				krsort($data);
339
			}
340
341
			$this->data = [];
342
343
			foreach ($data as $i) {
344
				foreach ($i as $item) {
345
					$this->data[] = $item;
346
				}
347
			}
348
		}
349
350
		return $this;
351
	}
352
353
}
354