PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.23
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.23
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Support / Collection.php

Collection.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.5.23, at vendor/wpfluent/framework/src/WPFluent/Support/Collection.php

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