Completed
Pull Request — master (#259)
by
unknown
03:19
created

ArrayDataSource   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 342
Duplicated Lines 18.71 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 5
Bugs 0 Features 4
Metric Value
wmc 56
c 5
b 0
f 4
lcom 1
cbo 9
dl 64
loc 342
rs 6.5957

12 Methods

Rating   Name   Duplication   Size   Complexity  
A limit() 0 6 1
A applyFilterMultiSelect() 0 7 1
B applyFilterRange() 10 19 7
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
D applyFilter() 5 29 10
C applyFilterDateRange() 49 60 13
B applyFilterDate() 0 32 5
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
			if (!($date_from instanceof \DateTime)) {
224
				/** Date given is not in valid format */
225
				return FALSE;
226
			}
227
			if (!($row_value instanceof \DateTime)) {
228
				/**
229
				 * Try to convert string to DateTime object
230
				 */
231
				try {
232
					$row_value = DateTimeHelper::tryConvertToDate($row_value);
233
				} catch (DataGridDateTimeHelperException $e) {
234
					/**
235
					 * Otherwise just return raw string
236
					 */
237
					return FALSE;
238
				}
239
			}
240
241
			if ($row_value->getTimeStamp() < $date_from->getTimeStamp()) {
242
				return FALSE;
243
			}
244
		}
245
246 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...
247
			$date_to = \DateTime::createFromFormat($format, $values['to']);
248
			if (!($date_to instanceof \DateTime)) {
249
				/** Date given is not in valid format */
250
				return FALSE;
251
			}
252
253
			if (!($row_value instanceof \DateTime)) {
254
				/**
255
				 * Try to convert string to DateTime object
256
				 */
257
				try {
258
					$row_value = DateTimeHelper::tryConvertToDate($row_value);
259
				} catch (DataGridDateTimeHelperException $e) {
260
					/**
261
					 * Otherwise just return raw string
262
					 */
263
					return FALSE;
264
				}
265
			}
266
267
			if ($row_value->getTimeStamp() > $date_to->getTimeStamp()) {
268
				return FALSE;
269
			}
270
		}
271
272
		return TRUE;
273
	}
274
275
276
	/**
277
	 * Apply fitler date and tell whether row value matches or not
278
	 * @param  mixed  $row
279
	 * @param  FilterDate $filter
280
	 * @return mixed
281
	 */
282
	protected function applyFilterDate($row, FilterDate $filter)
283
	{
284
		$format = $filter->getPhpFormat();
285
		$condition = $filter->getCondition();
286
287
		foreach ($condition as $column => $value) {
288
			$row_value = $row[$column];
289
290
			$date = \DateTime::createFromFormat($format, $value);
291
292
			if (!($date instanceof \DateTime)) {
293
				/** Date given is not in valid format */
294
				return FALSE;
295
			}
296
297
			if (!($row_value instanceof \DateTime)) {
298
				/**
299
				 * Try to convert string to DateTime object
300
				 */
301
				try {
302
					$row_value = DateTimeHelper::tryConvertToDateTime($row_value);
303
				} catch (DataGridDateTimeHelperException $e) {
304
					/**
305
					 * Otherwise just return raw string
306
					 */
307
					return FALSE;
308
				}
309
			}
310
311
			return $row_value->format($format) == $date->format($format);
312
		}
313
	}
314
315
316
	/**
317
	 * Sort data
318
	 * @param  Sorting $sorting
319
	 * @return static
320
	 */
321
	public function sort(Sorting $sorting)
322
	{
323
		if (is_callable($sorting->getSortCallback())) {
324
			$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...
325
				$sorting->getSortCallback(),
326
				$this->data,
327
				$sorting->getSort()
328
			);
329
330
			if (!is_array($this->data)) {
331
				throw new DataGridArrayDataSourceException('Sorting callback has to return array');
332
			}
333
334
			return $this;
335
		}
336
337
		$sort = $sorting->getSort();
338
339
		foreach ($sort as $column => $order) {
340
			$data = [];
341
342
			foreach ($this->data as $item) {
343
				$sort_by = (string) $item[$column];
344
				$data[$sort_by][] = $item;
345
			}
346
347
			if ($order === 'ASC') {
348
				ksort($data);
349
			} else {
350
				krsort($data);
351
			}
352
353
			$this->data = [];
354
355
			foreach ($data as $i) {
356
				foreach ($i as $item) {
357
					$this->data[] = $item;
358
				}
359
			}
360
		}
361
362
		return $this;
363
	}
364
365
}
366