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

ContainerQuery::addSorter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 3
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
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
		if ($this->uniqueOnly) {
74 123
			if (reset($data) instanceof Base) {
75 119
				$ids = [];
76 119
				foreach ($data as $key => $obj) {
77 119
					if (in_array($obj->getId(), $ids, true)) {
78 22
						unset($data[$key]);
79 22
						continue;
80
					}
81 119
					$ids[] = $obj->getId();
82
				}
83
			}
84
			else {
85 10
				$data = array_unique($data);
86
			}
87
		}
88
89
		// Filters
90 128
		foreach ($this->filters as $filter) {
91 5
			$data = array_filter($data, $filter);
92
		}
93
94
		// Sorting
95 128
		if (isset($this->sorter)) {
96 48
			$data = $this->sorter->sort($data);
97
		}
98 126
		elseif (isset($this->sortClosure)) {
99 1
			uasort($data, $this->sortClosure);
100
		}
101 126
		elseif (isset($this->sortProperty)) {
102 1
			uasort($data, [$this, 'sortByPropertyCallback']);
103
		}
104
105
		// Order reverse
106 128
		if ($this->desc) {
107 2
			$data = array_reverse($data, true);
108
		}
109
110
		// "Pluck" a specific value from an object
111 128
		if (isset($this->pluck)) {
112 8
			$data = array_map(function($item) {
113 8
				if (is_array($item) && isset($item[$this->pluck])) {
114 1
					return $item[$this->pluck];
115
				}
116 7
				if (is_object($item)) {
117 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

117
					if (property_exists($item, /** @scrutinizer ignore-type */ $this->pluck)) {
Loading history...
118 1
						return $item->{$this->pluck};
119
					}
120 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

120
					if (method_exists($item, /** @scrutinizer ignore-type */ $this->pluck)) {
Loading history...
121 5
						return $item->{$this->pluck}();
122
					}
123
				}
124 2
				return $item;
125 8
			}, $data);
126
		}
127
128 128
		return $data;
129
	}
130
131
	/**
132
	 * Add a filter callback
133
	 *
134
	 * @param Closure $callback
135
	 *
136
	 * @return $this
137
	 */
138 5
	public function filter(Closure $callback) : ContainerQuery {
139 5
		$this->filters[] = $callback;
140 5
		return $this;
141
	}
142
143
	/**
144
	 * Sort in descending order
145
	 *
146
	 * @return $this
147
	 */
148 2
	public function desc() : ContainerQuery {
149 2
		$this->desc = true;
150 2
		return $this;
151
	}
152
153
	/**
154
	 * Sort a result using a callback - maintaining the index association
155
	 *
156
	 * @param Closure $callback
157
	 *
158
	 * @return $this
159
	 */
160 1
	public function sort(Closure $callback) : ContainerQuery {
161 1
		$this->sortClosure = $callback;
162 1
		return $this;
163
	}
164
165
	/**
166
	 * Sort a result set by a given property
167
	 *
168
	 * @warning Sort callback has a priority.
169
	 *
170
	 * @param string $property
171
	 *
172
	 * @return $this
173
	 */
174 1
	public function sortBy(string $property) : ContainerQuery {
175 1
		$this->sortProperty = $property;
176 1
		return $this;
177
	}
178
179
	/**
180
	 * @param BaseSorter $sorter
181
	 *
182
	 * @return $this
183
	 */
184 48
	public function addSorter(BaseSorter $sorter) : ContainerQuery {
185 48
		$this->sorter = $sorter;
186 48
		return $this;
187
	}
188
189
	/**
190
	 * Get only unique values
191
	 *
192
	 * @return $this
193
	 */
194 123
	public function unique() : ContainerQuery {
195 123
		$this->uniqueOnly = true;
196 123
		return $this;
197
	}
198
199
	/**
200
	 * Get only the object's ids
201
	 *
202
	 * @return $this
203
	 * @throws Exception
204
	 */
205 4
	public function ids() : ContainerQuery {
206 4
		$this->only('getId');
207 4
		return $this;
208
	}
209
210
	/**
211
	 * Pluck a specific key from all values
212
	 *
213
	 * @param string $property Property, array key or method to extract from values
214
	 *
215
	 * @return ContainerQuery
216
	 * @throws Exception
217
	 */
218 9
	public function only(string $property) : ContainerQuery {
219 9
		if (!empty($this->pluck)) {
220 1
			throw new Exception('only() can be only called once.');
221
		}
222 9
		$this->pluck = $property;
223 9
		return $this;
224
	}
225
226
	/**
227
	 * Sort function for sorting by a defined property
228
	 *
229
	 * @param array|object $value1
230
	 * @param array|object $value2
231
	 *
232
	 * @return int
233
	 */
234 1
	protected function sortByPropertyCallback($value1, $value2) : int {
235
		// Get values
236 1
		$property = $this->sortProperty ?? '';
237 1
		$property1 = null;
238 1
		$property2 = null;
239 1
		if (is_object($value1) && isset($value1->$property)) {
240 1
			$property1 = $value1->$property;
241
		}
242 1
		elseif (is_array($value1) && isset($value1[$property])) {
243 1
			$property1 = $value1[$property];
244
		}
245 1
		if (is_object($value2) && isset($value2->$property)) {
246 1
			$property2 = $value2->$property;
247
		}
248 1
		elseif (is_array($value2) && isset($value2[$property])) {
249 1
			$property2 = $value2[$property];
250
		}
251
252
		// Compare values
253 1
		if ($property1 === $property2) {
254 1
			return 0;
255
		}
256 1
		return $property1 < $property2 ? -1 : 1;
257
	}
258
259
}