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