Collection::each
Runs a callback over every item, stopping early when the callback returns false.
each(callable $callback): $this- Added in Laravel 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$$callback | callable(TValue, TKey): mixed | Called once per item with the value and the key. Return exactly false to stop iterating. |
Return value
$thisThe same collection instance, unchanged, so the chain can continue.
Changelog
- PHP 5.2Returning false from the callback began breaking the loop; earlier versions ran to completion regardless.
each() is a foreach that fits in a chain. The callback receives the value first and the key second — the opposite order to array_walk(), and a frequent source of confusion.
It does not transform
The return value of the callback is discarded and each() hands back the same collection, untouched. Writing $c->each(fn ($n) => $n * 2) and expecting doubled numbers is the single most common mistake here; that is map(). Use each() only for side effects: dispatching jobs, writing logs, sending mail, filling an array outside the closure.
Breaking out early
Returning exactly false stops the loop. It is a strict === false check, so returning null, 0 or an empty string does not break — but an arrow function whose body happens to evaluate to false will, silently truncating the loop. Prefer a block closure with an explicit return false; when early exit is intended, and give arrow functions a return value you control.
Mutating items
Objects passed to the callback are the same instances the collection holds, so changing their properties changes the collection. Scalars and arrays are passed by value and cannot be modified this way unless you declare the parameter by reference.
Example
When to use it
- Dispatch a queued job for every record in a batch.
- Log or report each row while leaving the collection unchanged.
- Scan an ordered list and stop at the first item that meets a condition.
Frequently asked questions
Why does each() not change my collection?
Because it discards the callback's return value and returns the original collection unchanged — it exists for side effects. Use map() to build a new collection from the returned values, or transform() to modify in place.
How do I break out of a collection each() loop?
Return exactly false from the callback; the check is strict, so null, 0 and '' keep iterating. Use a block closure with an explicit return false so an arrow function's implicit value cannot break the loop by accident.
What order are the callback arguments in?
The value comes first and the key second — function ($value, $key) — which is the opposite of PHP's array_walk(). Getting them backwards is a common cause of silently wrong output.
More examples
The accidental early break
The first loop visits only two items because the arrow function's implicit return became false on the second flag. The block closure returns null, so all four run. The last block confirms the check is === false: returning 0 does not break. This is why side-effect loops should use block closures.
<?php
$flags = collect([true, false, true, true]);
// The arrow function's value IS the item, so the loop stops at the false.
$seen = 0;
$flags->each(fn (bool $f) => $seen++ && $f);
echo $seen, PHP_EOL;
// 2 <- expected 4
// A block closure with no return value never breaks.
$seen = 0;
$flags->each(function (bool $f) use (&$seen) {
$seen++;
});
echo $seen, PHP_EOL;
// 4
// Only an exact false stops it; null and 0 do not.
$n = 0;
collect([1, 2, 3])->each(function () use (&$n) {
$n++;
return 0;
});
echo $n, PHP_EOL;
// 3Side effects in an application, and each() versus map()
each() drives the side effects and returns the same collection, so the queued_at set inside the closure is visible afterwards — models are objects and are passed by handle. map() is what produces the derived list of labels; using each() for that would discard every value. Needs a booted application with a database and a queue.
<?php
$orders = Order::pending()->get();
// Side effects: dispatch, log, mutate models in place.
$orders->each(function (Order $order, int $key) {
ProcessOrder::dispatch($order);
// Models are objects, so this mutation sticks in the collection.
$order->queued_at = now();
Log::info("queued order {$order->id} at position {$key}");
});
// Still the same collection, with the mutation visible.
echo $orders->first()->queued_at->toDateTimeString(), PHP_EOL;
// Transformation belongs to map(), which returns a NEW collection.
$labels = $orders->map(fn (Order $o) => "#{$o->id} ({$o->status})");
echo $labels->implode(', '), PHP_EOL;
// #101 (pending), #102 (pending)
Discussion