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
<?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 RAMWhen 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.
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.
$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.
// 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