Group Adjacent Collection Items in Laravel with chunkBy()
Laravel Tips & Tricks #Laravel #Collections #LazyCollection #Laravel 13 #Performance

Group Adjacent Collection Items in Laravel with chunkBy()

3 min read Mohamed Said Mohamed Said

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 common chunkWhile() pattern.
  • It groups adjacent items only — sort your data first if you need global grouping.
  • Dot-notation keys work via data_get().
  • On LazyCollection it 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()

Found this useful?

Frequently Asked Questions

3 questions
Q01 What is the difference between chunkBy() and groupBy() in Laravel?
chunkBy() groups only consecutive items that share the same key value, producing multiple chunks if the same value appears in non-adjacent positions. groupBy() collects all items with the same key regardless of their position, merging them into a single group.
Q02 Does chunkBy() work with LazyCollection for large datasets?
Yes. On a LazyCollection, chunkBy() yields each chunk as soon as the grouping value changes, so only the current chunk is held in memory at any time. Pair it with a cursor() query sorted by the grouping column for memory-efficient processing of large database tables.
Q03 Does chunkBy() use strict or loose comparison?
chunkBy() uses loose comparison (==), not strict (===). This means values like '1', 1, and 1.0 are treated as equal and land in the same chunk. Cast the return value of your callback to a consistent type if strict separation is needed.

Continue reading

More Articles

View all