Completed
Push — master ( 8e8354...a7ba40 )
by Tomáš
02:28
created

ContainerQuery::sortData()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 6
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 9
ccs 7
cts 7
cp 1
crap 4
rs 10
1
<?php
2
3
4
namespace TournamentGenerator\Containers;
5
6
use Closure;
7
use Exception;
8
use TournamentGenerator\Base;
9
use TournamentGenerator\Helpers\Sorter\BaseSorter;
10
11
/**
12
 * Class ContainerQuery
13
 *
14
 * Container query is a helper class to filter, sort, etc. the values of the container hierarchy.
15
 *
16
 * @package TournamentGenerator\Containers
17
 * @author  Tomáš Vojík <[email protected]>
18
 * @since   0.4
19
 */
20
class ContainerQuery
21
{
22
23
	/** @var BaseContainer Queried container */
24
	protected BaseContainer $container;
25
	/** @var Closure[] Filter closures */
26
	protected array      $filters      = [];
27
	protected Closure    $sortClosure;
28
	protected string     $sortProperty;
29
	protected bool       $desc         = false;
30
	protected BaseSorter $sorter;
31
	protected bool       $topLevelOnly = false;
32
	protected bool       $uniqueOnly   = false;
33
	protected ?string    $pluck        = null;
34
35
	/**
36
	 * ContainerQuery constructor.
37
	 *
38
	 * @param BaseContainer $container Queried container
39
	 */
40 129
	public function __construct(BaseContainer $container, bool $topLevelOnly = false) {
41 129
		$this->container = $container;
42 129
		$this->topLevelOnly = $topLevelOnly;
43 129
	}
44
45
	/**
46
	 * Gets the first result of container query
47
	 *
48
	 * @return mixed|null
49
	 */
50 4
	public function getFirst() {
51 4
		$data = $this->get();
52 4
		if (count($data) === 0) {
53 3
			return null;
54
		}
55 4
		return reset($data);
56
	}
57
58
	/**
59
	 * Get the result
60
	 *
61
	 * @return array
62
	 */
63 128
	public function get() : array {
64
		// Get initial data
65 128
		if ($this->topLevelOnly) {
66 1
			$data = $this->container->getTopLevel();
67
		}
68
		else {
69 128
			$data = $this->container->get();
70
		}
71
72
		// Unique
73 128
		$this->filterUnique($data);
74
75
		// Filters
76 128
		$this->applyFilters($data);
77
78
		// Sorting
79 128
		$this->sortData($data);
80
81
		// Order reverse
82 128
		if ($this->desc) {
83 2
			$data = array_reverse($data, true);
84
		}
85
86
		// "Pluck" a specific value from an object
87 128
		$this->pluckData($data);
88
89 128
		return $data;
90
	}
91
92
	/**
93
	 *  Filter data to contain only unique values
94
	 *
95
	 * @param array $data
96
	 */
97 128
	protected function filterUnique(array &$data) : void {
98 128
		if ($this->uniqueOnly) {
99 123
			if (reset($data) instanceof Base) {
100 119
				$ids = [];
101 119
				foreach ($data as $key => $obj) {
102 119
					if (in_array($obj->getId(), $ids, true)) {
103 22
						unset($data[$key]);
104 22
						continue;
105
					}
106 119
					$ids[] = $obj->getId();
107
				}
108
			}
109
			else {
110 10
				$data = array_unique($data);
111
			}
112
		}
113 128
	}
114
115
	/**
116
	 * Apply predefined filters on data
117
	 *
118
	 * @param $data
119
	 */
120 128
	protected function applyFilters(&$data) : void {
121 128
		foreach ($this->filters as $filter) {
122 5
			$data = array_filter($data, $filter);
123
		}
124 128
	}
125
126
	/**
127
	 * Sort data using a predefined filters
128
	 *
129
	 * @param array $data
130
	 */
131 128
	protected function sortData(array &$data) : void {
132 128
		if (isset($this->sorter)) {
133 48
			$data = $this->sorter->sort($data);
134
		}
135 126
		elseif (isset($this->sortClosure)) {
136 1
			uasort($data, $this->sortClosure);
137
		}
138 126
		elseif (isset($this->sortProperty)) {
139 1
			uasort($data, [$this, 'sortByPropertyCallback']);
140
		}
141 128
	}
142
143
	/**
144
	 * Pluck a predefined value from data values
145
	 *
146
	 * @param $data
147
	 */
148 128
	protected function pluckData(&$data) : void {
149 128
		if (!empty($this->pluck)) {
150 8
			$data = array_map(function($item) {
151 8
				if (is_array($item) && isset($item[$this->pluck])) {
152 1
					return $item[$this->pluck];
153
				}
154 7
				if (is_object($item)) {
155 6
					if (property_exists($item, $this->pluck)) {
0 ignored issues
show
Bug introduced by
It seems like $this->pluck can also be of type null; however, parameter $property of property_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
					if (property_exists($item, /** @scrutinizer ignore-type */ $this->pluck)) {
Loading history...
156 1
						return $item->{$this->pluck};
157
					}
158 5
					if (method_exists($item, $this->pluck)) {
0 ignored issues
show
Bug introduced by
It seems like $this->pluck can also be of type null; however, parameter $method of method_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

158
					if (method_exists($item, /** @scrutinizer ignore-type */ $this->pluck)) {
Loading history...
159 5
						return $item->{$this->pluck}();
160
					}
161
				}
162 2
				return $item;
163 8
			}, $data);
164
		}
165 128
	}
166
167
	/**
168
	 * Add a filter callback
169
	 *
170
	 * @param Closure $callback
171
	 *
172
	 * @return $this
173
	 */
174 5
	public function filter(Closure $callback) : ContainerQuery {
175 5
		$this->filters[] = $callback;
176 5
		return $this;
177
	}
178
179
	/**
180
	 * Sort in descending order
181
	 *
182
	 * @return $this
183
	 */
184 2
	public function desc() : ContainerQuery {
185 2
		$this->desc = true;
186 2
		return $this;
187
	}
188
189
	/**
190
	 * Sort a result using a callback - maintaining the index association
191
	 *
192
	 * @param Closure $callback
193
	 *
194
	 * @return $this
195
	 */
196 1
	public function sort(Closure $callback) : ContainerQuery {
197 1
		$this->sortClosure = $callback;
198 1
		return $this;
199
	}
200
201
	/**
202
	 * Sort a result set by a given property
203
	 *
204
	 * @warning Sort callback has a priority.
205
	 *
206
	 * @param string $property
207
	 *
208
	 * @return $this
209
	 */
210 1
	public function sortBy(string $property) : ContainerQuery {
211 1
		$this->sortProperty = $property;
212 1
		return $this;
213
	}
214
215
	/**
216
	 * @param BaseSorter $sorter
217
	 *
218
	 * @return $this
219
	 */
220 48
	public function addSorter(BaseSorter $sorter) : ContainerQuery {
221 48
		$this->sorter = $sorter;
222 48
		return $this;
223
	}
224
225
	/**
226
	 * Get only unique values
227
	 *
228
	 * @return $this
229
	 */
230 123
	public function unique() : ContainerQuery {
231 123
		$this->uniqueOnly = true;
232 123
		return $this;
233
	}
234
235
	/**
236
	 * Get only the object's ids
237
	 *
238
	 * @return $this
239
	 * @throws Exception
240
	 */
241 4
	public function ids() : ContainerQuery {
242 4
		$this->only('getId');
243 4
		return $this;
244
	}
245
246
	/**
247
	 * Pluck a specific key from all values
248
	 *
249
	 * @param string $property Property, array key or method to extract from values
250
	 *
251
	 * @return ContainerQuery
252
	 * @throws Exception
253
	 */
254 9
	public function only(string $property) : ContainerQuery {
255 9
		if (!empty($this->pluck)) {
256 1
			throw new Exception('only() can be only called once.');
257
		}
258 9
		$this->pluck = $property;
259 9
		return $this;
260
	}
261
262
	/**
263
	 * Sort function for sorting by a defined property
264
	 *
265
	 * @param array|object $value1
266
	 * @param array|object $value2
267
	 *
268
	 * @return int
269
	 */
270 1
	protected function sortByPropertyCallback($value1, $value2) : int {
271
		// Get values
272 1
		$property = $this->sortProperty ?? '';
273 1
		$property1 = null;
274 1
		$property2 = null;
275 1
		if (is_object($value1) && isset($value1->$property)) {
276 1
			$property1 = $value1->$property;
277
		}
278 1
		elseif (is_array($value1) && isset($value1[$property])) {
279 1
			$property1 = $value1[$property];
280
		}
281 1
		if (is_object($value2) && isset($value2->$property)) {
282 1
			$property2 = $value2->$property;
283
		}
284 1
		elseif (is_array($value2) && isset($value2[$property])) {
285 1
			$property2 = $value2[$property];
286
		}
287
288
		// Compare values
289 1
		if ($property1 === $property2) {
290 1
			return 0;
291
		}
292 1
		return $property1 < $property2 ? -1 : 1;
293
	}
294
295
}