1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace kartavik\Collections; |
4
|
|
|
|
5
|
|
|
use kartavik\Collections\Exceptions\InvalidElementException; |
6
|
|
|
use kartavik\Collections\Exceptions\UnprocessedTypeException; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Class Collection |
10
|
|
|
* @package kartavik\Collections |
11
|
|
|
*/ |
12
|
|
|
class Collection implements \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable, \Serializable |
13
|
|
|
{ |
14
|
|
|
/** @var string */ |
15
|
|
|
private $type = null; |
16
|
|
|
|
17
|
|
|
/** @var array */ |
18
|
|
|
protected $container = []; |
19
|
|
|
|
20
|
|
|
public function __construct(string $type, iterable ...$iterables) |
21
|
|
|
{ |
22
|
|
|
$this->type = $type; |
23
|
|
|
|
24
|
|
|
foreach ($iterables as $iterable) { |
25
|
|
|
foreach ($iterable as $item) { |
26
|
|
|
$this->add($item); |
27
|
|
|
} |
28
|
|
|
} |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
final public function type(): string |
32
|
|
|
{ |
33
|
|
|
return $this->type; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
public function offsetExists($offset): bool |
37
|
|
|
{ |
38
|
|
|
return array_key_exists($offset, $this->container); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
public function offsetGet($offset) |
42
|
|
|
{ |
43
|
|
|
return $this->container[$offset]; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
public function offsetUnset($offset): void |
47
|
|
|
{ |
48
|
|
|
if ($this->offsetExists($offset)) { |
49
|
|
|
unset($this->container[$offset]); |
50
|
|
|
} |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function getIterator(): \ArrayIterator |
54
|
|
|
{ |
55
|
|
|
return new \ArrayIterator($this->container); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
public function append(): void |
59
|
|
|
{ |
60
|
|
|
$values = func_get_args(); |
61
|
|
|
|
62
|
|
|
foreach ($values as $item) { |
63
|
|
|
$this->add($item); |
64
|
|
|
} |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
public static function makeSafe(string $type, iterable ...$iterables) |
68
|
|
|
{ |
69
|
|
|
$collection = new Collection($type); |
70
|
|
|
|
71
|
|
|
foreach ($iterables as $iterable) { |
72
|
|
|
foreach ($iterable as $key => $item) { |
73
|
|
|
$collection->add($item, $key); |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
return $collection; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @param mixed $index |
82
|
|
|
* @param mixed $value |
83
|
|
|
* |
84
|
|
|
* @throws InvalidElementException |
85
|
|
|
*/ |
86
|
|
|
public function offsetSet($index, $value): void |
87
|
|
|
{ |
88
|
|
|
$this->add($value, $index); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
public function serialize() |
92
|
|
|
{ |
93
|
|
|
return serialize($this->container); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
public function unserialize($serialized, array $options = []) |
97
|
|
|
{ |
98
|
|
|
$unserialized = unserialize($serialized, $options); |
99
|
|
|
|
100
|
|
|
if (is_iterable($unserialized)) { |
101
|
|
|
return $this->join($unserialized, true); |
|
|
|
|
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
throw new \InvalidArgumentException('Serialized object must be iterable'); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
public function join(iterable $iterable, bool $useKeys = false) |
108
|
|
|
{ |
109
|
|
|
foreach ($iterable as $key => $item) { |
110
|
|
|
$this->add($item, $useKeys ? $key : null); |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
public function jsonSerialize(): array |
115
|
|
|
{ |
116
|
|
|
return $this->container; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
public function isCompatible($element): bool |
120
|
|
|
{ |
121
|
|
|
try { |
122
|
|
|
if ($element instanceof self) { |
123
|
|
|
return true; |
124
|
|
|
} elseif (is_array($element)) { |
125
|
|
|
foreach ($element as $item) { |
126
|
|
|
try { |
127
|
|
|
$this->validate($item); |
128
|
|
|
} catch (InvalidElementException $ex) { |
129
|
|
|
return false; |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
return true; |
134
|
|
|
} |
135
|
|
|
} catch (\InvalidArgumentException $exception) { |
|
|
|
|
136
|
|
|
} finally { |
137
|
|
|
return false; |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
public function first() |
142
|
|
|
{ |
143
|
|
|
reset($this->container); |
144
|
|
|
|
145
|
|
|
return $this->container[key($this->container)]; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* @param $item |
150
|
|
|
* |
151
|
|
|
* @throws InvalidElementException |
152
|
|
|
*/ |
153
|
|
|
public function validate($item): void |
154
|
|
|
{ |
155
|
|
|
$type = $this->type(); |
156
|
|
|
|
157
|
|
|
if (!$item instanceof $type) { |
158
|
|
|
throw new InvalidElementException($item, $type); |
159
|
|
|
} |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
public function map(callable $callback): Collection |
163
|
|
|
{ |
164
|
|
|
$type = get_class(call_user_func( |
165
|
|
|
$callback, |
166
|
|
|
$this->first() |
167
|
|
|
)); |
168
|
|
|
|
169
|
|
|
$elements = array_map($callback, $this->container, array_keys($this->container)); |
170
|
|
|
|
171
|
|
|
return Collection::{$type}($elements); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
public function walk(callable $callback): bool |
175
|
|
|
{ |
176
|
|
|
return array_walk($this->container, $callback); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
public function chunk(int $size): Collection |
180
|
|
|
{ |
181
|
|
|
$mappedType = get_class($this->offsetGet(0)); |
182
|
|
|
/** @var Collection $collection */ |
183
|
|
|
$collection = Collection::{Collection::class}(); |
184
|
|
|
$chunked = array_chunk($this->jsonSerialize(), $size); |
185
|
|
|
|
186
|
|
|
foreach ($chunked as $index => $chunk) { |
187
|
|
|
$collection->append(Collection::{$mappedType}()); |
188
|
|
|
|
189
|
|
|
foreach ($chunk as $item) { |
190
|
|
|
$collection[$index]->append($item); |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
return $collection; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
public function column(string $property, callable $callback = null): Collection |
198
|
|
|
{ |
199
|
|
|
$getterType = get_class($this->offsetGet(0)->$property); |
200
|
|
|
|
201
|
|
|
if (!is_null($callback)) { |
202
|
|
|
/** @var Collection $collection */ |
203
|
|
|
$collection = Collection::{$getterType}(); |
204
|
|
|
|
205
|
|
|
foreach ($this->jsonSerialize() as $item) { |
206
|
|
|
$collection->append(call_user_func($callback, $item->$property)); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return $collection; |
210
|
|
|
} else { |
211
|
|
|
return Collection::{$getterType}(array_map( |
212
|
|
|
function ($item) use ($property) { |
213
|
|
|
return $item->$property; |
214
|
|
|
}, |
215
|
|
|
$this->jsonSerialize() |
216
|
|
|
)); |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
public function pop() |
221
|
|
|
{ |
222
|
|
|
return array_pop($this->container); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
public function sum(callable $callback) |
226
|
|
|
{ |
227
|
|
|
$sum = 0; |
228
|
|
|
|
229
|
|
|
foreach ($this as $element) { |
230
|
|
|
$sum += call_user_func($callback, $element); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
return $sum; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* @param string $name |
238
|
|
|
* @param array $arguments |
239
|
|
|
* |
240
|
|
|
* @return Collection |
241
|
|
|
*/ |
242
|
|
|
public static function __callStatic(string $name, array $arguments = []) |
243
|
|
|
{ |
244
|
|
|
if (!empty($arguments) && is_array($arguments[0])) { |
245
|
|
|
$arguments = $arguments[0]; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
static::validateType($name); |
249
|
|
|
|
250
|
|
|
reset($arguments); |
251
|
|
|
|
252
|
|
|
if (current($arguments) instanceof Collection) { |
253
|
|
|
return new static($name, ...$arguments); |
254
|
|
|
} else { |
255
|
|
|
return new static($name, $arguments); |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
public function count(): int |
260
|
|
|
{ |
261
|
|
|
return count($this->container); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
public function add($item, $index = null): void |
265
|
|
|
{ |
266
|
|
|
$this->validate($item); |
267
|
|
|
$this->container[$index ?? $this->count()] = $item; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
protected static function validateType(string $type): void |
271
|
|
|
{ |
272
|
|
|
if (!class_exists($type)) { |
273
|
|
|
throw new UnprocessedTypeException($type); |
274
|
|
|
} |
275
|
|
|
} |
276
|
|
|
} |
277
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.