Collection Power Moves & Tips

Senior-level Collection tricks — pipelines, higher-order proxies, lazy collections and quiet gotchas.

You already know map, filter and reduce. This lesson is the senior toolkit — the methods and idioms that turn ten lines of loops into one readable pipeline, plus the traps that catch people.

Higher-order proxies

Skip the closure for one-method calls: $users->each->markActive() or $orders->sum->total read beautifully.

// Instead of map(fn ($u) => $u->name())
$names = $users->map->name();
$active->each->activate();

Underused gems

  • groupBy + map — build reports in a breath.
  • mapWithKeys — reshape into a keyed array.
  • partition — split into [passed, failed] in one pass.
  • flatMap, zip, sliding, tap, pipe, when/unless — conditional chaining without breaking the flow.
  • whenEmpty / whenNotEmpty — branch inside a pipeline.

LazyCollection

For huge or streamed data, LazyCollection processes one item at a time via generators — read a million-line file or an Eloquent cursor() without loading it all into memory.

Example

Example · php
<?php

use Illuminate\Support\LazyCollection;

// --- A real reporting pipeline: revenue by month, top 3 ---
$topMonths = $orders                       // Collection<Order>
    ->groupBy(fn ($o) => $o->created_at->format('Y-m'))
    ->map(fn ($group) => $group->sum->total_cents)   // higher-order proxy
    ->sortDesc()
    ->take(3);

// --- partition: split in a single pass ---
[$paid, $unpaid] = $orders->partition->isPaid();

// --- conditional chaining, no broken flow ---
$query = collect(request('filters', []))
    ->when(request('vip'), fn ($c) => $c->push('vip'))
    ->unique()
    ->values();

// --- mapWithKeys: reshape to a lookup table ---
$byEmail = $users->mapWithKeys(fn ($u) => [$u->email => $u->name]);

// --- LazyCollection: stream a huge CSV, constant memory ---
$total = LazyCollection::make(function () {
        $handle = fopen(storage_path('app/big.csv'), 'r');
        while (($row = fgetcsv($handle)) !== false) {
            yield $row;
        }
        fclose($handle);
    })
    ->skip(1)                              // header
    ->map(fn ($row) => (int) $row[3])
    ->sum();                               // never holds the whole file in RAM

When to use it

  • A developer uses lazy() to iterate over a million-row result set in a for-each loop without exhausting PHP memory, processing each record as it is streamed.
  • A complex data transformation is expressed as a single readable pipeline using pipe() and collect(), making the business logic auditable in one glance.
  • A test uses Collection::times() to generate a deterministic list of fixture objects without hitting the database, keeping the test fast and isolated.

More examples

LazyCollection for memory-efficient iteration

LazyCollection::make() wraps a generator so each CSV row is yielded on demand; filter() and map() are applied lazily, keeping memory constant.

Example · php
use Illuminate\Support\LazyCollection;

// Stream a huge CSV without loading it all at once
$results = LazyCollection::make(function () use ($filePath) {
    $handle = fopen($filePath, 'r');
    while (($line = fgets($handle)) !== false) {
        yield str_getcsv($line);
    }
    fclose($handle);
})->filter(fn($row) => isset($row[2]) && $row[2] !== 'N/A')
  ->map(fn($row) => ['sku' => $row[0], 'price' => (float) $row[1]]);

foreach ($results as $item) {
    Product::updateOrCreate(['sku' => $item['sku']], $item);
}

pipe() for readable multi-step transforms

pipe() passes the collection into a closure and returns its result, enabling complex transformations to be expressed as a linear top-to-bottom pipeline.

Example · php
$report = collect($rawOrders)
    ->pipe(fn($c) => $c->where('status', 'completed'))
    ->pipe(fn($c) => $c->groupBy('region'))
    ->pipe(fn($c) => $c->map(fn($group) => [
        'count'   => $group->count(),
        'revenue' => $group->sum('total'),
    ]));

times(), zip(), and crossJoin()

times() generates fixture lists; zip() pairs two collections positionally; crossJoin() produces the cartesian product — useful for generating product variant combinations.

Example · php
// Generate N items
$dates = Collection::times(7, fn($n) => now()->addDays($n - 1)->toDateString());
// => ['2025-03-14', '2025-03-15', ..., '2025-03-20']

// Pair two collections element-by-element
$zipped = collect(['A', 'B', 'C'])->zip([1, 2, 3]);
// => [['A',1], ['B',2], ['C',3]]

// Cartesian product
$variants = collect(['S','M','L'])->crossJoin(['Red','Blue']);
// => [['S','Red'],['S','Blue'],['M','Red'],['M','Blue'],...]

Discussion

  • Be the first to comment on this lesson.