> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Colecciones

> Aprende a manipular datos de forma eficiente con la clase Collection de Laravel.

## 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.

```php theme={null}
// 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();
```

<Info>
  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.
</Info>

## Crear una colección

### Helper `collect()`

Es la forma más habitual. Pasa un array y obtén una colección.

```php theme={null}
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.

```php theme={null}
$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.

```php theme={null}
$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.

```php theme={null}
$users = collect([
    ['name' => 'Ana García', 'email' => 'ana@example.com'],
    ['name' => 'Luis Pérez', 'email' => 'luis@example.com'],
]);

// 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.

```php theme={null}
$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']
```

<Tip>
  Tras `filter()` los índices pueden quedar con huecos. Llama a `values()` para reindexar desde 0.
</Tip>

### `first` / `last`: obtener un elemento

Devuelve el primer o el último elemento que cumple la condición.

```php theme={null}
$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.

```php theme={null}
$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

```php theme={null}
$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

```php theme={null}
$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.

```php theme={null}
$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.

```php theme={null}
$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.

```php theme={null}
$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
```

<Tip>
  Para sumas sencillas puedes usar `sum()`: `$orders->sum(fn ($o) => $o['price'] * $o['quantity'])`.
</Tip>

### `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.

```php theme={null}
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
```

<Tip>
  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.
</Tip>

Para agregar sobre un escalar o un array, utiliza el paso por referencia (`&`) en la callback.

```php theme={null}
$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.

```php theme={null}
$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.

```php theme={null}
$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.

```php theme={null}
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'),
]);
```

<Warning>
  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.
</Warning>

### Métodos propios de la colección de Eloquent

`Eloquent\Collection` añade métodos específicos.

```php theme={null}
$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.

```php theme={null}
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

```php theme={null}
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

```php theme={null}
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());
```

<Info>
  `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.
</Info>

### Paginado con `take` y `skip`

```php theme={null}
$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

<AccordionGroup>
  <Accordion title="Métodos más habituales">
    | Método                        | Descripció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                          |
  </Accordion>

  <Accordion title="Colecciones frente a funciones de array">
    Las colecciones aportan mucha más legibilidad que las funciones de array.

    ```php theme={null}
    // 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();
    ```
  </Accordion>

  <Accordion title="Colecciones normales frente a LazyCollection">
    * **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.
  </Accordion>
</AccordionGroup>


## Related topics

- [Colecciones Eloquent](/es/eloquent-collections.md)
- [Mensajes de orden superior en colecciones](/es/advanced/higher-order-messages.md)
- [Laravel AI SDK](/es/ai-sdk.md)
- [Guía de actualización de Laravel 12 a 13](/es/blog/upgrade-12-to-13.md)
- [API resources de Eloquent](/es/eloquent-resources.md)
