PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.7.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.7.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Support / Collection.php

Collection.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.7.0, at vendor/wpfluent/framework/src/WPFluent/Support/Collection.php

1,755 lines 42.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\Framework\Support;
4
5 use stdClass;
6 use ArrayAccess;
7 use ArrayIterator;
8 use FluentCommunity\Framework\Support\Helper;
9 use FluentCommunity\Framework\Support\MacroableTrait;
10 use FluentCommunity\Framework\Support\EnumeratesValues;
11 use FluentCommunity\Framework\Support\CanBeEscapedWhenCastToString;
12
13 class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable
14 {
15 use EnumeratesValues, MacroableTrait;
16
17 /**
18 * Pass only key to any callback.
19 */
20 const USE_KEY = 1;
21
22 /**
23 * Pass both key/value to any callback.
24 */
25 const USE_BOTH = 2;
26
27 /**
28 * The items contained in the collection.
29 *
30 * @var array
31 */
32 protected $items = [];
33
34 /**
35 * Create a new collection.
36 *
37 * @param mixed $items
38 * @return void
39 */
40 public function __construct($items = [])
41 {
42 $this->items = $this->getArrayableItems($items);
43 }
44
45 /**
46 * Create a new collection instance.
47 *
48 * @param array $items
49 * @return static
50 */
51 public static function of($items)
52 {
53 return new static($items);
54 }
55
56 /**
57 * Create a collection with the given range.
58 *
59 * @param int $from
60 * @param int $to
61 * @return static
62 */
63 public static function range($from, $to)
64 {
65 return new static(range($from, $to));
66 }
67
68 /**
69 * Get all of the items in the collection.
70 *
71 * @return array
72 */
73 public function all()
74 {
75 return $this->items;
76 }
77
78 /**
79 * Get a lazy collection for the items in this collection.
80 *
81 * @return \FluentCommunity\Framework\Support\LazyCollection
82 */
83 public function lazy()
84 {
85 return new LazyCollection($this->items);
86 }
87
88 /**
89 * Get the average value of a given key.
90 *
91 * @param callable|string|null $callback
92 * @return mixed
93 */
94 public function avg($callback = null)
95 {
96 $callback = $this->valueRetriever($callback);
97
98 $items = $this->map(function ($value) use ($callback) {
99 return $callback($value);
100 })->filter(function ($value) {
101 return ! is_null($value);
102 });
103
104 if ($count = $items->count()) {
105 return $items->sum() / $count;
106 }
107 }
108
109 /**
110 * Get the median of a given key.
111 *
112 * @param string|array|null $key
113 * @return mixed
114 */
115 public function median($key = null)
116 {
117 $values = (isset($key) ? $this->pluck($key) : $this)
118 ->filter(function ($item) {
119 return ! is_null($item);
120 })->sort()->values();
121
122 $count = $values->count();
123
124 if ($count === 0) {
125 return;
126 }
127
128 $middle = (int) ($count / 2);
129
130 if ($count % 2) {
131 return $values->get($middle);
132 }
133
134 return (new static([
135 $values->get($middle - 1), $values->get($middle),
136 ]))->average();
137 }
138
139 /**
140 * Get the mode of a given key.
141 *
142 * @param string|array|null $key
143 * @return array|null
144 */
145 public function mode($key = null)
146 {
147 if ($this->count() === 0) {
148 return;
149 }
150
151 $collection = isset($key) ? $this->pluck($key) : $this;
152
153 $counts = new static;
154
155 $collection->each(function ($value) use ($counts) {
156 $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1;
157 });
158
159 $sorted = $counts->sort();
160
161 $highestValue = $sorted->last();
162
163 return $sorted->filter(function ($value) use ($highestValue) {
164 return $value == $highestValue;
165 })->sort()->keys()->all();
166 }
167
168 /**
169 * Collapse the collection of items into a single array.
170 *
171 * @return static
172 */
173 public function collapse()
174 {
175 return new static(Arr::collapse($this->items));
176 }
177
178 /**
179 * Determine if an item exists in the collection.
180 *
181 * @param mixed $key
182 * @param mixed $operator
183 * @param mixed $value
184 * @return bool
185 */
186 public function contains($key, $operator = null, $value = null)
187 {
188 if (func_num_args() === 1) {
189 if ($this->useAsCallable($key)) {
190 $placeholder = new stdClass;
191
192 return $this->first($key, $placeholder) !== $placeholder;
193 }
194
195 return in_array($key, $this->items);
196 }
197
198 return $this->contains($this->operatorForWhere(...func_get_args()));
199 }
200
201 /**
202 * Determine if an item is not contained in the collection.
203 *
204 * @param mixed $key
205 * @param mixed $operator
206 * @param mixed $value
207 * @return bool
208 */
209 public function doesntContain($key, $operator = null, $value = null)
210 {
211 return ! $this->contains(...func_get_args());
212 }
213
214 /**
215 * Cross join with the given lists, returning all possible permutations.
216 *
217 * @param mixed ...$lists
218 * @return static
219 */
220 public function crossJoin(...$lists)
221 {
222 return new static(Arr::crossJoin(
223 $this->items, ...array_map([$this, 'getArrayableItems'], $lists)
224 ));
225 }
226
227 /**
228 * Get the items in the collection that are not present in the given items.
229 *
230 * @param mixed $items
231 * @return static
232 */
233 public function diff($items)
234 {
235 return new static(array_diff($this->items, $this->getArrayableItems($items)));
236 }
237
238 /**
239 * Get the items in the collection that are not present in the given items, using the callback.
240 *
241 * @param mixed $items
242 * @param callable $callback
243 * @return static
244 */
245 public function diffUsing($items, callable $callback)
246 {
247 return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback));
248 }
249
250 /**
251 * Get the items in the collection whose keys and values are not present in the given items.
252 *
253 * @param mixed $items
254 * @return static
255 */
256 public function diffAssoc($items)
257 {
258 return new static(array_diff_assoc($this->items, $this->getArrayableItems($items)));
259 }
260
261 /**
262 * Get the items in the collection whose keys and values are not present in the given items, using the callback.
263 *
264 * @param mixed $items
265 * @param callable $callback
266 * @return static
267 */
268 public function diffAssocUsing($items, callable $callback)
269 {
270 return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback));
271 }
272
273 /**
274 * Get the items in the collection whose keys are not present in the given items.
275 *
276 * @param mixed $items
277 * @return static
278 */
279 public function diffKeys($items)
280 {
281 return new static(array_diff_key($this->items, $this->getArrayableItems($items)));
282 }
283
284 /**
285 * Get the items in the collection whose keys are not present in the given items, using the callback.
286 *
287 * @param mixed $items
288 * @param callable $callback
289 * @return static
290 */
291 public function diffKeysUsing($items, callable $callback)
292 {
293 return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback));
294 }
295
296 /**
297 * Retrieve duplicate items from the collection.
298 *
299 * @param callable|string|null $callback
300 * @param bool $strict
301 * @return static
302 */
303 public function duplicates($callback = null, $strict = false)
304 {
305 $items = $this->map($this->valueRetriever($callback));
306
307 $uniqueItems = $items->unique(null, $strict);
308
309 $compare = $this->duplicateComparator($strict);
310
311 $duplicates = new static;
312
313 foreach ($items as $key => $value) {
314 if ($uniqueItems->isNotEmpty() && $compare($value, $uniqueItems->first())) {
315 $uniqueItems->shift();
316 } else {
317 $duplicates[$key] = $value;
318 }
319 }
320
321 return $duplicates;
322 }
323
324 /**
325 * Retrieve duplicate items from the collection using strict comparison.
326 *
327 * @param callable|string|null $callback
328 * @return static
329 */
330 public function duplicatesStrict($callback = null)
331 {
332 return $this->duplicates($callback, true);
333 }
334
335 /**
336 * Get the comparison function to detect duplicates.
337 *
338 * @param bool $strict
339 * @return \Closure
340 */
341 protected function duplicateComparator($strict)
342 {
343 if ($strict) {
344 return function ($a, $b) {
345 return $a === $b;
346 };
347 }
348
349 return function ($a, $b) {
350 return $a == $b;
351 };
352 }
353
354 /**
355 * Get all items except for those with the specified keys.
356 *
357 * @param \FluentCommunity\Framework\Support\Collection|mixed $keys
358 * @return static
359 */
360 public function except($keys)
361 {
362 if ($keys instanceof Enumerable) {
363 $keys = $keys->all();
364 } elseif (! is_array($keys)) {
365 $keys = func_get_args();
366 }
367
368 return new static(Arr::except($this->items, $keys));
369 }
370
371 /**
372 * Filter the collection using SQL like where.
373 *
374 * @param string $key
375 * @param string|null $operator
376 * @param mixed $value
377 * @return static
378 */
379 public function where($key, $operator = null, $value = null)
380 {
381 return $this->filter(
382 is_callable($key) ? $key : $this->operatorForWhere(
383 ...func_get_args()
384 )
385 );
386 }
387
388 /**
389 * Run a filter over each of the items.
390 *
391 * @param callable|null $callback
392 * @return static
393 */
394 public function filter(?callable $callback = null)
395 {
396 if ($callback) {
397 return new static(Arr::where($this->items, $callback));
398 }
399
400 return new static(array_filter($this->items));
401 }
402
403 /**
404 * Pass the items through a series of callbacks.
405 *
406 * @param array $callbacks
407 * @param integer $mode
408 * @return self
409 */
410 public function passThrough(array $callbacks, $mode = 0)
411 {
412 return new static(
413 Arr::passThrough($this->items, $callbacks, $mode)
414 );
415 }
416
417 /**
418 * Get the first item from the collection passing the given truth test.
419 *
420 * @param callable|null $callback
421 * @param mixed $default
422 * @return mixed
423 */
424 public function first(?callable $callback = null, $default = null)
425 {
426 return Arr::first($this->items, $callback, $default);
427 }
428
429 /**
430 * Get a flattened array of the items in the collection.
431 *
432 * @param int|float $depth
433 * @return static
434 */
435 public function flatten($depth = INF)
436 {
437 return new static(Arr::flatten($this->items, $depth));
438 }
439
440 /**
441 * Flip the items in the collection.
442 *
443 * @return static
444 */
445 public function flip()
446 {
447 return new static(array_flip($this->items));
448 }
449
450 /**
451 * Remove an item from the collection by key.
452 *
453 * @param string|int|array $keys
454 * @return $this
455 */
456 public function forget($keys)
457 {
458 foreach ((array) $keys as $key) {
459 $this->offsetUnset($key);
460 }
461
462 return $this;
463 }
464
465 /**
466 * Get an item from the collection by key.
467 *
468 * @param mixed $key
469 * @param mixed $default
470 * @return mixed
471 */
472 public function get($key, $default = null)
473 {
474 if (array_key_exists($key, $this->items)) {
475 return $this->items[$key];
476 }
477
478 return Helper::value($default);
479 }
480
481 /**
482 * Get an item from the collection by key or add it to collection if it does not exist.
483 *
484 * @param mixed $key
485 * @param mixed $value
486 * @return mixed
487 */
488 public function getOrPut($key, $value)
489 {
490 if (array_key_exists($key, $this->items)) {
491 return $this->items[$key];
492 }
493
494 $this->offsetSet($key, $value = Helper::value($value));
495
496 return $value;
497 }
498
499 /**
500 * Group an associative array by a field or using a callback.
501 *
502 * @param array|callable|string $groupBy
503 * @param bool $preserveKeys
504 * @return static
505 */
506 public function groupBy($groupBy, $preserveKeys = false)
507 {
508 if (! $this->useAsCallable($groupBy) && is_array($groupBy)) {
509 $nextGroups = $groupBy;
510
511 $groupBy = array_shift($nextGroups);
512 }
513
514 $groupBy = $this->valueRetriever($groupBy);
515
516 $results = [];
517
518 foreach ($this->items as $key => $value) {
519 $groupKeys = $groupBy($value, $key);
520
521 if (! is_array($groupKeys)) {
522 $groupKeys = [$groupKeys];
523 }
524
525 foreach ($groupKeys as $groupKey) {
526 $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey;
527
528 if (! array_key_exists($groupKey, $results)) {
529 $results[$groupKey] = new static;
530 }
531
532 $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value);
533 }
534 }
535
536 $result = new static($results);
537
538 if (! empty($nextGroups)) {
539 return $result->map->groupBy($nextGroups, $preserveKeys);
540 }
541
542 return $result;
543 }
544
545 /**
546 * Key an associative array by a field or using a callback.
547 *
548 * @param callable|string $keyBy
549 * @return static
550 */
551 public function keyBy($keyBy)
552 {
553 $keyBy = $this->valueRetriever($keyBy);
554
555 $results = [];
556
557 foreach ($this->items as $key => $item) {
558 $resolvedKey = $keyBy($item, $key);
559
560 if (is_object($resolvedKey)) {
561 $resolvedKey = (string) $resolvedKey;
562 }
563
564 $results[$resolvedKey] = $item;
565 }
566
567 return new static($results);
568 }
569
570 /**
571 * Determine if an item exists in the collection by key.
572 *
573 * @param mixed $key
574 * @return bool
575 */
576 public function has($key)
577 {
578 $keys = is_array($key) ? $key : func_get_args();
579
580 foreach ($keys as $value) {
581 if (! array_key_exists($value, $this->items)) {
582 return false;
583 }
584 }
585
586 return true;
587 }
588
589 /**
590 * Determine if any of the keys exist in the collection.
591 *
592 * @param mixed $key
593 * @return bool
594 */
595 public function hasAny($key)
596 {
597 if ($this->isEmpty()) {
598 return false;
599 }
600
601 $keys = is_array($key) ? $key : func_get_args();
602
603 foreach ($keys as $value) {
604 if ($this->has($value)) {
605 return true;
606 }
607 }
608
609 return false;
610 }
611
612 /**
613 * Concatenate values of a given key as a string.
614 *
615 * @param string $value
616 * @param string|null $glue
617 * @return string
618 */
619 public function implode($value, $glue = null)
620 {
621 $first = $this->first();
622
623 if (is_array($first) || (is_object($first) && ! $first instanceof Stringable)) {
624 return implode($glue ?? '', $this->pluck($value)->all());
625 }
626
627 // @phpstan-ignore-next-line
628 return implode($value ?? '', $this->items);
629 }
630
631 /**
632 * Intersect the collection with the given items.
633 *
634 * @param mixed $items
635 * @return static
636 */
637 public function intersect($items)
638 {
639 return new static(array_intersect($this->items, $this->getArrayableItems($items)));
640 }
641
642 /**
643 * Intersect the collection with the given items,
644 * comparing both keys and values.
645 *
646 * @param mixed $items
647 * @return static
648 */
649 public function intersectAssoc($items)
650 {
651 return new static(
652 array_intersect_assoc(
653 $this->items,
654 $this->getArrayableItems($items)
655 )
656 );
657 }
658
659 /**
660 * Intersect the collection with the given items by key.
661 *
662 * @param mixed $items
663 * @return static
664 */
665 public function intersectByKeys($items)
666 {
667 return new static(array_intersect_key(
668 $this->items, $this->getArrayableItems($items)
669 ));
670 }
671
672 /**
673 * Determine if the collection is empty or not.
674 *
675 * @return bool
676 */
677 public function isEmpty()
678 {
679 return empty($this->items);
680 }
681
682 /**
683 * Determine if the collection contains a single item.
684 *
685 * @return bool
686 */
687 public function containsOneItem()
688 {
689 return $this->count() === 1;
690 }
691
692 /**
693 * Join all items from the collection using a string. The final items can use a separate glue string.
694 *
695 * @param string $glue
696 * @param string $finalGlue
697 * @return string
698 */
699 public function join($glue, $finalGlue = '')
700 {
701 if ($finalGlue === '') {
702 return $this->implode($glue);
703 }
704
705 $count = $this->count();
706
707 if ($count === 0) {
708 return '';
709 }
710
711 if ($count === 1) {
712 return $this->last();
713 }
714
715 $collection = new static($this->items);
716
717 $finalItem = $collection->pop();
718
719 return $collection->implode($glue).$finalGlue.$finalItem;
720 }
721
722 /**
723 * Get the keys of the collection items.
724 *
725 * @return static
726 */
727 public function keys()
728 {
729 return new static(array_keys($this->items));
730 }
731
732 /**
733 * Get the last item from the collection.
734 *
735 * @param callable|null $callback
736 * @param mixed $default
737 * @return mixed
738 */
739 public function last(?callable $callback = null, $default = null)
740 {
741 return Arr::last($this->items, $callback, $default);
742 }
743
744 /**
745 * Get the values of a given key.
746 *
747 * @param string|array|int|null $value
748 * @param string|null $key
749 * @return static
750 */
751 public function pluck($value, $key = null)
752 {
753 return new static(Arr::pluck($this->items, $value, $key));
754 }
755
756 /**
757 * Run a map over each of the items.
758 *
759 * @param callable $callback
760 * @return static
761 */
762 public function map(callable $callback)
763 {
764 $keys = array_keys($this->items);
765
766 $items = array_map($callback, $this->items, $keys);
767
768 return new static(array_combine($keys, $items));
769 }
770
771 /**
772 * Run a dictionary map over the items.
773 *
774 * The callback should return an associative array with a single key/value pair.
775 *
776 * @param callable $callback
777 * @return static
778 */
779 public function mapToDictionary(callable $callback)
780 {
781 $dictionary = [];
782
783 foreach ($this->items as $key => $item) {
784 $pair = $callback($item, $key);
785
786 $key = key($pair);
787
788 $value = reset($pair);
789
790 if (! isset($dictionary[$key])) {
791 $dictionary[$key] = [];
792 }
793
794 $dictionary[$key][] = $value;
795 }
796
797 return new static($dictionary);
798 }
799
800 /**
801 * Run an associative map over each of the items.
802 *
803 * The callback should return an associative array with a single key/value pair.
804 *
805 * @param callable $callback
806 * @return static
807 */
808 public function mapWithKeys(callable $callback)
809 {
810 $result = [];
811
812 foreach ($this->items as $key => $value) {
813 $assoc = $callback($value, $key);
814
815 foreach ($assoc as $mapKey => $mapValue) {
816 $result[$mapKey] = $mapValue;
817 }
818 }
819
820 return new static($result);
821 }
822
823 /**
824 * Merge the collection with the given items.
825 *
826 * @param mixed $items
827 * @return static
828 */
829 public function merge($items)
830 {
831 return new static(array_merge($this->items, $this->getArrayableItems($items)));
832 }
833
834 /**
835 * Recursively merge the collection with the given items.
836 *
837 * @param mixed $items
838 * @return static
839 */
840 public function mergeRecursive($items)
841 {
842 return new static(array_merge_recursive($this->items, $this->getArrayableItems($items)));
843 }
844
845 /**
846 * Create a collection by using this collection for keys and another for its values.
847 *
848 * @param mixed $values
849 * @return static
850 */
851 public function combine($values)
852 {
853 return new static(array_combine($this->all(), $this->getArrayableItems($values)));
854 }
855
856 /**
857 * Union the collection with the given items.
858 *
859 * @param mixed $items
860 * @return static
861 */
862 public function union($items)
863 {
864 return new static($this->items + $this->getArrayableItems($items));
865 }
866
867 /**
868 * Create a new collection consisting of every n-th element.
869 *
870 * @param int $step
871 * @param int $offset
872 * @return static
873 */
874 public function nth($step, $offset = 0)
875 {
876 $new = [];
877
878 $position = 0;
879
880 foreach ($this->slice($offset)->items as $item) {
881 if ($position % $step === 0) {
882 $new[] = $item;
883 }
884
885 $position++;
886 }
887
888 return new static($new);
889 }
890
891 /**
892 * Get the items with the specified keys.
893 *
894 * @param mixed $keys
895 * @return static
896 */
897 public function only($keys)
898 {
899 if (is_null($keys)) {
900 return new static($this->items);
901 }
902
903 if ($keys instanceof Enumerable) {
904 $keys = $keys->all();
905 }
906
907 $keys = is_array($keys) ? $keys : func_get_args();
908
909 return new static(Arr::only($this->items, $keys));
910 }
911
912 /**
913 * Get and remove the last N items from the collection.
914 *
915 * @param int $count
916 * @return mixed
917 */
918 public function pop($count = 1)
919 {
920 if ($count === 1) {
921 return array_pop($this->items);
922 }
923
924 if ($this->isEmpty()) {
925 return new static;
926 }
927
928 $results = [];
929
930 $collectionCount = $this->count();
931
932 foreach (range(1, min($count, $collectionCount)) as $item) {
933 array_push($results, array_pop($this->items));
934 }
935
936 return new static($results);
937 }
938
939 /**
940 * Push an item onto the beginning of the collection.
941 *
942 * @param mixed $value
943 * @param mixed $key
944 * @return $this
945 */
946 public function prepend($value, $key = null)
947 {
948 $this->items = Arr::prepend($this->items, ...func_get_args());
949
950 return $this;
951 }
952
953 /**
954 * Push one or more items onto the end of the collection.
955 *
956 * @param mixed $values
957 * @return $this
958 */
959 public function push(...$values)
960 {
961 foreach ($values as $value) {
962 $this->items[] = $value;
963 }
964
965 return $this;
966 }
967
968 /**
969 * Push all of the given items onto the collection.
970 *
971 * @param iterable $source
972 * @return static
973 */
974 public function concat($source)
975 {
976 $result = new static($this);
977
978 foreach ($source as $item) {
979 $result->push($item);
980 }
981
982 return $result;
983 }
984
985 /**
986 * Get and remove an item from the collection.
987 *
988 * @param mixed $key
989 * @param mixed $default
990 * @return mixed
991 */
992 public function pull($key, $default = null)
993 {
994 return Arr::pull($this->items, $key, $default);
995 }
996
997 /**
998 * Put an item in the collection by key.
999 *
1000 * @param mixed $key
1001 * @param mixed $value
1002 * @return $this
1003 */
1004 public function put($key, $value)
1005 {
1006 $this->offsetSet($key, $value);
1007
1008 return $this;
1009 }
1010
1011 /**
1012 * Get one or a specified number of items randomly from the collection.
1013 *
1014 * @param int|null $number
1015 * @return static|mixed
1016 *
1017 * @throws \InvalidArgumentException
1018 */
1019 public function random($number = null)
1020 {
1021 if (is_null($number)) {
1022 return Arr::random($this->items);
1023 }
1024
1025 return new static(Arr::random($this->items, $number));
1026 }
1027
1028 /**
1029 * Replace the collection items with the given items.
1030 *
1031 * @param mixed $items
1032 * @return static
1033 */
1034 public function replace($items)
1035 {
1036 return new static(array_replace($this->items, $this->getArrayableItems($items)));
1037 }
1038
1039 /**
1040 * Recursively replace the collection items with the given items.
1041 *
1042 * @param mixed $items
1043 * @return static
1044 */
1045 public function replaceRecursive($items)
1046 {
1047 return new static(array_replace_recursive($this->items, $this->getArrayableItems($items)));
1048 }
1049
1050 /**
1051 * Reverse items order.
1052 *
1053 * @return static
1054 */
1055 public function reverse()
1056 {
1057 return new static(array_reverse($this->items, true));
1058 }
1059
1060 /**
1061 * Search the collection for a given value and return the corresponding key if successful.
1062 *
1063 * @param mixed $value
1064 * @param bool $strict
1065 * @return mixed
1066 */
1067 public function search($value, $strict = false)
1068 {
1069 if (! $this->useAsCallable($value)) {
1070 return array_search($value, $this->items, $strict);
1071 }
1072
1073 foreach ($this->items as $key => $item) {
1074 if ($value($item, $key)) {
1075 return $key;
1076 }
1077 }
1078
1079 return false;
1080 }
1081
1082 /**
1083 * Get and remove the first N items from the collection.
1084 *
1085 * @param int $count
1086 * @return mixed
1087 */
1088 public function shift($count = 1)
1089 {
1090 if ($count === 1) {
1091 return array_shift($this->items);
1092 }
1093
1094 if ($this->isEmpty()) {
1095 return new static;
1096 }
1097
1098 $results = [];
1099
1100 $collectionCount = $this->count();
1101
1102 foreach (range(1, min($count, $collectionCount)) as $item) {
1103 array_push($results, array_shift($this->items));
1104 }
1105
1106 return new static($results);
1107 }
1108
1109 /**
1110 * Shuffle the items in the collection.
1111 *
1112 * @param int|null $seed
1113 * @return static
1114 */
1115 public function shuffle($seed = null)
1116 {
1117 return new static(Arr::shuffle($this->items, $seed));
1118 }
1119
1120 /**
1121 * Create chunks representing a "sliding window" view of the items in the collection.
1122 *
1123 * @param int $size
1124 * @param int $step
1125 * @return static
1126 */
1127 public function sliding($size = 2, $step = 1)
1128 {
1129 $chunks = floor(($this->count() - $size) / $step) + 1;
1130
1131 return static::times($chunks, function ($number) use ($size, $step) {
1132 return $this->slice(($number - 1) * $step, $size);
1133 });
1134 }
1135
1136 /**
1137 * Skip the first {$count} items.
1138 *
1139 * @param int $count
1140 * @return static
1141 */
1142 public function skip($count)
1143 {
1144 return $this->slice($count);
1145 }
1146
1147 /**
1148 * Skip items in the collection until the given condition is met.
1149 *
1150 * @param mixed $value
1151 * @return static
1152 */
1153 public function skipUntil($value)
1154 {
1155 return new static($this->lazy()->skipUntil($value)->all());
1156 }
1157
1158 /**
1159 * Skip items in the collection while the given condition is met.
1160 *
1161 * @param mixed $value
1162 * @return static
1163 */
1164 public function skipWhile($value)
1165 {
1166 return new static($this->lazy()->skipWhile($value)->all());
1167 }
1168
1169 /**
1170 * Slice the underlying collection array.
1171 *
1172 * @param int $offset
1173 * @param int|null $length
1174 * @return static
1175 */
1176 public function slice($offset, $length = null)
1177 {
1178 return new static(array_slice($this->items, $offset, $length, true));
1179 }
1180
1181 /**
1182 * Split a collection into a certain number of groups.
1183 *
1184 * @param int $numberOfGroups
1185 * @return static
1186 */
1187 public function split($numberOfGroups)
1188 {
1189 if ($this->isEmpty()) {
1190 return new static;
1191 }
1192
1193 $groups = new static;
1194
1195 $groupSize = floor($this->count() / $numberOfGroups);
1196
1197 $remain = $this->count() % $numberOfGroups;
1198
1199 $start = 0;
1200
1201 for ($i = 0; $i < $numberOfGroups; $i++) {
1202 $size = $groupSize;
1203
1204 if ($i < $remain) {
1205 $size++;
1206 }
1207
1208 if ($size) {
1209 $groups->push(new static(array_slice($this->items, $start, $size)));
1210
1211 $start += $size;
1212 }
1213 }
1214
1215 return $groups;
1216 }
1217
1218 /**
1219 * Split a collection into a certain number of groups, and fill the first groups completely.
1220 *
1221 * @param int $numberOfGroups
1222 * @return static
1223 */
1224 public function splitIn($numberOfGroups)
1225 {
1226 return $this->chunk(ceil($this->count() / $numberOfGroups));
1227 }
1228
1229 /**
1230 * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
1231 *
1232 * @param mixed $key
1233 * @param mixed $operator
1234 * @param mixed $value
1235 * @return mixed
1236 *
1237 * @throws \FluentCommunity\Framework\Support\ItemNotFoundException
1238 * @throws \FluentCommunity\Framework\Support\MultipleItemsFoundException
1239 */
1240 public function sole($key = null, $operator = null, $value = null)
1241 {
1242 $filter = func_num_args() > 1
1243 ? $this->operatorForWhere(...func_get_args())
1244 : $key;
1245
1246 $items = $this->when($filter)->filter($filter);
1247
1248 if ($items->isEmpty()) {
1249 throw new ItemNotFoundException;
1250 }
1251
1252 if ($items->count() > 1) {
1253 throw new MultipleItemsFoundException;
1254 }
1255
1256 return $items->first();
1257 }
1258
1259 /**
1260 * Get the first item in the collection but throw an exception if no matching items exist.
1261 *
1262 * @param mixed $key
1263 * @param mixed $operator
1264 * @param mixed $value
1265 * @return mixed
1266 *
1267 * @throws \FluentCommunity\Framework\Support\ItemNotFoundException
1268 */
1269 public function firstOrFail($key = null, $operator = null, $value = null)
1270 {
1271 $filter = func_num_args() > 1
1272 ? $this->operatorForWhere(...func_get_args())
1273 : $key;
1274
1275 $placeholder = new stdClass();
1276
1277 $item = $this->first($filter, $placeholder);
1278
1279 if ($item === $placeholder) {
1280 throw new ItemNotFoundException;
1281 }
1282
1283 return $item;
1284 }
1285
1286 /**
1287 * Chunk the collection into chunks of the given size.
1288 *
1289 * @param int $size
1290 * @return static
1291 */
1292 public function chunk($size)
1293 {
1294 if ($size <= 0) {
1295 return new static;
1296 }
1297
1298 $chunks = [];
1299
1300 foreach (array_chunk($this->items, $size, true) as $chunk) {
1301 $chunks[] = new static($chunk);
1302 }
1303
1304 return new static($chunks);
1305 }
1306
1307 /**
1308 * Chunk the collection into chunks with a callback.
1309 *
1310 * @param callable $callback
1311 * @return static
1312 */
1313 public function chunkWhile(callable $callback)
1314 {
1315 return new static(
1316 $this->lazy()->chunkWhile($callback)->mapInto(static::class)
1317 );
1318 }
1319
1320 /**
1321 * Sort through each item with a callback.
1322 *
1323 * @param callable|int|null $callback
1324 * @return static
1325 */
1326 public function sort($callback = null)
1327 {
1328 $items = $this->items;
1329
1330 $callback && is_callable($callback)
1331 ? uasort($items, $callback)
1332 : asort($items, $callback ?? SORT_REGULAR);
1333
1334 return new static($items);
1335 }
1336
1337 /**
1338 * Sort items in descending order.
1339 *
1340 * @param int $options
1341 * @return static
1342 */
1343 public function sortDesc($options = SORT_REGULAR)
1344 {
1345 $items = $this->items;
1346
1347 arsort($items, $options);
1348
1349 return new static($items);
1350 }
1351
1352 /**
1353 * Sort the collection using the given callback.
1354 *
1355 * @param callable|array|string $callback
1356 * @param int $options
1357 * @param bool $descending
1358 * @return static
1359 */
1360 public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
1361 {
1362 if (is_array($callback) && ! is_callable($callback)) {
1363 return $this->sortByMany($callback);
1364 }
1365
1366 $results = [];
1367
1368 $callback = $this->valueRetriever($callback);
1369
1370 // First we will loop through the items and get the comparator from a callback
1371 // function which we were given. Then, we will sort the returned values and
1372 // grab all the corresponding values for the sorted keys from this array.
1373 foreach ($this->items as $key => $value) {
1374 $results[$key] = $callback($value, $key);
1375 }
1376
1377 $descending ? arsort($results, $options)
1378 : asort($results, $options);
1379
1380 // Once we have sorted all of the keys in the array, we will loop through them
1381 // and grab the corresponding model so we can set the underlying items list
1382 // to the sorted version. Then we'll just return the collection instance.
1383 foreach (array_keys($results) as $key) {
1384 $results[$key] = $this->items[$key];
1385 }
1386
1387 return new static($results);
1388 }
1389
1390 /**
1391 * Sort the collection using multiple comparisons.
1392 *
1393 * @param array $comparisons
1394 * @return static
1395 */
1396 protected function sortByMany(array $comparisons = [])
1397 {
1398 $items = $this->items;
1399
1400 usort($items, function ($a, $b) use ($comparisons) {
1401 foreach ($comparisons as $comparison) {
1402 $comparison = Arr::wrap($comparison);
1403
1404 $prop = $comparison[0];
1405
1406 $ascending = Arr::get($comparison, 1, true) === true ||
1407 Arr::get($comparison, 1, true) === 'asc';
1408
1409 $result = 0;
1410
1411 if (! is_string($prop) && is_callable($prop)) {
1412 $result = $prop($a, $b);
1413 } else {
1414 $values = [Helper::dataGet($a, $prop), Helper::dataGet($b, $prop)];
1415
1416 if (! $ascending) {
1417 $values = array_reverse($values);
1418 }
1419
1420 $result = $values[0] <=> $values[1];
1421 }
1422
1423 if ($result === 0) {
1424 continue;
1425 }
1426
1427 return $result;
1428 }
1429 });
1430
1431 return new static($items);
1432 }
1433
1434 /**
1435 * Sort the collection in descending order using the given callback.
1436 *
1437 * @param callable|string $callback
1438 * @param int $options
1439 * @return static
1440 */
1441 public function sortByDesc($callback, $options = SORT_REGULAR)
1442 {
1443 return $this->sortBy($callback, $options, true);
1444 }
1445
1446 /**
1447 * Sort the collection keys.
1448 *
1449 * @param int $options
1450 * @param bool $descending
1451 * @return static
1452 */
1453 public function sortKeys($options = SORT_REGULAR, $descending = false)
1454 {
1455 $items = $this->items;
1456
1457 $descending ? krsort($items, $options) : ksort($items, $options);
1458
1459 return new static($items);
1460 }
1461
1462 /**
1463 * Sort the collection keys in descending order.
1464 *
1465 * @param int $options
1466 * @return static
1467 */
1468 public function sortKeysDesc($options = SORT_REGULAR)
1469 {
1470 return $this->sortKeys($options, true);
1471 }
1472
1473 /**
1474 * Sort the collection keys using a callback.
1475 *
1476 * @param callable $callback
1477 * @return static
1478 */
1479 public function sortKeysUsing(callable $callback)
1480 {
1481 $items = $this->items;
1482
1483 uksort($items, $callback);
1484
1485 return new static($items);
1486 }
1487
1488 /**
1489 * Splice a portion of the underlying collection array.
1490 *
1491 * @param int $offset
1492 * @param int|null $length
1493 * @param mixed $replacement
1494 * @return static
1495 */
1496 public function splice($offset, $length = null, $replacement = [])
1497 {
1498 if (func_num_args() === 1) {
1499 return new static(array_splice($this->items, $offset));
1500 }
1501
1502 return new static(array_splice($this->items, $offset, $length, $this->getArrayableItems($replacement)));
1503 }
1504
1505 /**
1506 * Take the first or last {$limit} items.
1507 *
1508 * @param int $limit
1509 * @return static
1510 */
1511 public function take($limit)
1512 {
1513 if ($limit < 0) {
1514 return $this->slice($limit, abs($limit));
1515 }
1516
1517 return $this->slice(0, $limit);
1518 }
1519
1520 /**
1521 * Take items in the collection until the given condition is met.
1522 *
1523 * @param mixed $value
1524 * @return static
1525 */
1526 public function takeUntil($value)
1527 {
1528 return new static($this->lazy()->takeUntil($value)->all());
1529 }
1530
1531 /**
1532 * Take items in the collection while the given condition is met.
1533 *
1534 * @param mixed $value
1535 * @return static
1536 */
1537 public function takeWhile($value)
1538 {
1539 return new static($this->lazy()->takeWhile($value)->all());
1540 }
1541
1542 /**
1543 * Transform each item in the collection using a callback.
1544 *
1545 * @param callable $callback
1546 * @return $this
1547 */
1548 public function transform(callable $callback)
1549 {
1550 $this->items = $this->map($callback)->all();
1551
1552 return $this;
1553 }
1554
1555 /**
1556 * Flattens a multi-dimensional collection into
1557 * a single level collection with dots.
1558 *
1559 * @return static
1560 */
1561 public function dot()
1562 {
1563 return new static(Arr::dot($this->all()));
1564 }
1565
1566 /**
1567 * Convert a flatten "dot" notation array into an expanded array.
1568 *
1569 * @return static
1570 */
1571 public function undot()
1572 {
1573 return new static(Arr::undot($this->all()));
1574 }
1575
1576 /**
1577 * Return only unique items from the collection array.
1578 *
1579 * @param string|callable|null $key
1580 * @param bool $strict
1581 * @return static
1582 */
1583 public function unique($key = null, $strict = false)
1584 {
1585 if (is_null($key) && $strict === false) {
1586 return new static(array_unique($this->items, SORT_REGULAR));
1587 }
1588
1589 $callback = $this->valueRetriever($key);
1590
1591 $exists = [];
1592
1593 return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
1594 if (in_array($id = $callback($item, $key), $exists, $strict)) {
1595 return true;
1596 }
1597
1598 $exists[] = $id;
1599 });
1600 }
1601
1602 /**
1603 * Reset the keys on the underlying array.
1604 *
1605 * @return static
1606 */
1607 public function values()
1608 {
1609 return new static(array_values($this->items));
1610 }
1611
1612 /**
1613 * Zip the collection together with one or more arrays.
1614 *
1615 * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
1616 * => [[1, 4], [2, 5], [3, 6]]
1617 *
1618 * @param mixed ...$items
1619 * @return static
1620 */
1621 public function zip($items)
1622 {
1623 $arrayableItems = array_map(function ($items) {
1624 return $this->getArrayableItems($items);
1625 }, func_get_args());
1626
1627 $params = array_merge([function () {
1628 return new static(func_get_args());
1629 }, $this->items], $arrayableItems);
1630
1631 return new static(array_map(...$params));
1632 }
1633
1634 /**
1635 * Pad collection to the specified length with a value.
1636 *
1637 * @param int $size
1638 * @param mixed $value
1639 * @return static
1640 */
1641 public function pad($size, $value)
1642 {
1643 return new static(array_pad($this->items, $size, $value));
1644 }
1645
1646 /**
1647 * Get an iterator for the items.
1648 *
1649 * @return \ArrayIterator
1650 */
1651 #[\ReturnTypeWillChange]
1652 public function getIterator()
1653 {
1654 return new ArrayIterator($this->items);
1655 }
1656
1657 /**
1658 * Count the number of items in the collection.
1659 *
1660 * @return int
1661 */
1662 #[\ReturnTypeWillChange]
1663 public function count()
1664 {
1665 return count($this->items);
1666 }
1667
1668 /**
1669 * Count the number of items in the collection by a field or using a callback.
1670 *
1671 * @param callable|string $countBy
1672 * @return static
1673 */
1674 public function countBy($countBy = null)
1675 {
1676 return new static($this->lazy()->countBy($countBy)->all());
1677 }
1678
1679 /**
1680 * Add an item to the collection.
1681 *
1682 * @param mixed $item
1683 * @return $this
1684 */
1685 public function add($item)
1686 {
1687 $this->items[] = $item;
1688
1689 return $this;
1690 }
1691
1692 /**
1693 * Get a base Support collection instance from this collection.
1694 *
1695 * @return \FluentCommunity\Framework\Support\Collection
1696 */
1697 public function toBase()
1698 {
1699 return new self($this);
1700 }
1701
1702 /**
1703 * Determine if an item exists at an offset.
1704 *
1705 * @param mixed $key
1706 * @return bool
1707 */
1708 #[\ReturnTypeWillChange]
1709 public function offsetExists($key)
1710 {
1711 return isset($this->items[$key]);
1712 }
1713
1714 /**
1715 * Get an item at a given offset.
1716 *
1717 * @param mixed $key
1718 * @return mixed
1719 */
1720 #[\ReturnTypeWillChange]
1721 public function offsetGet($key)
1722 {
1723 return $this->items[$key];
1724 }
1725
1726 /**
1727 * Set the item at a given offset.
1728 *
1729 * @param mixed $key
1730 * @param mixed $value
1731 * @return void
1732 */
1733 #[\ReturnTypeWillChange]
1734 public function offsetSet($key, $value)
1735 {
1736 if (is_null($key)) {
1737 $this->items[] = $value;
1738 } else {
1739 $this->items[$key] = $value;
1740 }
1741 }
1742
1743 /**
1744 * Unset the item at a given offset.
1745 *
1746 * @param mixed $key
1747 * @return void
1748 */
1749 #[\ReturnTypeWillChange]
1750 public function offsetUnset($key)
1751 {
1752 unset($this->items[$key]);
1753 }
1754 }
1755