ArrayDataSource   C
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 368
Duplicated Lines 16.03 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 54.1%

Importance

Changes 0
Metric Value
wmc 60
c 0
b 0
f 0
lcom 1
cbo 8
dl 59
loc 368
rs 6.0975
ccs 66
cts 122
cp 0.541

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getCount() 0 4 1
A getData() 0 4 1
A setData() 0 6 1
A filter() 0 21 4
A filterOne() 0 16 4
A limit() 0 7 1
C applyFilter() 5 38 14
A applyFilterMultiSelect() 0 7 1
B applyFilterRange() 10 19 7
C applyFilterDateRange() 44 55 11
B applyFilterDate() 0 27 4
C sort() 0 51 10

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 Nette\Utils\Strings;
12
use Ublaboo\DataGrid\Exception\DataGridArrayDataSourceException;
13
use Ublaboo\DataGrid\Exception\DataGridDateTimeHelperException;
14
use Ublaboo\DataGrid\Filter\Filter;
15
use Ublaboo\DataGrid\Filter\FilterDate;
16
use Ublaboo\DataGrid\Filter\FilterDateRange;
17
use Ublaboo\DataGrid\Filter\FilterMultiSelect;
18
use Ublaboo\DataGrid\Filter\FilterRange;
19
use Ublaboo\DataGrid\Filter\FilterText;
20
use Ublaboo\DataGrid\Utils\DateTimeHelper;
21
use Ublaboo\DataGrid\Utils\Sorting;
22
23 1
class ArrayDataSource implements IDataSource
24
{
25
26
	/**
27
	 * @var array
28
	 */
29
	protected $data = [];
30
31
	/**
32
	 * @var int
33
	 */
34
	protected $count = 0;
35
36
37
	/**
38
	 * @param array $data_source
39
	 */
40
	public function __construct(array $data_source)
41
	{
42 1
		$this->setData($data_source);
43 1
	}
44
45
46
	/********************************************************************************
47
	 *                          IDataSource implementation                          *
48
	 ********************************************************************************/
49
50
51
	/**
52
	 * Get count of data
53
	 * @return int
54
	 */
55
	public function getCount()
56
	{
57 1
		return sizeof($this->data);
58
	}
59
60
61
	/**
62
	 * Get the data
63
	 * @return array
64
	 */
65
	public function getData()
66
	{
67 1
		return $this->data;
68
	}
69
70
71
	/**
72
	 * Set the data
73
	 * @param array $data_source
74
	 * @return static
75
	 */
76
	private function setData(array $data_source)
77
	{
78 1
		$this->data = $data_source;
79
80 1
		return $this;
81
	}
82
83
84
	/**
85
	 * Filter data
86
	 * @param Filter[] $filters
87
	 * @return static
88
	 */
89
	public function filter(array $filters)
90
	{
91 1
		foreach ($filters as $filter) {
92 1
			if ($filter->isValueSet()) {
93 1
				if ($filter->hasConditionCallback()) {
94
					$data = (array) call_user_func_array(
95
						$filter->getConditionCallback(),
96
						[$this->data, $filter->getValue()]
97
					);
98
					$this->setData($data);
99
				} else {
100 1
					$data = array_filter($this->data, function ($row) use ($filter) {
101 1
						return $this->applyFilter($row, $filter);
102 1
					});
103 1
					$this->setData($data);
104
				}
105
			}
106
		}
107
108 1
		return $this;
109
	}
110
111
112
	/**
113
	 * Filter data - get one row
114
	 * @param array $condition
115
	 * @return ArrayDataSource
116
	 */
117
	public function filterOne(array $condition)
118
	{
119 1
		foreach ($this->data as $item) {
120 1
			foreach ($condition as $key => $value) {
121 1
				if ($item[$key] == $value) {
122 1
					$this->setData([$item]);
123
124 1
					return $this;
125
				}
126
			}
127
		}
128
129
		$this->setData([]);
130
131
		return $this;
132
	}
133
134
135
	/**
136
	 * Apply limit and offset on data
137
	 * @param int $offset
138
	 * @param int $limit
139
	 * @return static
140
	 */
141
	public function limit($offset, $limit)
142
	{
143 1
		$data = array_slice($this->data, $offset, $limit);
144 1
		$this->setData($data);
145
146 1
		return $this;
147
	}
148
149
150
	/**
151
	 * Apply fitler and tell whether row passes conditions or not
152
	 * @param  mixed  $row
153
	 * @param  Filter $filter
154
	 * @return mixed
155
	 */
156
	protected function applyFilter($row, Filter $filter)
157
	{
158 1
		if (is_array($row) || $row instanceof \Traversable) {
159 1
			if ($filter instanceof FilterDate) {
160
				return $this->applyFilterDate($row, $filter);
161 1
			} elseif ($filter instanceof FilterMultiSelect) {
162
				return $this->applyFilterMultiSelect($row, $filter);
163 1
			} elseif ($filter instanceof FilterDateRange) {
164
				return $this->applyFilterDateRange($row, $filter);
165 1
			} elseif ($filter instanceof FilterRange) {
166 1
				return $this->applyFilterRange($row, $filter);
167
			}
168
169 1
			$condition = $filter->getCondition();
170
171 1
			foreach ($condition as $column => $value) {
172 1
				if ($filter instanceof FilterText && $filter->isExactSearch()) {
173 1
					return $row[$column] == $value;
174
				}
175
176 1 View Code Duplication
				if ($filter instanceof FilterText && $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...
177 1
					$words = [$value];
178
				} else {
179 1
					$words = explode(' ', $value);
180
				}
181
182 1
				$row_value = strtolower(Strings::toAscii($row[$column]));
183
184 1
				foreach ($words as $word) {
185 1
					if (strpos($row_value, strtolower(Strings::toAscii($word))) !== false) {
186 1
						return $row;
187
					}
188
				}
189
			}
190
		}
191
192 1
		return false;
193
	}
194
195
196
	/**
197
	 * Filter by multi select value
198
	 * @param  mixed  $row
199
	 * @param  FilterMultiSelect $filter
200
	 * @return void
201
	 */
202
	public function applyFilterMultiSelect($row, FilterMultiSelect $filter)
203
	{
204
		$condition = $filter->getCondition();
205
		$values = $condition[$filter->getColumn()];
206
207
		return in_array($row[$filter->getColumn()], $values, true);
208
	}
209
210
211
	/**
212
	 * @param  mixed  $row
213
	 * @param  FilterRange $filter
214
	 * @return void
215
	 */
216
	public function applyFilterRange($row, FilterRange $filter)
217
	{
218 1
		$condition = $filter->getCondition();
219 1
		$values = $condition[$filter->getColumn()];
220
221 1 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 1
			if ($values['from'] > $row[$filter->getColumn()]) {
223 1
				return false;
224
			}
225
		}
226
227 1 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...
228 1
			if ($values['to'] < $row[$filter->getColumn()]) {
229 1
				return false;
230
			}
231
		}
232
233 1
		return true;
234
	}
235
236
237
	/**
238
	 * @param  mixed  $row
239
	 * @param  FilterDateRange $filter
240
	 * @return void
241
	 */
242
	public function applyFilterDateRange($row, FilterDateRange $filter)
243
	{
244
		$format = $filter->getPhpFormat();
245
		$condition = $filter->getCondition();
246
		$values = $condition[$filter->getColumn()];
247
		$row_value = $row[$filter->getColumn()];
248
249 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...
250
			$date_from = DateTimeHelper::tryConvertToDate($values['from'], [$format]);
251
			$date_from->setTime(0, 0, 0);
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_from->getTimeStamp()) {
268
				return false;
269
			}
270
		}
271
272 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...
273
			$date_to = DateTimeHelper::tryConvertToDate($values['to'], [$format]);
274
			$date_to->setTime(23, 59, 59);
275
276
			if (!($row_value instanceof \DateTime)) {
277
				/**
278
				 * Try to convert string to DateTime object
279
				 */
280
				try {
281
					$row_value = DateTimeHelper::tryConvertToDate($row_value);
282
				} catch (DataGridDateTimeHelperException $e) {
283
					/**
284
					 * Otherwise just return raw string
285
					 */
286
					return false;
287
				}
288
			}
289
290
			if ($row_value->getTimeStamp() > $date_to->getTimeStamp()) {
291
				return false;
292
			}
293
		}
294
295
		return true;
296
	}
297
298
299
	/**
300
	 * Apply fitler date and tell whether row value matches or not
301
	 * @param  mixed  $row
302
	 * @param  Filter $filter
303
	 * @return mixed
304
	 */
305
	protected function applyFilterDate($row, FilterDate $filter)
306
	{
307
		$format = $filter->getPhpFormat();
308
		$condition = $filter->getCondition();
309
310
		foreach ($condition as $column => $value) {
311
			$row_value = $row[$column];
312
313
			$date = DateTimeHelper::tryConvertToDateTime($value, [$format]);
314
315
			if (!($row_value instanceof \DateTime)) {
316
				/**
317
				 * Try to convert string to DateTime object
318
				 */
319
				try {
320
					$row_value = DateTimeHelper::tryConvertToDateTime($row_value);
321
				} catch (DataGridDateTimeHelperException $e) {
322
					/**
323
					 * Otherwise just return raw string
324
					 */
325
					return false;
326
				}
327
			}
328
329
			return $row_value->format($format) == $date->format($format);
330
		}
331
	}
332
333
334
	/**
335
	 * Sort data
336
	 * @param  Sorting $sorting
337
	 * @return static
338
	 */
339
	public function sort(Sorting $sorting)
340
	{
341 1
		if (is_callable($sorting->getSortCallback())) {
342
			$data = call_user_func(
343
				$sorting->getSortCallback(),
344
				$this->data,
345
				$sorting->getSort()
346
			);
347
348
			if (!is_array($data)) {
349
				throw new DataGridArrayDataSourceException('Sorting callback has to return array');
350
			}
351
352
			$this->setData($data);
353
354
			return $this;
355
		}
356
357 1
		$sort = $sorting->getSort();
358
359 1
		foreach ($sort as $column => $order) {
360 1
			$data = [];
361
362 1
			foreach ($this->data as $item) {
363 1
				if (is_object($item[$column]) && $item[$column] instanceof \DateTimeInterface) {
364
					$sort_by = $item[$column]->format('Y-m-d H:i:s');
365
				} else {
366 1
					$sort_by = (string) $item[$column];
367
				}
368 1
				$data[$sort_by][] = $item;
369
			}
370
371 1
			if ($order === 'ASC') {
372
				ksort($data);
373
			} else {
374 1
				krsort($data);
375
			}
376
377 1
			$data_source = [];
378
379 1
			foreach ($data as $i) {
380 1
				foreach ($i as $item) {
381 1
					$data_source[] = $item;
382
				}
383
			}
384
385 1
			$this->setData($data_source);
386
		}
387
388 1
		return $this;
389
	}
390
}
391