Saltar al contenido principal

Qué son las colecciones

La clase Illuminate\Support\Collection es un envoltorio fluido para trabajar con arrays. En lugar de invocar por separado las funciones nativas de arrays de PHP, permite transformar los datos de manera intuitiva encadenando métodos.
// Operación tradicional con arrays
$names = array_filter(
    array_map(fn ($user) => $user['name'], $users),
    fn ($name) => $name !== null
);

// Con una colección
$names = collect($users)
    ->pluck('name')
    ->filter()
    ->values();
Las colecciones son inmutables. Cada método devuelve una nueva instancia sin modificar la colección original, de modo que puedes operar con seguridad sin perder los datos de partida.

Crear una colección

Helper collect()

Es la forma más habitual. Pasa un array y obtén una colección.
use Illuminate\Support\Collection;

// Crear una colección a partir de un array
$users = collect([
    ['name' => 'Ana García', 'age' => 28, 'role' => 'admin'],
    ['name' => 'Luis Pérez', 'age' => 34, 'role' => 'editor'],
    ['name' => 'María López', 'age' => 22, 'role' => 'viewer'],
]);

// También funcionan los arrays anidados
$products = collect([
    ['name' => 'Portátil', 'price' => 1200, 'stock' => 5],
    ['name' => 'Ratón', 'price' => 35, 'stock' => 20],
    ['name' => 'Teclado', 'price' => 80, 'stock' => 12],
]);

Collection::make()

Equivalente a collect(). Úsalo cuando prefieras un estilo tipo facade.
$collection = Collection::make([1, 2, 3]);

Collection::fromJson()

Crea una colección a partir de una cadena JSON. Útil para procesar respuestas de APIs externas.
$collection = Collection::fromJson('[{"name":"Ana García"},{"name":"Luis Pérez"}]');

Métodos más habituales

map: transformar cada elemento

Aplica una operación a cada elemento y devuelve una nueva colección con los valores transformados.
$users = collect([
    ['name' => 'Ana García', 'email' => '[email protected]'],
    ['name' => 'Luis Pérez', 'email' => '[email protected]'],
]);

// Enmascara el email para su visualización
$masked = $users->map(function (array $user) {
    $parts = explode('@', $user['email']);
    return [
        'name' => $user['name'],
        'email' => substr($parts[0], 0, 2) . '***@' . $parts[1],
    ];
});

// [['name' => 'Ana García', 'email' => 'an***@example.com'], ...]

filter / reject: filtrar por condición

filter() conserva los elementos que cumplen la condición; reject() descarta los que la cumplen.
$products = collect([
    ['name' => 'Portátil', 'price' => 1200, 'in_stock' => true],
    ['name' => 'Ratón', 'price' => 35, 'in_stock' => false],
    ['name' => 'Teclado', 'price' => 80, 'in_stock' => true],
]);

// Solo los productos con stock
$inStock = $products->filter(fn ($product) => $product['in_stock']);

// Excluye los que no tienen stock (reject es la operación inversa de filter)
$available = $products->reject(fn ($product) => ! $product['in_stock']);

// filter() sin argumentos elimina los valores «falsy»
$names = collect(['Ana', '', null, 'Luis', false])->filter()->values();
// ['Ana', 'Luis']
Tras filter() los índices pueden quedar con huecos. Llama a values() para reindexar desde 0.

first / last: obtener un elemento

Devuelve el primer o el último elemento que cumple la condición.
$orders = collect([
    ['id' => 1, 'status' => 'shipped', 'amount' => 50],
    ['id' => 2, 'status' => 'pending', 'amount' => 120],
    ['id' => 3, 'status' => 'pending', 'amount' => 35],
]);

// Primer pedido pendiente
$nextOrder = $orders->first(fn ($order) => $order['status'] === 'pending');
// ['id' => 2, 'status' => 'pending', 'amount' => 120]

// Valor por defecto si no se cumple la condición
$order = $orders->first(fn ($order) => $order['status'] === 'cancelled', null);

// Último elemento
$latest = $orders->last();

pluck: extraer una clave concreta

Extrae únicamente un campo de arrays o modelos anidados.
$users = collect([
    ['id' => 1, 'name' => 'Ana García', 'department' => 'Desarrollo'],
    ['id' => 2, 'name' => 'Luis Pérez', 'department' => 'Ventas'],
    ['id' => 3, 'name' => 'María López', 'department' => 'Desarrollo'],
]);

// Solo los nombres
$names = $users->pluck('name');
// ['Ana García', 'Luis Pérez', 'María López']

// Mapea por id
$nameById = $users->pluck('name', 'id');
// [1 => 'Ana García', 2 => 'Luis Pérez', 3 => 'María López']

groupBy: agrupar por una clave

$users = collect([
    ['name' => 'Ana García', 'department' => 'Desarrollo'],
    ['name' => 'Luis Pérez', 'department' => 'Ventas'],
    ['name' => 'María López', 'department' => 'Desarrollo'],
    ['name' => 'Pablo Ruiz', 'department' => 'Ventas'],
]);

$byDepartment = $users->groupBy('department');
// [
//   'Desarrollo' => [['name' => 'Ana García', ...], ['name' => 'María López', ...]],
//   'Ventas'     => [['name' => 'Luis Pérez', ...], ['name' => 'Pablo Ruiz', ...]],
// ]

// Clave de grupo dinámica con una closure
$byFirstChar = $users->groupBy(fn ($user) => mb_substr($user['name'], 0, 1));

sortBy / sortByDesc: ordenar

$products = collect([
    ['name' => 'Portátil', 'price' => 1200],
    ['name' => 'Ratón', 'price' => 35],
    ['name' => 'Teclado', 'price' => 80],
]);

// Por precio ascendente
$cheapFirst = $products->sortBy('price');

// Por precio descendente
$expensiveFirst = $products->sortByDesc('price');

// Ordenar por varias claves
$sorted = $products->sortBy([
    ['price', 'asc'],
    ['name', 'asc'],
]);

each: ejecutar código sobre cada elemento

Úsalo para operaciones con efectos secundarios (logs, envío de correos, etc.). No modifica la colección.
$orders = collect([
    ['id' => 1, 'user_id' => 10, 'amount' => 50],
    ['id' => 2, 'user_id' => 11, 'amount' => 120],
]);

$orders->each(function (array $order) {
    \Log::info("Procesando pedido #{$order['id']}", ['amount' => $order['amount']]);
    // Envío de notificaciones, despacho a colas...
});

// Devuelve false para interrumpir el bucle
$orders->each(function (array $order) {
    if ($order['amount'] > 100) {
        return false; // Sale del bucle
    }
    // ...
});

flatMap: mapear y aplanar un nivel

Convierte cada elemento en un array y aplana el conjunto a una sola dimensión.
$users = collect([
    ['name' => 'Ana García', 'tags' => ['php', 'laravel']],
    ['name' => 'Luis Pérez', 'tags' => ['javascript', 'vue']],
]);

// Lista plana de todas las etiquetas
$allTags = $users->flatMap(fn ($user) => $user['tags']);
// ['php', 'laravel', 'javascript', 'vue']

reduce: agregar

Reduce toda la colección a un único valor.
$orders = collect([
    ['product' => 'Portátil', 'quantity' => 1, 'price' => 1200],
    ['product' => 'Ratón', 'quantity' => 2, 'price' => 35],
    ['product' => 'Teclado', 'quantity' => 1, 'price' => 80],
]);

// Importe total
$total = $orders->reduce(
    fn ($carry, $order) => $carry + ($order['price'] * $order['quantity']),
    0
);
// 1350
Para sumas sencillas puedes usar sum(): $orders->sum(fn ($o) => $o['price'] * $o['quantity']).

reduceInto: agregar sobre un objeto

Se comporta como reduce, pero la callback no necesita devolver un valor. Es útil para modificar directamente un objeto existente.
class OrderStats
{
    public int $total = 0;
    public int $count = 0;
}

$orders = collect([
    ['amount' => 100],
    ['amount' => 250],
    ['amount' => 50],
]);

$stats = $orders->reduceInto(new OrderStats, function (OrderStats $stats, array $order) {
    $stats->total += $order['amount'];
    $stats->count++;
});

$stats->total; // 400
$stats->count; // 3
En reduce el retorno de la callback se convierte en el siguiente $carry, por lo que encaja bien con la agregación de valores primitivos. En reduceInto se muta directamente un objeto, dando lugar a un código más simple.
Para agregar sobre un escalar o un array, utiliza el paso por referencia (&) en la callback.
$collection = collect([1, 2, 3, 4, 5]);

$even = $collection->reduceInto([], function (array &$result, int $value) {
    if ($value % 2 === 0) {
        $result[] = $value;
    }
});

// [2, 4]

chunk: dividir en trozos

Divide una colección grande en fragmentos del tamaño indicado. Es útil para procesamiento por lotes o para paginar en pantalla.
$users = collect(range(1, 100))->map(fn ($i) => ['id' => $i, 'name' => "Usuario {$i}"]);

// Divide en trozos de 10
$chunks = $users->chunk(10);
// $chunks->count() === 10

// Procesa cada lote
$chunks->each(function (\Illuminate\Support\Collection $batch) {
    // Operaciones de BD, envío de correos, etc. por lote
});

Encadenamiento de métodos

La verdadera potencia de las colecciones está en el encadenamiento. Puedes expresar transformaciones complejas de datos en una sola secuencia.
$orders = collect([
    ['customer' => 'Ana García',  'status' => 'completed', 'amount' => 50,  'category' => 'Electrónica'],
    ['customer' => 'Luis Pérez',  'status' => 'pending',   'amount' => 120, 'category' => 'Electrónica'],
    ['customer' => 'María López', 'status' => 'completed', 'amount' => 35,  'category' => 'Papelería'],
    ['customer' => 'Pablo Ruiz',  'status' => 'completed', 'amount' => 80,  'category' => 'Electrónica'],
    ['customer' => 'Carla Soto',  'status' => 'cancelled', 'amount' => 20,  'category' => 'Papelería'],
]);

// Obtener los pedidos de electrónica completados por importe descendente y quedarnos solo con el cliente y el importe
$result = $orders
    ->filter(fn ($order) => $order['status'] === 'completed')
    ->filter(fn ($order) => $order['category'] === 'Electrónica')
    ->sortByDesc('amount')
    ->map(fn ($order) => [
        'customer' => $order['customer'],
        'amount'   => number_format($order['amount']) . ' €',
    ])
    ->values();

// [
//   ['customer' => 'Pablo Ruiz', 'amount' => '80 €'],
//   ['customer' => 'Ana García', 'amount' => '50 €'],
// ]

Integración con Eloquent

Los resultados de las consultas de Eloquent siempre son instancias de Illuminate\Database\Eloquent\Collection. Esta clase extiende la Collection base, así que todos los métodos vistos están disponibles.
use App\Models\User;
use App\Models\Order;

// El resultado de get() es una colección
$users = User::where('is_active', true)->get(); // Collection

// Puedes usar directamente los métodos de colección
$adminEmails = User::all()
    ->filter(fn ($user) => $user->role === 'admin')
    ->pluck('email');

// También puedes manipular las relaciones ya cargadas
$orders = Order::with('items')->where('status', 'completed')->get();

$summary = $orders->map(fn ($order) => [
    'id'         => $order->id,
    'customer'   => $order->user->name,
    'item_count' => $order->items->count(),
    'total'      => $order->items->sum('price'),
]);
El filtrado y la ordenación que pueda hacer la base de datos deben resolverse en el query builder (where, orderBy, etc.), no en la colección. Si filtras después de convertir a colección, cargarás en memoria todos los datos innecesarios.

Métodos propios de la colección de Eloquent

Eloquent\Collection añade métodos específicos.
$users = User::all();

// Busca un modelo por ID
$user = $users->find(1);

// Obtiene la colección de claves primarias
$ids = $users->modelKeys(); // [1, 2, 3, ...]

// Carga relaciones de forma masiva
$users->load('orders', 'profile');

// Diferencia e intersección
$diff = $users->diff($otherUsers);
$intersect = $users->intersect($otherUsers);

Lazy Collections

Mientras que una colección normal carga en memoria todos los datos, LazyCollection aprovecha los generadores de PHP y procesa los elementos uno a uno. Permite ahorrar memoria cuando trabajas con decenas de miles de registros.
use Illuminate\Support\LazyCollection;

// Colección normal: carga todos los datos en memoria
$users = User::all(); // Con 100 000 registros, todos están en memoria

// LazyCollection: procesa uno a uno
User::cursor()->each(function (User $user) {
    // Procesa cada usuario individualmente
    // Solo un registro en memoria en cada instante
});

Crear una LazyCollection

use Illuminate\Support\LazyCollection;

// A partir de una closure (generador)
$lazy = LazyCollection::make(function () {
    $handle = fopen('large-file.csv', 'r');

    while (($line = fgetcsv($handle)) !== false) {
        yield $line;
    }

    fclose($handle);
});

// El método cursor() de Eloquent devuelve una LazyCollection
$lazy = User::where('is_active', true)->cursor();

Procesamiento por lotes de grandes volúmenes

use App\Models\Order;

// Procesa todos los pedidos uno a uno (eficiente en memoria)
Order::cursor()
    ->filter(fn ($order) => $order->amount > 100)
    ->each(fn ($order) => $order->sendConfirmationEmail());

// Con cursor() se recuperan los pedidos uno a uno y filter/each los tratan en pipeline
Order::where('status', 'completed')
    ->cursor()
    ->filter(fn ($order) => $order->amount > 100)
    ->each(fn ($order) => $order->sendConfirmationEmail());
cursor() recupera los registros uno a uno de la base de datos, con lo que ahorra mucha memoria al procesar grandes cantidades. Ten en cuenta, sin embargo, que la conexión con la base de datos se mantiene activa hasta que finaliza el procesamiento.

Paginado con take y skip

$lazy = LazyCollection::make(function () {
    foreach (range(1, 1000000) as $i) {
        yield $i;
    }
});

// Salta los primeros 1000 y obtén los siguientes 100
$page = $lazy->skip(1000)->take(100)->values();

Resumen

MétodoDescripción
collect($array)Crea una colección
map($callback)Transforma cada elemento
filter($callback)Filtra por condición
reject($callback)Descarta los elementos que cumplen la condición
first($callback)Devuelve el primer elemento que cumple la condición
pluck($key)Extrae los valores de una clave
groupBy($key)Agrupa por una clave
sortBy($key)Ordena ascendente
sortByDesc($key)Ordena descendente
each($callback)Ejecuta código sobre cada elemento (efectos secundarios)
flatMap($callback)Mapea y aplana
reduce($callback, $initial)Reduce a un solo valor
chunk($size)Divide en trozos del tamaño indicado
values()Reindexa los valores
sum($key)Calcula la suma
count()Devuelve el número de elementos
Las colecciones aportan mucha más legibilidad que las funciones de array.
// Con funciones de array
$result = array_values(array_filter(
    array_map(fn ($u) => $u['name'], $users),
    fn ($name) => strlen($name) > 2
));

// Con una colección
$result = collect($users)
    ->pluck('name')
    ->filter(fn ($name) => strlen($name) > 2)
    ->values()
    ->all();
  • Colección normal: datos del orden de cientos o miles de elementos. Sencilla e intuitiva.
  • LazyCollection: grandes volúmenes (decenas de miles o más) donde interesa ahorrar memoria. Combínala con cursor() para obtener el máximo rendimiento.
  • get() de Eloquent devuelve una colección normal; cursor() devuelve una LazyCollection.
Última modificación el 13 de julio de 2026