PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.7.7
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.7.7
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 1.1.0 All 77 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Support / LazyCollection.php

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

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