LazyCollection.php
TLDR
The LazyCollection.php
file is a part of the Illuminate\Support
namespace in the Laravel framework. It contains the implementation of the LazyCollection
class, which is an implementation of the Enumerable
interface. This class provides lazy evaluation capabilities to collections in Laravel.
Methods
make
Creates a new instance of the LazyCollection
class with the given items.
range
Creates a new collection instance with a range of values from $from
to $to
.
all
Get all items in the collection as an array.
eager
Eager load all items into a new lazy collection backed by an array.
remember
Cache values as they're enumerated.
avg
Get the average value of a given key.
median
Get the median of a given key.
mode
Get the mode of a given key.
collapse
Collapse the collection of items into a single array.
contains
Determine if an item exists in the collection.
containsStrict
Determine if an item exists in the collection using strict comparison.
doesntContain
Determine if an item is not contained in the collection.
crossJoin
Cross join the collection with the given iterables.
countBy
Count the number of items in the collection by a field or using a callback.
diff
Get the items that are not present in the given items.
diffUsing
Get the items that are not present in the given items, using a callback.
diffAssoc
Get the items whose keys and values are not present in the given items.
diffAssocUsing
Get the items whose keys and values are not present in the given items, using a callback.
diffKeys
Get the items whose keys are not present in the given items.
diffKeysUsing
Get the items whose keys are not present in the given items, using a callback.
duplicates
Retrieve duplicate items.
duplicatesStrict
Retrieve duplicate items using strict comparison.
except
Get all items except for those with the specified keys.
filter
Run a filter over each of the items.
first
Get the first item from the collection passing the given truth test.
flatten
Get a flattened list of the items in the collection.
flip
Flip the items in the collection.
get
Get an item by key.
groupBy
Group an associative array by a field or using a callback.
keyBy
Key an associative array by a field or using a callback.
has
Determine if an item exists in the collection by key.
hasAny
Determine if any of the keys exist in the collection.
implode
Concatenate values of a given key as a string.
intersect
Intersect the collection with the given items.
intersectUsing
Intersect the collection with the given items, using a callback.
intersectAssoc
Intersect the collection with the given items by key and value.
intersectAssocUsing
Intersect the collection with the given items by key and value, using a callback.
intersectByKeys
Intersect the collection with the given items by key.
isEmpty
Determine if the collection is empty.
containsOneItem
Determine if the collection contains only one item.
join
Join all items from the collection using a string.
keys
Get the keys of the collection items.
last
Get the last item from the collection.
pluck
Get the values of a given key.
map
Run a map over each of the items.
mapToDictionary
Run a dictionary map over the items.
mapWithKeys
Run an associative map over each of the items.
merge
Merge the collection with the given items.
mergeRecursive
Recursively merge the collection with the given items.
combine
Create a collection by using this collection for keys and another for its values.
union
Union the collection with the given items.
nth
Create a new collection consisting of every n-th element.
only
Get the items with the specified keys.
concat
Push all of the given items onto the collection.
random
Get one or a specified number of items randomly from the collection.
replace
Replace the collection items with the given items.
replaceRecursive
Recursively replace the collection items with the given items.
reverse
Reverse items order.
search
Search the collection for a given value and return the corresponding key if successful.
shuffle
Shuffle the items in the collection.
sliding
Create chunks representing a "sliding window" view of the items in the collection.
skip
Skip the first $count
items.
skipUntil
Skip items in the collection until the given condition is met.
skipWhile
Skip items in the collection while the given condition is met.
slice
Get a slice of items from the collection.
split
Split a collection into a certain number of groups.
splitIn
Split a collection into a certain number of groups, and fill the first groups completely.
sole
Get the first item in the collection, but only if exactly one item exists.
firstOrFail
Get the first item in the collection but throw an exception if no matching items exist.
chunk
Chunk the collection into chunks of the given size.
chunkWhile
Chunk the collection into chunks with a callback.
sort
Sort through each item with a callback.
sortDesc
Sort items in descending order.
sortBy
Sort the collection using the given callback.
sortByDesc
Sort the collection in descending order using the given callback.
sortKeys
Sort the collection keys.
sortKeysDesc
Sort the collection keys in descending order.
sortKeysUsing
Sort the collection keys using a callback.
take
Take the first or last $limit
items.
takeUntil
Take items in the collection until the given condition is met.
takeUntilTimeout
Take items in the collection until a given point in time.
takeWhile
Take items in the collection while the given condition is met.
tapEach
Pass each item in the collection to the given callback, lazily.
dot
Flatten a multi-dimensional associative array with dots.
undot
Convert a flatten "dot" notation array into an expanded array.
unique
Return only unique items from the collection array.
values
Reset the keys on the underlying array.
zip
Zip the collection together with one or more arrays.
pad
Pad collection to the specified length with a value.
getIterator
Get the values iterator.
count
Count the number of items in the collection.
Classes
<?php
namespace Illuminate\Support;
use ArrayIterator;
use Closure;
use DateTimeInterface;
use Generator;
use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString;
use Illuminate\Support\Traits\EnumeratesValues;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use IteratorAggregate;
use stdClass;
use Traversable;
/**
* @template TKey of array-key
*
* @template-covariant TValue
*
* @implements \Illuminate\Support\Enumerable<TKey, TValue>
*/
class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable
{
/**
* @use \Illuminate\Support\Traits\EnumeratesValues<TKey, TValue>
*/
use EnumeratesValues, Macroable;
/**
* The source from which to generate items.
*
* @var (Closure(): \Generator<TKey, TValue, mixed, void>)|static|array<TKey, TValue>
*/
public $source;
/**
* Create a new lazy collection instance.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue>|(Closure(): \Generator<TKey, TValue, mixed, void>)|self<TKey, TValue>|array<TKey, TValue>|null $source
* @return void
*/
public function __construct($source = null)
{
if ($source instanceof Closure || $source instanceof self) {
$this->source = $source;
} elseif (is_null($source)) {
$this->source = static::empty();
} elseif ($source instanceof Generator) {
throw new InvalidArgumentException(
'Generators should not be passed directly to LazyCollection. Instead, pass a generator function.'
);
} else {
$this->source = $this->getArrayableItems($source);
}
}
/**
* Create a new collection instance if the value isn't one already.
*
* @template TMakeKey of array-key
* @template TMakeValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TMakeKey, TMakeValue>|iterable<TMakeKey, TMakeValue>|(Closure(): \Generator<TMakeKey, TMakeValue, mixed, void>)|self<TMakeKey, TMakeValue>|array<TMakeKey, TMakeValue>|null $items
* @return static<TMakeKey, TMakeValue>
*/
public static function make($items = [])
{
return new static($items);
}
/**
* Create a collection with the given range.
*
* @param int $from
* @param int $to
* @return static<int, int>
*/
public static function range($from, $to)
{
return new static(function () use ($from, $to) {
if ($from <= $to) {
for (; $from <= $to; $from++) {
yield $from;
}
} else {
for (; $from >= $to; $from--) {
yield $from;
}
}
});
}
/**
* Get all items in the enumerable.
*
* @return array<TKey, TValue>
*/
public function all()
{
if (is_array($this->source)) {
return $this->source;
}
return iterator_to_array($this->getIterator());
}
/**
* Eager load all items into a new lazy collection backed by an array.
*
* @return static
*/
public function eager()
{
return new static($this->all());
}
/**
* Cache values as they're enumerated.
*
* @return static
*/
public function remember()
{
$iterator = $this->getIterator();
$iteratorIndex = 0;
$cache = [];
return new static(function () use ($iterator, &$iteratorIndex, &$cache) {
for ($index = 0; true; $index++) {
if (array_key_exists($index, $cache)) {
yield $cache[$index][0] => $cache[$index][1];
continue;
}
if ($iteratorIndex < $index) {
$iterator->next();
$iteratorIndex++;
}
if (! $iterator->valid()) {
break;
}
$cache[$index] = [$iterator->key(), $iterator->current()];
yield $cache[$index][0] => $cache[$index][1];
}
});
}
/**
* Get the average value of a given key.
*
* @param (callable(TValue): float|int)|string|null $callback
* @return float|int|null
*/
public function avg($callback = null)
{
return $this->collect()->avg($callback);
}
/**
* Get the median of a given key.
*
* @param string|array<array-key, string>|null $key
* @return float|int|null
*/
public function median($key = null)
{
return $this->collect()->median($key);
}
/**
* Get the mode of a given key.
*
* @param string|array<string>|null $key
* @return array<int, float|int>|null
*/
public function mode($key = null)
{
return $this->collect()->mode($key);
}
/**
* Collapse the collection of items into a single array.
*
* @return static<int, mixed>
*/
public function collapse()
{
return new static(function () {
foreach ($this as $values) {
if (is_array($values) || $values instanceof Enumerable) {
foreach ($values as $value) {
yield $value;
}
}
}
});
}
/**
* Determine if an item exists in the enumerable.
*
* @param (callable(TValue, TKey): bool)|TValue|string $key
* @param mixed $operator
* @param mixed $value
* @return bool
*/
public function contains($key, $operator = null, $value = null)
{
if (func_num_args() === 1 && $this->useAsCallable($key)) {
$placeholder = new stdClass;
/** @var callable $key */
return $this->first($key, $placeholder) !== $placeholder;
}
if (func_num_args() === 1) {
$needle = $key;
foreach ($this as $value) {
if ($value == $needle) {
return true;
}
}
return false;
}
return $this->contains($this->operatorForWhere(...func_get_args()));
}
/**
* Determine if an item exists, using strict comparison.
*
* @param (callable(TValue): bool)|TValue|array-key $key
* @param TValue|null $value
* @return bool
*/
public function containsStrict($key, $value = null)
{
if (func_num_args() === 2) {
return $this->contains(fn ($item) => data_get($item, $key) === $value);
}
if ($this->useAsCallable($key)) {
return ! is_null($this->first($key));
}
foreach ($this as $item) {
if ($item === $key) {
return true;
}
}
return false;
}
/**
* Determine if an item is not contained in the enumerable.
*
* @param mixed $key
* @param mixed $operator
* @param mixed $value
* @return bool
*/
public function doesntContain($key, $operator = null, $value = null)
{
return ! $this->contains(...func_get_args());
}
/**
* Cross join the given iterables, returning all possible permutations.
*
* @template TCrossJoinKey
* @template TCrossJoinValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TCrossJoinKey, TCrossJoinValue>|iterable<TCrossJoinKey, TCrossJoinValue> ...$arrays
* @return static<int, array<int, TValue|TCrossJoinValue>>
*/
public function crossJoin(...$arrays)
{
return $this->passthru('crossJoin', func_get_args());
}
/**
* Count the number of items in the collection by a field or using a callback.
*
* @param (callable(TValue, TKey): array-key)|string|null $countBy
* @return static<array-key, int>
*/
public function countBy($countBy = null)
{
$countBy = is_null($countBy)
? $this->identity()
: $this->valueRetriever($countBy);
return new static(function () use ($countBy) {
$counts = [];
foreach ($this as $key => $value) {
$group = $countBy($value, $key);
if (empty($counts[$group])) {
$counts[$group] = 0;
}
$counts[$group]++;
}
yield from $counts;
});
}
/**
* Get the items that are not present in the given items.
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
* @return static
*/
public function diff($items)
{
return $this->passthru('diff', func_get_args());
}
/**
* Get the items that are not present in the given items, using the callback.
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
* @param callable(TValue, TValue): int $callback
* @return static
*/
public function diffUsing($items, callable $callback)
{
return $this->passthru('diffUsing', func_get_args());
}
/**
* Get the items whose keys and values are not present in the given items.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function diffAssoc($items)
{
return $this->passthru('diffAssoc', func_get_args());
}
/**
* Get the items whose keys and values are not present in the given items, using the callback.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function diffAssocUsing($items, callable $callback)
{
return $this->passthru('diffAssocUsing', func_get_args());
}
/**
* Get the items whose keys are not present in the given items.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function diffKeys($items)
{
return $this->passthru('diffKeys', func_get_args());
}
/**
* Get the items whose keys are not present in the given items, using the callback.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function diffKeysUsing($items, callable $callback)
{
return $this->passthru('diffKeysUsing', func_get_args());
}
/**
* Retrieve duplicate items.
*
* @param (callable(TValue): bool)|string|null $callback
* @param bool $strict
* @return static
*/
public function duplicates($callback = null, $strict = false)
{
return $this->passthru('duplicates', func_get_args());
}
/**
* Retrieve duplicate items using strict comparison.
*
* @param (callable(TValue): bool)|string|null $callback
* @return static
*/
public function duplicatesStrict($callback = null)
{
return $this->passthru('duplicatesStrict', func_get_args());
}
/**
* Get all items except for those with the specified keys.
*
* @param \Illuminate\Support\Enumerable<array-key, TKey>|array<array-key, TKey> $keys
* @return static
*/
public function except($keys)
{
return $this->passthru('except', func_get_args());
}
/**
* Run a filter over each of the items.
*
* @param (callable(TValue, TKey): bool)|null $callback
* @return static
*/
public function filter(callable $callback = null)
{
if (is_null($callback)) {
$callback = fn ($value) => (bool) $value;
}
return new static(function () use ($callback) {
foreach ($this as $key => $value) {
if ($callback($value, $key)) {
yield $key => $value;
}
}
});
}
/**
* Get the first item from the enumerable passing the given truth test.
*
* @template TFirstDefault
*
* @param (callable(TValue): bool)|null $callback
* @param TFirstDefault|(\Closure(): TFirstDefault) $default
* @return TValue|TFirstDefault
*/
public function first(callable $callback = null, $default = null)
{
$iterator = $this->getIterator();
if (is_null($callback)) {
if (! $iterator->valid()) {
return value($default);
}
return $iterator->current();
}
foreach ($iterator as $key => $value) {
if ($callback($value, $key)) {
return $value;
}
}
return value($default);
}
/**
* Get a flattened list of the items in the collection.
*
* @param int $depth
* @return static<int, mixed>
*/
public function flatten($depth = INF)
{
$instance = new static(function () use ($depth) {
foreach ($this as $item) {
if (! is_array($item) && ! $item instanceof Enumerable) {
yield $item;
} elseif ($depth === 1) {
yield from $item;
} else {
yield from (new static($item))->flatten($depth - 1);
}
}
});
return $instance->values();
}
/**
* Flip the items in the collection.
*
* @return static<TValue, TKey>
*/
public function flip()
{
return new static(function () {
foreach ($this as $key => $value) {
yield $value => $key;
}
});
}
/**
* Get an item by key.
*
* @template TGetDefault
*
* @param TKey|null $key
* @param TGetDefault|(\Closure(): TGetDefault) $default
* @return TValue|TGetDefault
*/
public function get($key, $default = null)
{
if (is_null($key)) {
return;
}
foreach ($this as $outerKey => $outerValue) {
if ($outerKey == $key) {
return $outerValue;
}
}
return value($default);
}
/**
* Group an associative array by a field or using a callback.
*
* @param (callable(TValue, TKey): array-key)|array|string $groupBy
* @param bool $preserveKeys
* @return static<array-key, static<array-key, TValue>>
*/
public function groupBy($groupBy, $preserveKeys = false)
{
return $this->passthru('groupBy', func_get_args());
}
/**
* Key an associative array by a field or using a callback.
*
* @param (callable(TValue, TKey): array-key)|array|string $keyBy
* @return static<array-key, TValue>
*/
public function keyBy($keyBy)
{
return new static(function () use ($keyBy) {
$keyBy = $this->valueRetriever($keyBy);
foreach ($this as $key => $item) {
$resolvedKey = $keyBy($item, $key);
if (is_object($resolvedKey)) {
$resolvedKey = (string) $resolvedKey;
}
yield $resolvedKey => $item;
}
});
}
/**
* Determine if an item exists in the collection by key.
*
* @param mixed $key
* @return bool
*/
public function has($key)
{
$keys = array_flip(is_array($key) ? $key : func_get_args());
$count = count($keys);
foreach ($this as $key => $value) {
if (array_key_exists($key, $keys) && --$count == 0) {
return true;
}
}
return false;
}
/**
* Determine if any of the keys exist in the collection.
*
* @param mixed $key
* @return bool
*/
public function hasAny($key)
{
$keys = array_flip(is_array($key) ? $key : func_get_args());
foreach ($this as $key => $value) {
if (array_key_exists($key, $keys)) {
return true;
}
}
return false;
}
/**
* Concatenate values of a given key as a string.
*
* @param callable|string $value
* @param string|null $glue
* @return string
*/
public function implode($value, $glue = null)
{
return $this->collect()->implode(...func_get_args());
}
/**
* Intersect the collection with the given items.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function intersect($items)
{
return $this->passthru('intersect', func_get_args());
}
/**
* Intersect the collection with the given items, using the callback.
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
* @param callable(TValue, TValue): int $callback
* @return static
*/
public function intersectUsing($items, callable $callback)
{
return $this->passthru('intersectUsing', func_get_args());
}
/**
* Intersect the collection with the given items with additional index check.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function intersectAssoc($items)
{
return $this->passthru('intersectAssoc', func_get_args());
}
/**
* Intersect the collection with the given items with additional index check, using the callback.
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TValue>|iterable<array-key, TValue> $items
* @param callable(TValue, TValue): int $callback
* @return static
*/
public function intersectAssocUsing($items, callable $callback)
{
return $this->passthru('intersectAssocUsing', func_get_args());
}
/**
* Intersect the collection with the given items by key.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function intersectByKeys($items)
{
return $this->passthru('intersectByKeys', func_get_args());
}
/**
* Determine if the items are empty or not.
*
* @return bool
*/
public function isEmpty()
{
return ! $this->getIterator()->valid();
}
/**
* Determine if the collection contains a single item.
*
* @return bool
*/
public function containsOneItem()
{
return $this->take(2)->count() === 1;
}
/**
* Join all items from the collection using a string. The final items can use a separate glue string.
*
* @param string $glue
* @param string $finalGlue
* @return string
*/
public function join($glue, $finalGlue = '')
{
return $this->collect()->join(...func_get_args());
}
/**
* Get the keys of the collection items.
*
* @return static<int, TKey>
*/
public function keys()
{
return new static(function () {
foreach ($this as $key => $value) {
yield $key;
}
});
}
/**
* Get the last item from the collection.
*
* @template TLastDefault
*
* @param (callable(TValue, TKey): bool)|null $callback
* @param TLastDefault|(\Closure(): TLastDefault) $default
* @return TValue|TLastDefault
*/
public function last(callable $callback = null, $default = null)
{
$needle = $placeholder = new stdClass;
foreach ($this as $key => $value) {
if (is_null($callback) || $callback($value, $key)) {
$needle = $value;
}
}
return $needle === $placeholder ? value($default) : $needle;
}
/**
* Get the values of a given key.
*
* @param string|array<array-key, string> $value
* @param string|null $key
* @return static<int, mixed>
*/
public function pluck($value, $key = null)
{
return new static(function () use ($value, $key) {
[$value, $key] = $this->explodePluckParameters($value, $key);
foreach ($this as $item) {
$itemValue = data_get($item, $value);
if (is_null($key)) {
yield $itemValue;
} else {
$itemKey = data_get($item, $key);
if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
$itemKey = (string) $itemKey;
}
yield $itemKey => $itemValue;
}
}
});
}
/**
* Run a map over each of the items.
*
* @template TMapValue
*
* @param callable(TValue, TKey): TMapValue $callback
* @return static<TKey, TMapValue>
*/
public function map(callable $callback)
{
return new static(function () use ($callback) {
foreach ($this as $key => $value) {
yield $key => $callback($value, $key);
}
});
}
/**
* Run a dictionary map over the items.
*
* The callback should return an associative array with a single key/value pair.
*
* @template TMapToDictionaryKey of array-key
* @template TMapToDictionaryValue
*
* @param callable(TValue, TKey): array<TMapToDictionaryKey, TMapToDictionaryValue> $callback
* @return static<TMapToDictionaryKey, array<int, TMapToDictionaryValue>>
*/
public function mapToDictionary(callable $callback)
{
return $this->passthru('mapToDictionary', func_get_args());
}
/**
* Run an associative map over each of the items.
*
* The callback should return an associative array with a single key/value pair.
*
* @template TMapWithKeysKey of array-key
* @template TMapWithKeysValue
*
* @param callable(TValue, TKey): array<TMapWithKeysKey, TMapWithKeysValue> $callback
* @return static<TMapWithKeysKey, TMapWithKeysValue>
*/
public function mapWithKeys(callable $callback)
{
return new static(function () use ($callback) {
foreach ($this as $key => $value) {
yield from $callback($value, $key);
}
});
}
/**
* Merge the collection with the given items.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function merge($items)
{
return $this->passthru('merge', func_get_args());
}
/**
* Recursively merge the collection with the given items.
*
* @template TMergeRecursiveValue
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TMergeRecursiveValue>|iterable<TKey, TMergeRecursiveValue> $items
* @return static<TKey, TValue|TMergeRecursiveValue>
*/
public function mergeRecursive($items)
{
return $this->passthru('mergeRecursive', func_get_args());
}
/**
* Create a collection by using this collection for keys and another for its values.
*
* @template TCombineValue
*
* @param \IteratorAggregate<array-key, TCombineValue>|array<array-key, TCombineValue>|(callable(): \Generator<array-key, TCombineValue>) $values
* @return static<TValue, TCombineValue>
*/
public function combine($values)
{
return new static(function () use ($values) {
$values = $this->makeIterator($values);
$errorMessage = 'Both parameters should have an equal number of elements';
foreach ($this as $key) {
if (! $values->valid()) {
trigger_error($errorMessage, E_USER_WARNING);
break;
}
yield $key => $values->current();
$values->next();
}
if ($values->valid()) {
trigger_error($errorMessage, E_USER_WARNING);
}
});
}
/**
* Union the collection with the given items.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function union($items)
{
return $this->passthru('union', func_get_args());
}
/**
* Create a new collection consisting of every n-th element.
*
* @param int $step
* @param int $offset
* @return static
*/
public function nth($step, $offset = 0)
{
return new static(function () use ($step, $offset) {
$position = 0;
foreach ($this->slice($offset) as $item) {
if ($position % $step === 0) {
yield $item;
}
$position++;
}
});
}
/**
* Get the items with the specified keys.
*
* @param \Illuminate\Support\Enumerable<array-key, TKey>|array<array-key, TKey>|string $keys
* @return static
*/
public function only($keys)
{
if ($keys instanceof Enumerable) {
$keys = $keys->all();
} elseif (! is_null($keys)) {
$keys = is_array($keys) ? $keys : func_get_args();
}
return new static(function () use ($keys) {
if (is_null($keys)) {
yield from $this;
} else {
$keys = array_flip($keys);
foreach ($this as $key => $value) {
if (array_key_exists($key, $keys)) {
yield $key => $value;
unset($keys[$key]);
if (empty($keys)) {
break;
}
}
}
}
});
}
/**
* Push all of the given items onto the collection.
*
* @param iterable<array-key, TValue> $source
* @return static
*/
public function concat($source)
{
return (new static(function () use ($source) {
yield from $this;
yield from $source;
}))->values();
}
/**
* Get one or a specified number of items randomly from the collection.
*
* @param int|null $number
* @return static<int, TValue>|TValue
*
* @throws \InvalidArgumentException
*/
public function random($number = null)
{
$result = $this->collect()->random(...func_get_args());
return is_null($number) ? $result : new static($result);
}
/**
* Replace the collection items with the given items.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function replace($items)
{
return new static(function () use ($items) {
$items = $this->getArrayableItems($items);
foreach ($this as $key => $value) {
if (array_key_exists($key, $items)) {
yield $key => $items[$key];
unset($items[$key]);
} else {
yield $key => $value;
}
}
foreach ($items as $key => $value) {
yield $key => $value;
}
});
}
/**
* Recursively replace the collection items with the given items.
*
* @param \Illuminate\Contracts\Support\Arrayable<TKey, TValue>|iterable<TKey, TValue> $items
* @return static
*/
public function replaceRecursive($items)
{
return $this->passthru('replaceRecursive', func_get_args());
}
/**
* Reverse items order.
*
* @return static
*/
public function reverse()
{
return $this->passthru('reverse', func_get_args());
}
/**
* Search the collection for a given value and return the corresponding key if successful.
*
* @param TValue|(callable(TValue,TKey): bool) $value
* @param bool $strict
* @return TKey|false
*/
public function search($value, $strict = false)
{
/** @var (callable(TValue,TKey): bool) $predicate */
$predicate = $this->useAsCallable($value)
? $value
: function ($item) use ($value, $strict) {
return $strict ? $item === $value : $item == $value;
};
foreach ($this as $key => $item) {
if ($predicate($item, $key)) {
return $key;
}
}
return false;
}
/**
* Shuffle the items in the collection.
*
* @param int|null $seed
* @return static
*/
public function shuffle($seed = null)
{
return $this->passthru('shuffle', func_get_args());
}
/**
* Create chunks representing a "sliding window" view of the items in the collection.
*
* @param int $size
* @param int $step
* @return static<int, static>
*/
public function sliding($size = 2, $step = 1)
{
return new static(function () use ($size, $step) {
$iterator = $this->getIterator();
$chunk = [];
while ($iterator->valid()) {
$chunk[$iterator->key()] = $iterator->current();
if (count($chunk) == $size) {
yield (new static($chunk))->tap(function () use (&$chunk, $step) {
$chunk = array_slice($chunk, $step, null, true);
});
// If the $step between chunks is bigger than each chunk's $size
// we will skip the extra items (which should never be in any
// chunk) before we continue to the next chunk in the loop.
if ($step > $size) {
$skip = $step - $size;
for ($i = 0; $i < $skip && $iterator->valid(); $i++) {
$iterator->next();
}
}
}
$iterator->next();
}
});
}
/**
* Skip the first {$count} items.
*
* @param int $count
* @return static
*/
public function skip($count)
{
return new static(function () use ($count) {
$iterator = $this->getIterator();
while ($iterator->valid() && $count--) {
$iterator->next();
}
while ($iterator->valid()) {
yield $iterator->key() => $iterator->current();
$iterator->next();
}
});
}
/**
* Skip items in the collection until the given condition is met.
*
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function skipUntil($value)
{
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return $this->skipWhile($this->negate($callback));
}
/**
* Skip items in the collection while the given condition is met.
*
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function skipWhile($value)
{
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return new static(function () use ($callback) {
$iterator = $this->getIterator();
while ($iterator->valid() && $callback($iterator->current(), $iterator->key())) {
$iterator->next();
}
while ($iterator->valid()) {
yield $iterator->key() => $iterator->current();
$iterator->next();
}
});
}
/**
* Get a slice of items from the enumerable.
*
* @param int $offset
* @param int|null $length
* @return static
*/
public function slice($offset, $length = null)
{
if ($offset < 0 || $length < 0) {
return $this->passthru('slice', func_get_args());
}
$instance = $this->skip($offset);
return is_null($length) ? $instance : $instance->take($length);
}
/**
* Split a collection into a certain number of groups.
*
* @param int $numberOfGroups
* @return static<int, static>
*/
public function split($numberOfGroups)
{
return $this->passthru('split', func_get_args());
}
/**
* Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
*
* @param (callable(TValue, TKey): bool)|string $key
* @param mixed $operator
* @param mixed $value
* @return TValue
*
* @throws \Illuminate\Support\ItemNotFoundException
* @throws \Illuminate\Support\MultipleItemsFoundException
*/
public function sole($key = null, $operator = null, $value = null)
{
$filter = func_num_args() > 1
? $this->operatorForWhere(...func_get_args())
: $key;
return $this
->unless($filter == null)
->filter($filter)
->take(2)
->collect()
->sole();
}
/**
* Get the first item in the collection but throw an exception if no matching items exist.
*
* @param (callable(TValue, TKey): bool)|string $key
* @param mixed $operator
* @param mixed $value
* @return TValue
*
* @throws \Illuminate\Support\ItemNotFoundException
*/
public function firstOrFail($key = null, $operator = null, $value = null)
{
$filter = func_num_args() > 1
? $this->operatorForWhere(...func_get_args())
: $key;
return $this
->unless($filter == null)
->filter($filter)
->take(1)
->collect()
->firstOrFail();
}
/**
* Chunk the collection into chunks of the given size.
*
* @param int $size
* @return static<int, static>
*/
public function chunk($size)
{
if ($size <= 0) {
return static::empty();
}
return new static(function () use ($size) {
$iterator = $this->getIterator();
while ($iterator->valid()) {
$chunk = [];
while (true) {
$chunk[$iterator->key()] = $iterator->current();
if (count($chunk) < $size) {
$iterator->next();
if (! $iterator->valid()) {
break;
}
} else {
break;
}
}
yield new static($chunk);
$iterator->next();
}
});
}
/**
* Split a collection into a certain number of groups, and fill the first groups completely.
*
* @param int $numberOfGroups
* @return static<int, static>
*/
public function splitIn($numberOfGroups)
{
return $this->chunk(ceil($this->count() / $numberOfGroups));
}
/**
* Chunk the collection into chunks with a callback.
*
* @param callable(TValue, TKey, Collection<TKey, TValue>): bool $callback
* @return static<int, static<int, TValue>>
*/
public function chunkWhile(callable $callback)
{
return new static(function () use ($callback) {
$iterator = $this->getIterator();
$chunk = new Collection;
if ($iterator->valid()) {
$chunk[$iterator->key()] = $iterator->current();
$iterator->next();
}
while ($iterator->valid()) {
if (! $callback($iterator->current(), $iterator->key(), $chunk)) {
yield new static($chunk);
$chunk = new Collection;
}
$chunk[$iterator->key()] = $iterator->current();
$iterator->next();
}
if ($chunk->isNotEmpty()) {
yield new static($chunk);
}
});
}
/**
* Sort through each item with a callback.
*
* @param (callable(TValue, TValue): int)|null|int $callback
* @return static
*/
public function sort($callback = null)
{
return $this->passthru('sort', func_get_args());
}
/**
* Sort items in descending order.
*
* @param int $options
* @return static
*/
public function sortDesc($options = SORT_REGULAR)
{
return $this->passthru('sortDesc', func_get_args());
}
/**
* Sort the collection using the given callback.
*
* @param array<array-key, (callable(TValue, TValue): mixed)|(callable(TValue, TKey): mixed)|string|array{string, string}>|(callable(TValue, TKey): mixed)|string $callback
* @param int $options
* @param bool $descending
* @return static
*/
public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
{
return $this->passthru('sortBy', func_get_args());
}
/**
* Sort the collection in descending order using the given callback.
*
* @param array<array-key, (callable(TValue, TValue): mixed)|(callable(TValue, TKey): mixed)|string|array{string, string}>|(callable(TValue, TKey): mixed)|string $callback
* @param int $options
* @return static
*/
public function sortByDesc($callback, $options = SORT_REGULAR)
{
return $this->passthru('sortByDesc', func_get_args());
}
/**
* Sort the collection keys.
*
* @param int $options
* @param bool $descending
* @return static
*/
public function sortKeys($options = SORT_REGULAR, $descending = false)
{
return $this->passthru('sortKeys', func_get_args());
}
/**
* Sort the collection keys in descending order.
*
* @param int $options
* @return static
*/
public function sortKeysDesc($options = SORT_REGULAR)
{
return $this->passthru('sortKeysDesc', func_get_args());
}
/**
* Sort the collection keys using a callback.
*
* @param callable(TKey, TKey): int $callback
* @return static
*/
public function sortKeysUsing(callable $callback)
{
return $this->passthru('sortKeysUsing', func_get_args());
}
/**
* Take the first or last {$limit} items.
*
* @param int $limit
* @return static
*/
public function take($limit)
{
if ($limit < 0) {
return new static(function () use ($limit) {
$limit = abs($limit);
$ringBuffer = [];
$position = 0;
foreach ($this as $key => $value) {
$ringBuffer[$position] = [$key, $value];
$position = ($position + 1) % $limit;
}
for ($i = 0, $end = min($limit, count($ringBuffer)); $i < $end; $i++) {
$pointer = ($position + $i) % $limit;
yield $ringBuffer[$pointer][0] => $ringBuffer[$pointer][1];
}
});
}
return new static(function () use ($limit) {
$iterator = $this->getIterator();
while ($limit--) {
if (! $iterator->valid()) {
break;
}
yield $iterator->key() => $iterator->current();
if ($limit) {
$iterator->next();
}
}
});
}
/**
* Take items in the collection until the given condition is met.
*
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function takeUntil($value)
{
/** @var callable(TValue, TKey): bool $callback */
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return new static(function () use ($callback) {
foreach ($this as $key => $item) {
if ($callback($item, $key)) {
break;
}
yield $key => $item;
}
});
}
/**
* Take items in the collection until a given point in time.
*
* @param \DateTimeInterface $timeout
* @return static
*/
public function takeUntilTimeout(DateTimeInterface $timeout)
{
$timeout = $timeout->getTimestamp();
return new static(function () use ($timeout) {
if ($this->now() >= $timeout) {
return;
}
foreach ($this as $key => $value) {
yield $key => $value;
if ($this->now() >= $timeout) {
break;
}
}
});
}
/**
* Take items in the collection while the given condition is met.
*
* @param TValue|callable(TValue,TKey): bool $value
* @return static
*/
public function takeWhile($value)
{
/** @var callable(TValue, TKey): bool $callback */
$callback = $this->useAsCallable($value) ? $value : $this->equality($value);
return $this->takeUntil(fn ($item, $key) => ! $callback($item, $key));
}
/**
* Pass each item in the collection to the given callback, lazily.
*
* @param callable(TValue, TKey): mixed $callback
* @return static
*/
public function tapEach(callable $callback)
{
return new static(function () use ($callback) {
foreach ($this as $key => $value) {
$callback($value, $key);
yield $key => $value;
}
});
}
/**
* Flatten a multi-dimensional associative array with dots.
*
* @return static
*/
public function dot()
{
return $this->passthru('dot', []);
}
/**
* Convert a flatten "dot" notation array into an expanded array.
*
* @return static
*/
public function undot()
{
return $this->passthru('undot', []);
}
/**
* Return only unique items from the collection array.
*
* @param (callable(TValue, TKey): mixed)|string|null $key
* @param bool $strict
* @return static
*/
public function unique($key = null, $strict = false)
{
$callback = $this->valueRetriever($key);
return new static(function () use ($callback, $strict) {
$exists = [];
foreach ($this as $key => $item) {
if (! in_array($id = $callback($item, $key), $exists, $strict)) {
yield $key => $item;
$exists[] = $id;
}
}
});
}
/**
* Reset the keys on the underlying array.
*
* @return static<int, TValue>
*/
public function values()
{
return new static(function () {
foreach ($this as $item) {
yield $item;
}
});
}
/**
* Zip the collection together with one or more arrays.
*
* e.g. new LazyCollection([1, 2, 3])->zip([4, 5, 6]);
* => [[1, 4], [2, 5], [3, 6]]
*
* @template TZipValue
*
* @param \Illuminate\Contracts\Support\Arrayable<array-key, TZipValue>|iterable<array-key, TZipValue> ...$items
* @return static<int, static<int, TValue|TZipValue>>
*/
public function zip($items)
{
$iterables = func_get_args();
return new static(function () use ($iterables) {
$iterators = Collection::make($iterables)->map(function ($iterable) {
return $this->makeIterator($iterable);
})->prepend($this->getIterator());
while ($iterators->contains->valid()) {
yield new static($iterators->map->current());
$iterators->each->next();
}
});
}
/**
* Pad collection to the specified length with a value.
*
* @template TPadValue
*
* @param int $size
* @param TPadValue $value
* @return static<int, TValue|TPadValue>
*/
public function pad($size, $value)
{
if ($size < 0) {
return $this->passthru('pad', func_get_args());
}
return new static(function () use ($size, $value) {
$yielded = 0;
foreach ($this as $index => $item) {
yield $index => $item;
$yielded++;
}
while ($yielded++ < $size) {
yield $value;
}
});
}
/**
* Get the values iterator.
*
* @return \Traversable<TKey, TValue>
*/
public function getIterator(): Traversable
{
return $this->makeIterator($this->source);
}
/**
* Count the number of items in the collection.
*
* @return int
*/
public function count(): int
{
if (is_array($this->source)) {
return count($this->source);
}
return iterator_count($this->getIterator());
}
/**
* Make an iterator from the given source.
*
* @template TIteratorKey of array-key
* @template TIteratorValue
*
* @param \IteratorAggregate<TIteratorKey, TIteratorValue>|array<TIteratorKey, TIteratorValue>|(callable(): \Generator<TIteratorKey, TIteratorValue>) $source
* @return \Traversable<TIteratorKey, TIteratorValue>
*/
protected function makeIterator($source)
{
if ($source instanceof IteratorAggregate) {
return $source->getIterator();
}
if (is_array($source)) {
return new ArrayIterator($source);
}
if (is_callable($source)) {
$maybeTraversable = $source();
return $maybeTraversable instanceof Traversable
? $maybeTraversable
: new ArrayIterator(Arr::wrap($maybeTraversable));
}
return new ArrayIterator((array) $source);
}
/**
* Explode the "value" and "key" arguments passed to "pluck".
*
* @param string|string[] $value
* @param string|string[]|null $key
* @return array{string[],string[]|null}
*/
protected function explodePluckParameters($value, $key)
{
$value = is_string($value) ? explode('.', $value) : $value;
$key = is_null($key) || is_array($key) ? $key : explode('.', $key);
return [$value, $key];
}
/**
* Pass this lazy collection through a method on the collection class.
*
* @param string $method
* @param array<mixed> $params
* @return static
*/
protected function passthru($method, array $params)
{
return new static(function () use ($method, $params) {
yield from $this->collect()->$method(...$params);
});
}
/**
* Get the current time.
*
* @return int
*/
protected function now()
{
return Carbon::now()->timestamp;
}
}