1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace PublishingKit\Utilities\Collections; |
||||
6 | |||||
7 | use ArrayAccess; |
||||
8 | use ArrayIterator; |
||||
9 | use PublishingKit\Utilities\Contracts\Collectable; |
||||
10 | use PublishingKit\Utilities\Traits\Macroable; |
||||
11 | |||||
12 | /** |
||||
13 | * Collection class |
||||
14 | * |
||||
15 | * @psalm-consistent-constructor |
||||
16 | * @template T |
||||
17 | */ |
||||
18 | class Collection implements ArrayAccess, Collectable |
||||
19 | { |
||||
20 | use Macroable; |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
21 | |||||
22 | 1 | /** |
|||
23 | * Items |
||||
24 | * |
||||
25 | * @var iterable<T> |
||||
26 | */ |
||||
27 | protected $items; |
||||
28 | |||||
29 | /** |
||||
30 | * Position |
||||
31 | * |
||||
32 | * @var integer |
||||
33 | */ |
||||
34 | protected $position = 0; |
||||
35 | |||||
36 | /** |
||||
37 | * Constructor |
||||
38 | * |
||||
39 | * @param iterable<T> $items Items to collect. |
||||
40 | * @return void |
||||
41 | */ |
||||
42 | public function __construct(iterable $items = []) |
||||
43 | 12 | { |
|||
44 | $this->items = $items; |
||||
45 | 12 | } |
|||
46 | 9 | ||||
47 | /** |
||||
48 | 3 | * Create collection |
|||
49 | 3 | * |
|||
50 | 3 | * @param iterable $items Items to collect. |
|||
51 | * @return Collection |
||||
52 | 3 | */ |
|||
53 | public static function make(iterable $items) |
||||
54 | { |
||||
55 | return new static($items); |
||||
56 | } |
||||
57 | |||||
58 | /** |
||||
59 | * Return count of items |
||||
60 | * |
||||
61 | 159 | * @return integer |
|||
62 | */ |
||||
63 | 159 | public function count() |
|||
64 | 159 | { |
|||
65 | if (is_array($this->items)) { |
||||
66 | return count($this->items); |
||||
67 | } |
||||
68 | $count = 0; |
||||
69 | foreach ($this->items as $item) { |
||||
70 | $count++; |
||||
71 | } |
||||
72 | 6 | return $count; |
|||
73 | } |
||||
74 | 6 | ||||
75 | /** |
||||
76 | * Does item exist? |
||||
77 | * |
||||
78 | * @param mixed $offset The offset. |
||||
79 | * @return boolean |
||||
80 | */ |
||||
81 | public function offsetExists($offset) |
||||
82 | { |
||||
83 | 3 | return isset($this->items[$offset]); |
|||
84 | } |
||||
85 | 3 | ||||
86 | /** |
||||
87 | * Get offset |
||||
88 | * |
||||
89 | * @param mixed $offset The offset. |
||||
90 | * @return mixed |
||||
91 | */ |
||||
92 | public function offsetGet($offset) |
||||
93 | { |
||||
94 | 15 | return isset($this->items[$offset]) ? $this->items[$offset] : null; |
|||
95 | } |
||||
96 | 15 | ||||
97 | /** |
||||
98 | * Set offset |
||||
99 | * |
||||
100 | * @param mixed $offset The offset. |
||||
101 | * @param mixed $value The value to set. |
||||
102 | * @return void |
||||
103 | */ |
||||
104 | public function offsetSet($offset, $value): void |
||||
105 | { |
||||
106 | 6 | if (is_null($offset)) { |
|||
107 | $this->items[] = $value; |
||||
108 | 6 | return; |
|||
109 | 3 | } |
|||
110 | 3 | $this->items[$offset] = $value; |
|||
111 | } |
||||
112 | 3 | ||||
113 | 3 | /** |
|||
114 | * Unset offset |
||||
115 | * |
||||
116 | * @param mixed $offset The offset. |
||||
117 | * @return void |
||||
118 | */ |
||||
119 | public function offsetUnset($offset) |
||||
120 | { |
||||
121 | 3 | unset($this->items[$offset]); |
|||
122 | } |
||||
123 | 3 | ||||
124 | 3 | /** |
|||
125 | * {@inheritDoc} |
||||
126 | */ |
||||
127 | public function getIterator() |
||||
128 | { |
||||
129 | 3 | return new ArrayIterator($this->items); |
|||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of ArrayIterator::__construct() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
130 | } |
||||
131 | 3 | ||||
132 | /** |
||||
133 | * Serialize collection to JSON |
||||
134 | * |
||||
135 | * @return string|false |
||||
136 | */ |
||||
137 | public function jsonSerialize() |
||||
138 | { |
||||
139 | 6 | return json_encode($this->items); |
|||
140 | } |
||||
141 | 6 | ||||
142 | /** |
||||
143 | * Convert collection to JSON |
||||
144 | * |
||||
145 | * @return string|false |
||||
146 | */ |
||||
147 | public function toJson() |
||||
148 | { |
||||
149 | 3 | return $this->jsonSerialize(); |
|||
150 | } |
||||
151 | 3 | ||||
152 | /** |
||||
153 | * Convert collection to array |
||||
154 | * |
||||
155 | * @return iterable |
||||
156 | */ |
||||
157 | public function toArray(): iterable |
||||
158 | { |
||||
159 | 81 | return $this->items; |
|||
160 | } |
||||
161 | 81 | ||||
162 | /** |
||||
163 | * Map operation |
||||
164 | * |
||||
165 | * @param callable $callback The callback to use. |
||||
166 | * @return Collection |
||||
167 | */ |
||||
168 | public function map(callable $callback) |
||||
169 | { |
||||
170 | 12 | return new static(array_map($callback, $this->items)); |
|||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_map() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
171 | } |
||||
172 | 12 | ||||
173 | /** |
||||
174 | * {@inheritDoc} |
||||
175 | */ |
||||
176 | public function filter(callable $callback) |
||||
177 | { |
||||
178 | return new static(array_filter($this->items, $callback)); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_filter() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
179 | } |
||||
180 | |||||
181 | 6 | /** |
|||
182 | * {@inheritDoc} |
||||
183 | 6 | */ |
|||
184 | public function reject(callable $callback) |
||||
185 | { |
||||
186 | return $this->filter(function ($item) use ($callback) { |
||||
187 | return !$callback($item); |
||||
188 | }); |
||||
189 | } |
||||
190 | |||||
191 | /** |
||||
192 | 3 | * Reduce operation |
|||
193 | * |
||||
194 | 2 | * @param callable $callback The callback to use. |
|||
195 | 3 | * @param mixed $initial The initial value. |
|||
196 | 3 | * @return mixed |
|||
197 | */ |
||||
198 | public function reduce(callable $callback, $initial = 0) |
||||
199 | { |
||||
200 | $accumulator = $initial; |
||||
201 | foreach ($this->items as $item) { |
||||
202 | $accumulator = $callback($accumulator, $item); |
||||
203 | } |
||||
204 | return $accumulator; |
||||
205 | } |
||||
206 | 3 | ||||
207 | /** |
||||
208 | 3 | * Reduce operation that returns a collection |
|||
209 | 3 | * |
|||
210 | 3 | * @param callable $callback The callback to use. |
|||
211 | * @param mixed $initial The initial value. |
||||
212 | 3 | * @return Collection |
|||
213 | */ |
||||
214 | public function reduceToCollection(callable $callback, $initial = 0): Collection |
||||
215 | { |
||||
216 | $accumulator = $initial; |
||||
217 | foreach ($this->items as $item) { |
||||
218 | $accumulator = $callback($accumulator, $item); |
||||
219 | } |
||||
220 | return new static($accumulator); |
||||
221 | } |
||||
222 | 3 | ||||
223 | /** |
||||
224 | 3 | * Pluck a single field |
|||
225 | 3 | * |
|||
226 | 3 | * @param mixed $name Name of field to pluck. |
|||
227 | * @return mixed |
||||
228 | 3 | */ |
|||
229 | public function pluck($name) |
||||
230 | { |
||||
231 | return $this->map(function (array $item) use ($name) { |
||||
232 | return $item[$name]; |
||||
233 | }); |
||||
234 | } |
||||
235 | |||||
236 | /** |
||||
237 | 3 | * Apply callback to each item in the collection |
|||
238 | * |
||||
239 | 2 | * @param callable $callback The callback to use. |
|||
240 | 3 | * @return void |
|||
241 | 3 | */ |
|||
242 | public function each(callable $callback) |
||||
243 | { |
||||
244 | foreach ($this->items as $item) { |
||||
245 | $callback($item); |
||||
246 | } |
||||
247 | } |
||||
248 | |||||
249 | /** |
||||
250 | 3 | * Push item to end of collection |
|||
251 | * |
||||
252 | 3 | * @param mixed $item Item to push. |
|||
253 | 3 | * @return Collection |
|||
254 | */ |
||||
255 | 3 | public function push($item) |
|||
256 | { |
||||
257 | array_push($this->items, $item); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_push() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
258 | return new static($this->items); |
||||
259 | } |
||||
260 | |||||
261 | /** |
||||
262 | * Pop item from end of collection |
||||
263 | 3 | * |
|||
264 | * @return mixed |
||||
265 | 3 | */ |
|||
266 | 3 | public function pop() |
|||
267 | { |
||||
268 | return array_pop($this->items); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_pop() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
269 | } |
||||
270 | |||||
271 | /** |
||||
272 | * Push item to start of collection |
||||
273 | * |
||||
274 | 3 | * @param mixed $item Item to push. |
|||
275 | * @return Collection |
||||
276 | 3 | */ |
|||
277 | public function unshift($item) |
||||
278 | { |
||||
279 | array_unshift($this->items, $item); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_unshift() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
280 | return new static($this->items); |
||||
281 | } |
||||
282 | |||||
283 | /** |
||||
284 | * Pop item from start of collection |
||||
285 | 3 | * |
|||
286 | * @return mixed |
||||
287 | 3 | */ |
|||
288 | 3 | public function shift() |
|||
289 | { |
||||
290 | return array_shift($this->items); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_shift() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
291 | } |
||||
292 | |||||
293 | /** |
||||
294 | * Sort collection |
||||
295 | * |
||||
296 | 3 | * @param callable|null $callback The callback to use. |
|||
297 | * @return Collection |
||||
298 | 3 | */ |
|||
299 | public function sort(callable $callback = null) |
||||
300 | { |
||||
301 | if ($callback) { |
||||
302 | usort($this->items, $callback); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of usort() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
303 | } else { |
||||
304 | sort($this->items); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of sort() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
305 | } |
||||
306 | return new static($this->items); |
||||
307 | 6 | } |
|||
308 | |||||
309 | 6 | /** |
|||
310 | 3 | * Reverse collection |
|||
311 | * |
||||
312 | 3 | * @return Collection |
|||
313 | */ |
||||
314 | 6 | public function reverse() |
|||
315 | { |
||||
316 | return new static(array_reverse($this->items)); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_reverse() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
317 | } |
||||
318 | |||||
319 | /** |
||||
320 | * Return keys |
||||
321 | * |
||||
322 | 3 | * @return Collection |
|||
323 | */ |
||||
324 | 3 | public function keys() |
|||
325 | { |
||||
326 | return new static(array_keys($this->items)); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_keys() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
327 | } |
||||
328 | |||||
329 | /** |
||||
330 | * Return values |
||||
331 | * |
||||
332 | 3 | * @return Collection |
|||
333 | */ |
||||
334 | 3 | public function values(): Collection |
|||
335 | { |
||||
336 | return new static(array_values($this->items)); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_values() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
337 | } |
||||
338 | |||||
339 | /** |
||||
340 | * Return chunked collection |
||||
341 | * |
||||
342 | 3 | * @param integer $size Chunk size. |
|||
343 | * @return Collection |
||||
344 | 3 | */ |
|||
345 | public function chunk(int $size): Collection |
||||
346 | { |
||||
347 | return new static(array_chunk($this->items, $size)); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_chunk() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
348 | } |
||||
349 | |||||
350 | /** |
||||
351 | * Merge another array into the collection |
||||
352 | * |
||||
353 | 3 | * @param array<T> $merge Array to merge. |
|||
354 | * @return Collection |
||||
355 | 3 | */ |
|||
356 | public function merge($merge): Collection |
||||
357 | { |
||||
358 | return new static(array_merge($this->items, $merge)); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $arrays of array_merge() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
359 | } |
||||
360 | |||||
361 | /** |
||||
362 | * Group by a given key |
||||
363 | * |
||||
364 | 3 | * @param string $key Key to group by. |
|||
365 | * @return Collection |
||||
366 | 3 | */ |
|||
367 | public function groupBy(string $key): Collection |
||||
368 | { |
||||
369 | $items = []; |
||||
370 | foreach ($this->items as $item) { |
||||
371 | $items[$item[$key]][] = $item; |
||||
372 | } |
||||
373 | return new static($items); |
||||
374 | } |
||||
375 | 3 | ||||
376 | /** |
||||
377 | 3 | * Flatten items |
|||
378 | 3 | * |
|||
379 | 3 | * @return Collection |
|||
380 | */ |
||||
381 | 3 | public function flatten(): Collection |
|||
382 | { |
||||
383 | $return = []; |
||||
384 | array_walk_recursive($this->items, function ($a) use (&$return) { |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array|object expected by parameter $array of array_walk_recursive() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
385 | $return[] = $a; |
||||
386 | }); |
||||
387 | /** @var T[] $return **/ |
||||
388 | return new static($return); |
||||
389 | 3 | } |
|||
390 | |||||
391 | 3 | /** |
|||
392 | 2 | * Paginate items |
|||
393 | 3 | * |
|||
394 | 3 | * @return Collection |
|||
395 | 3 | */ |
|||
396 | public function paginate(int $perPage, int $page): Collection |
||||
397 | { |
||||
398 | $offset = ($page - 1) * $perPage; |
||||
399 | return new static(array_slice($this->items, $offset, $perPage)); |
||||
0 ignored issues
–
show
$this->items of type iterable is incompatible with the type array expected by parameter $array of array_slice() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
400 | } |
||||
401 | |||||
402 | /** |
||||
403 | 3 | * {@inheritDoc} |
|||
404 | */ |
||||
405 | 3 | public function serialize() |
|||
406 | 3 | { |
|||
407 | return serialize($this->items); |
||||
408 | } |
||||
409 | |||||
410 | /** |
||||
411 | * {@inheritDoc} |
||||
412 | 3 | */ |
|||
413 | public function unserialize($serialized) |
||||
414 | 3 | { |
|||
415 | $this->items = unserialize($serialized); |
||||
416 | } |
||||
417 | |||||
418 | /** |
||||
419 | * @return mixed |
||||
420 | 3 | */ |
|||
421 | public function pipe(callable $callback) |
||||
422 | 3 | { |
|||
423 | 3 | return $callback($this); |
|||
424 | } |
||||
425 | |||||
426 | public function __debugInfo() |
||||
427 | { |
||||
428 | 3 | return $this->toArray(); |
|||
429 | } |
||||
430 | 3 | ||||
431 | /** |
||||
432 | * {@inheritDoc} |
||||
433 | 3 | * |
|||
434 | * @return iterable |
||||
435 | 3 | */ |
|||
436 | public function all(): iterable |
||||
437 | { |
||||
438 | return $this->toArray(); |
||||
439 | } |
||||
440 | } |
||||
441 |