PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.22
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.22
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 / LazyCollection.php

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

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