What Is chunkBy() in Laravel?
Laravel 13.30 ships chunkBy(), a concise shorthand for the most common chunkWhile() pattern. Instead of writing:
$products->chunkWhile(
fn ($value, $key, $chunk) => $value->parent == $chunk->last()->parent
);
You can now write:
$products->chunkBy('parent');
The method accepts either a dot-notation key string or a callback:
$lineItems->chunkBy('order_id');
$lineItems->chunkBy(fn ($item) => $item->order_id);
// Dot notation reaches into nested objects
$users->chunkBy('address.city');
The key is resolved via data_get(), so nested array and object access works out of the box.
chunkBy() vs. groupBy(): Adjacent, Not Global
This is the most important distinction to internalize. chunkBy() only groups consecutive items that share the same value — it does not collect all matching items across the entire collection:
collect([1, 1, 2, 2, 1, 1])->chunkBy(fn ($v) => $v);
// [[1, 1], [2, 2], [1, 1]] — three chunks
collect([1, 1, 2, 2, 1, 1])->groupBy(fn ($v) => $v);
// [1 => [1, 1, 1, 1], 2 => [2, 2]] — two groups
If non-adjacent items with the same value need to end up together, either sort the data first or use groupBy(). Keys are preserved inside each chunk; call values() if you need a zero-indexed list.
Streaming Large Datasets with LazyCollection
chunkBy() is available on both standard and LazyCollection. On a lazy collection it yields each chunk as soon as the grouping value changes, keeping only the current chunk in memory at any time.
A practical example — exporting per-order CSVs from a table with millions of rows:
use App\Models\LineItem;
use Illuminate\Support\Facades\Storage;
LineItem::query()
->orderBy('order_id')
->orderBy('id')
->cursor()
->chunkBy('order_id')
->each(function ($items) {
$orderId = $items->first()->order_id;
Storage::disk('exports')->put(
"orders/{$orderId}.csv",
$items->map(fn ($item) => implode(',', [
$item->sku,
$item->quantity,
$item->unit_price,
]))->implode(PHP_EOL)
);
});
The orderBy('order_id') is not optional — chunkBy() relies on the data being sorted so the database handles ordering and PHP handles splitting, one row at a time.
The same pattern works over log files, paginated APIs, or any generator-based source that is too large to hold in memory.
Two Gotchas Worth Knowing
Loose comparison. The implementation uses ==, not ===. Mixed-type input like ['1', 1, 1.0] lands in a single chunk. Normalize the return value from your callback when type consistency matters:
$rows->chunkBy(fn ($row) => (string) $row['code']);
The resolver runs twice per boundary. Each boundary check resolves the current item and re-resolves the last item of the current chunk. For expensive operations (date parsing, hashing), precompute the value first:
$entries
->map(fn ($entry) => [$entry, Carbon::parse($entry->logged_at)->toDateString()])
->chunkBy(fn ($pair) => $pair[1]);
For simple key or property lookups this overhead is negligible.
Key Takeaways
chunkBy('key')is a readable shorthand for the commonchunkWhile()pattern.- It groups adjacent items only — sort your data first if you need global grouping.
- Dot-notation keys work via
data_get(). - On
LazyCollectionit streams chunk-by-chunk, keeping memory usage proportional to the largest single chunk. - Comparisons are loose (
==); cast return values when strict type matching is required. - Contributed by @JosephSilber in #61357.
Source: Laravel News — Group Adjacent Collection Items in Laravel with chunkBy()