php-standard-library/iter
Inspect and reduce any PHP iterable (arrays, generators, iterators) with small, focused helpers from PHP Standard Library - Iter. Designed for common iteration tasks and consistent behavior across iterable types.
Installation:
composer require php-standard-library/iter
No configuration or service provider setup is required—use the iter() helper globally.
First Use Case:
Replace a simple foreach loop with a declarative pipeline:
// Before (imperative)
$names = [];
foreach ($users as $user) {
if ($user->isActive()) {
$names[] = $user->name;
}
}
// After (declarative)
$names = iter($users)
->filter(fn($user) => $user->isActive())
->map(fn($user) => $user->name)
->toArray();
Key Entry Points:
iter($iterable): Convert any iterable (array, generator, Traversable) into an Iter object.map(), filter(), reduce(), chunk(), flatten(), tap().toArray(), first(), all(), count().Where to Look First:
Use Case: Process large datasets (e.g., database exports, file streams) without loading everything into memory.
// Eloquent cursor + lazy filtering (no N+1 queries)
$activeUsers = iter(User::cursor())
->filter(fn($user) => $user->isActive())
->map(fn($user) => $user->email)
->toArray();
// File stream processing
$lines = iter(fopen('large.csv', 'r'));
$processed = iter($lines)
->map(fn($line) => strtolower(trim($line)))
->filter(fn($line) => strlen($line) > 0)
->toArray();
Use Case: Chain transformations (e.g., API response normalization, ETL pipelines).
// API Resource transformation
$posts = iter($rawData)
->map(fn($post) => [
'id' => $post['id'],
'title' => ucfirst($post['title']),
'author' => $post['user']['name'],
])
->filter(fn($post) => $post['title'] !== 'Draft')
->sort(fn($a, $b) => strcmp($b['title'], $a['title']))
->chunk(10);
Use Case: Work with Laravel’s generators (e.g., Model::cursor(), file handles, custom iterators).
// Custom generator
function generateLargeData() {
for ($i = 0; $i < 1_000_000; $i++) {
yield $i;
}
}
// Process without memory overload
$sum = iter(generateLargeData())
->filter(fn($n) => $n % 2 === 0)
->reduce(fn($carry, $n) => $carry + $n, 0);
Use Case: Dispatch Laravel jobs in chunks to avoid memory issues.
iter(User::cursor())
->chunk(50)
->each(fn($chunk) => ProcessUsersJob::dispatch($chunk));
tap()Use Case: Inspect intermediate states in pipelines (e.g., logging, validation).
iter($users)
->tap(fn($iter) => Log::debug('Users loaded:', $iter->count()))
->filter(fn($user) => $user->isActive())
->tap(fn($iter) => Log::debug('Active users:', $iter->toArray()))
->map(fn($user) => $user->name);
cursor() with iter() to avoid eager loading.
$activeUserIds = iter(User::where('active', true)->cursor())
->pluck('id')
->toArray();
$postsWithComments = iter(Post::with('comments')->cursor())
->filter(fn($post) => $post->comments->count() > 0)
->toArray();
$resource = new PostResource(
iter($posts)
->map(fn($post) => [
'id' => $post->id,
'title' => $post->title,
'comments' => iter($post->comments)->pluck('body')->toArray(),
])
->toArray()
);
@foreach(iter($items)->filter(fn($item) => $item->isVisible()))
<div>{{ $item->name }}</div>
@endforeach
@foreach(iter($items)->chunk(3) as $chunk)
<div class="row">
@foreach($chunk as $item)
<div>{{ $item->name }}</div>
@endforeach
</div>
@endforeach
public function handle() {
iter(Storage::files('large-directory'))
->filter(fn($path) => str_ends_with($path, '.log'))
->each(fn($path) => $this->processLog($path));
}
$mockGenerator = iter([new User(), new User()]);
$names = iter($mockGenerator)->map(fn($u) => $u->name)->toArray();
$this->assertEquals(['Alice', 'Bob'], $names);
$iter = iter([1, 2, 3]);
$this->assertEquals([2, 4, 6], iter($iter)->map(fn($n) => $n * 2)->toArray());
Avoid Premature Materialization:
toArray() or all() only when needed. Keep pipelines lazy until the final step.iter($generator)->map(...)->filter(...) (still lazy) vs. iter($generator)->map(...)->toArray() (materialized).Leverage PHP 8.1+ Features:
iter($users)->filter(fn($user) => $user->isActive(), true); // $strict = true
match expressions:
$status = match($user->role) {
'admin' => 'active',
default => 'inactive',
};
Hybrid with Laravel Collections:
Collection when needed:
$collection = iter($array)->map(...)->toCollection();
$array = iter($collection)->toArray();
Custom Iterators:
iter($customIterator)->map(fn($item) => $item->transform());
Error Handling:
try-catch for pipeline failures:
try {
$result = iter($data)->map(fn($x) => $x->invalid())->toArray();
} catch (Error $e) {
Log::error('Pipeline failed:', ['error' => $e->getMessage()]);
}
Generator Exhaustion:
// ❌ Fails on second iteration
$gen = generateData();
iter($gen)->toArray(); // Works
iter($gen)->toArray(); // Empty (exhausted)
// ✅ Recreate generator
$gen = generateData();
$first = iter($gen)->toArray();
$gen = generateData(); // Reset
$second = iter($gen)->toArray();
Strict Typing Quirks:
filter() uses loose comparison (==) by default. Use $strict = true for strict checks:
iter([0, '0', 1])->filter(fn($x) => $x === 0, true);
How can I help you explore Laravel packages today?