Laravel Collections Reference Guide

A reference guide to Laravel Collections: a fluent, chainable wrapper around arrays that every Eloquent query returns.

Creating a collection

Every Eloquent query that returns multiple rows (get(), a hasMany relation, etc.) already returns a Collection, not a plain array — so these methods are available immediately, without any extra step.

$collection = collect([1, 2, 3, 4, 5]);

$users = User::where('active', true)->get(); // also a Collection

Transforming

Collection methods never mutate the original in place — each one returns a new Collection, leaving the source untouched. That's what makes chaining safe: you can build up a pipeline of filtermapsortBy without worrying that an earlier step altered data a later step, or another part of the request, still relies on.

$names = $users->map(fn ($user) => $user->name);

$adults = $users->filter(fn ($user) => $user->age >= 18);

$emails = $users->pluck('email');
$emailsById = $users->pluck('email', 'id'); // ['1' => 'a@x.com', ...]

$byCountry = $users->groupBy('country');

$sorted = $users->sortBy('name');
$sorted = $users->sortByDesc('created_at');

$total = $users->sum('order_count');
$average = $users->avg('age');

// Chain freely — every method above returns a new Collection
$topSpenderNames = $users
    ->filter(fn ($user) => $user->total_spent > 1000)
    ->sortByDesc('total_spent')
    ->pluck('name');

Reducing to a single value

$total = collect([10, 20, 30])->reduce(fn ($carry, $item) => $carry + $item, 0);

$hasAdmin = $users->contains(fn ($user) => $user->role === 'admin');
$firstAdmin = $users->first(fn ($user) => $user->role === 'admin');
$count = $users->count();

Splitting and combining

$chunks = $users->chunk(50); // for batching, e.g. sending emails

$unique = $users->unique('email');

$merged = $users->merge($moreUsers);

[$active, $inactive] = $users->partition(fn ($user) => $user->active);

Checking and inspecting

$users->isEmpty();
$users->isNotEmpty();
$users->contains('email', 'jane@example.com');
$users->toArray();
$users->toJson();

Higher order messages

A shorthand for calling the same method or accessing the same property on every item, without writing a full closure.

$users->each->sendWelcomeEmail();
$total = $orders->sum->total;
$names = $users->map->name;

A real-world pipeline

Because every method returns a new collection, it's common to build up a small pipeline that reads almost like a sentence describing the business rule, rather than a loop full of if statements and an accumulator variable:

// Group this month's paid orders by customer, and list the top 5 spenders
$topCustomers = Order::where('status', 'paid')
    ->whereMonth('created_at', now()->month)
    ->get()
    ->groupBy('customer_id')
    ->map(fn ($orders) => [
        'customer' => $orders->first()->customer,
        'total' => $orders->sum('total'),
        'order_count' => $orders->count(),
    ])
    ->sortByDesc('total')
    ->take(5)
    ->values();

Written as a loop, this same logic needs a running array keyed by customer id, manual accumulation, an arsort(), and an array_slice() — all of it bookkeeping that has nothing to do with the actual rule being expressed. The collection version reads top to bottom as the rule itself.

Lazy collections

A regular Collection holds every item in memory at once, which is fine for a page of results but becomes a problem when processing something like a multi-million-row export or a large log file. LazyCollection implements the same fluent API but wraps a PHP generator, pulling and processing one item at a time instead of materialising the whole set up front.

use Illuminate\Support\LazyCollection;

// Reads and processes the file one line at a time, not all at once
LazyCollection::make(function () {
    $handle = fopen('access.log', 'r');
    while (($line = fgets($handle)) !== false) {
        yield $line;
    }
})
    ->filter(fn ($line) => str_contains($line, 'ERROR'))
    ->take(100)
    ->each(fn ($line) => echo $line);

// Eloquent's cursor() returns a LazyCollection, running one query
// and hydrating models one at a time instead of loading them all
foreach (User::cursor() as $user) {
    // memory stays flat regardless of table size
}

The trade-off is that a lazy collection can only be iterated efficiently once per pipeline and can't do things that require knowing the full set up front, like sortBy, without pulling everything into memory anyway. Reach for it specifically when the row count is large and the work is a straightforward per-item transformation or filter.