Collections

Collections

Introduction

La clase Illuminate\Support\Collection proporciona un envoltorio fluido y conveniente para trabajar con arreglos de datos. Por ejemplo, echa un vistazo al siguiente código. Usaremos el helper collect para crear una nueva instancia de colección a partir del array, ejecutar la función strtoupper en cada elemento y luego eliminar todos los elementos vacíos:

$collection = collect(['taylor', 'abigail', null])->map(function (?string $name) {
    return strtoupper($name);
})->reject(function (string $name) {
    return empty($name);
});

Como puedes ver, la clase Collection te permite encadenar sus métodos para realizar un mapeo fluido y la reducción del array subyacente. En general, las colecciones son inmutables, lo que significa que cada método de Collection devuelve una nueva instancia de Collection.

Creating Collections

Como se mencionó anteriormente, el helper collect devuelve una nueva instancia de Illuminate\Support\Collection para el array dado. Así que, crear una colección es tan simple como:

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

ℹ️ Los resultados de las consultas Eloquent siempre se devuelven como instancias de Collection.

Extending Collections

Las colecciones son “macroables”, lo que te permite agregar métodos adicionales a la clase Collection en tiempo de ejecución. El método macro de la clase Illuminate\Support\Collection acepta una función anónima que se ejecutará cuando se llame a tu macro. La función anónima del macro puede acceder a otros métodos de la colección a través de $this, así como si fuera un método real de la clase de colección. Por ejemplo, el siguiente código agrega un método toUpper a la clase Collection:

use Illuminate\Support\Collection;
use Illuminate\Support\Str;
 
Collection::macro('toUpper', function () {
    return $this->map(function (string $value) {
        return Str::upper($value);
    });
});
 
$collection = collect(['first', 'second']);
 
$upper = $collection->toUpper();
 
// ['FIRST', 'SECOND']

Típicamente, debes declarar macros de colección en el método boot de un proveedor de servicios.

Macro Arguments

Si es necesario, puedes definir macros que acepten argumentos adicionales:

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Lang;
 
Collection::macro('toLocale', function (string $locale) {
    return $this->map(function (string $value) use ($locale) {
        return Lang::get($value, [], $locale);
    });
});
 
$collection = collect(['first', 'second']);
 
$translated = $collection->toLocale('es');

Available Methods

Para la mayoría de la documentación restante sobre colecciones, discutiremos cada método disponible en la clase Collection. Recuerda que todos estos métodos se pueden encadenar para manipular de manera fluida el array subyacente. Además, casi todos los métodos devuelven una nueva instancia de Collection, lo que te permite preservar la copia original de la colección cuando sea necesario:

Consultar todos los metodos disponibles

Higher Order Messages

Las colecciones también ofrecen soporte para “mensajes de orden superior”, que son atajos para realizar acciones comunes en colecciones. Los métodos de la colección que proporcionan mensajes de orden superior son: averageavgcontainseacheveryfilterfirstflatMapgroupBykeyBymapmaxminpartitionrejectskipUntilskipWhilesomesortBysortByDescsumtakeUntiltakeWhile, y unique.

Cada mensaje de orden superior se puede acceder como una propiedad dinámica en una instancia de colección. Por ejemplo, usemos el mensaje de orden superior each para llamar a un método en cada objeto dentro de una colección:

use App\Models\User;
 
$users = User::where('votes', '>', 500)->get();
 
$users->each->markAsVip();

Del mismo modo, podemos usar el mensaje de orden superior sum para recopilar el número total de “votos” para una colección de usuarios:

$users = User::where('group', 'Development')->get();
 
return $users->sum->votes;

Lazy Collections

Introduction

❗ Antes de aprender más sobre las colecciones perezosas de Laravel, tómate un tiempo para familiarizarte con los generadores de PHP.

Para complementar la ya poderosa clase Collection, la clase LazyCollection aprovecha los generadores de PHP para permitirte trabajar con conjuntos de datos muy grandes mientras mantienes bajo el uso de memoria.

Por ejemplo, imagina que tu aplicación necesita procesar un archivo de registro de varios gigabytes mientras aprovecha los métodos de colección de Laravel para analizar los registros. En lugar de leer todo el archivo en memoria de una vez, se pueden usar colecciones perezosas para mantener solo una pequeña parte del archivo en memoria en un momento dado:

use App\Models\LogEntry;
use Illuminate\Support\LazyCollection;
 
LazyCollection::make(function () {
    $handle = fopen('log.txt', 'r');
 
    while (($line = fgets($handle)) !== false) {
        yield $line;
    }
})->chunk(4)->map(function (array $lines) {
    return LogEntry::fromLines($lines);
})->each(function (LogEntry $logEntry) {
    // Process the log entry...
});

O, imagina que necesitas iterar a través de 10,000 modelos Eloquent. Al usar colecciones tradicionales de Laravel, todos los 10,000 modelos Eloquent deben cargarse en memoria al mismo tiempo:

use App\Models\User;
 
$users = User::all()->filter(function (User $user) {
    return $user->id > 500;
});

Sin embargo, el método cursor del generador de consultas devuelve una instancia de LazyCollection. Esto te permite seguir ejecutando una sola consulta contra la base de datos, pero también mantener solo un modelo Eloquent cargado en memoria a la vez. En este ejemplo, el callback filter no se ejecuta hasta que realmente iteramos sobre cada usuario de forma individual, lo que permite una drástica reducción en el uso de memoria:

use App\Models\User;
 
$users = User::cursor()->filter(function (User $user) {
    return $user->id > 500;
});
 
foreach ($users as $user) {
    echo $user->id;
}

Creating Lazy Collections

Para crear una instancia de colección perezosa, debes pasar una función generadora de PHP al método make de la colección:

use Illuminate\Support\LazyCollection;
 
LazyCollection::make(function () {
    $handle = fopen('log.txt', 'r');
 
    while (($line = fgets($handle)) !== false) {
        yield $line;
    }
});

The Enumerable Contract

Casi todos los métodos disponibles en la clase Collection también están disponibles en la clase LazyCollection. Ambas clases implementan el contrato Illuminate\Support\Enumerable, que define los siguientes métodos:

Consultar todos los metodos disponibles en Collection y tambien en LazyCollection

❗ Los métodos que mutan la colección (como shift, pop, prepend, etc.) no están disponibles en la clase LazyCollection.

Métodos de Colección Perezosa

Además de los métodos definidos en el contrato Enumerable, la clase LazyCollection contiene los siguientes métodos:

takeUntilTimeout()

El método takeUntilTimeout devuelve una nueva colección perezosa que enumerará valores hasta el tiempo especificado. Después de ese tiempo, la colección dejará de enumerar:

$lazyCollection = LazyCollection::times(INF)
    ->takeUntilTimeout(now()->addMinute());
 
$lazyCollection->each(function (int $number) {
    dump($number);
 
    sleep(1);
});
 
// 1
// 2
// ...
// 58
// 59

Para ilustrar el uso de este método, imagina una aplicación que envía facturas desde la base de datos utilizando un cursor. Podrías definir una tarea programada que se ejecute cada 15 minutos y procese solo facturas durante un máximo de 14 minutos:

use App\Models\Invoice;
use Illuminate\Support\Carbon;
 
Invoice::pending()->cursor()
    ->takeUntilTimeout(
        Carbon::createFromTimestamp(LARAVEL_START)->add(14, 'minutes')
    )
    ->each(fn (Invoice $invoice) => $invoice->submit());

tapEach()

Mientras que el método each llama al callback dado para cada elemento en la colección de inmediato, el método tapEach solo llama al callback dado a medida que se extraen los elementos de la lista uno por uno:

// Nothing has been dumped so far...
$lazyCollection = LazyCollection::times(INF)->tapEach(function (int $value) {
    dump($value);
});
 
// Three items are dumped...
$array = $lazyCollection->take(3)->all();
 
// 1
// 2
// 3

throttle()

El método throttle limitará la colección perezosa de modo que cada valor se devuelva después del número especificado de segundos. Este método es especialmente útil en situaciones donde puede estar interactuando con API externas que limitan la tasa de solicitudes entrantes:

use App\Models\User;
 
User::where('vip', true)
    ->cursor()
    ->throttle(seconds: 1)
    ->each(function (User $user) {
        // Call external API...
    });

remember()

El método remember devuelve una nueva colección perezosa que recordará cualquier valor que ya haya sido enumerado y no los volverá a recuperar en enumeraciones de colección posteriores:

// No query has been executed yet...
$users = User::cursor()->remember();
 
// The query is executed...
// The first 5 users are hydrated from the database...
$users->take(5)->all();
 
// First 5 users come from the collection's cache...
// The rest are hydrated from the database...
$users->take(20)->all();