Collections
Com utilitzar les Collections de Laravel: mètodes de transformació, filtratge, agrupació, reducció, higher-order messages i Lazy Collections.
Què són les Collections?#
Les Collections de Laravel són un embolcall elegant al voltant dels arrays de PHP que proporciona una interfície fluida i funcional per manipular conjunts de dades. En lloc de treballar amb les funcions natives de PHP com array_map(), array_filter(), array_reduce() i array_sort() --cadascuna amb la seva pròpia sintaxi inconsistent i ordres de paràmetres diferents--, les Collections ofereixen una API coherent i encadenable que transforma el codi en quelcom molt més llegible i expressiu.
La diferència fonamental entre treballar amb arrays PHP i amb Collections és la mateixa que entre escriure instruccions imperatives pas a pas i declarar transformacions funcionals. Amb arrays nadius, has d'anar mutant variables, creant arrays temporals i gestionant índexs manualment. Amb Collections, descrius què vols obtenir mitjançant una cadena de transformacions, i cada mètode retorna una nova Collection sense modificar l'original. Aquesta immutabilitat per defecte fa que el codi sigui més previsible i menys propens a errors.
Per què importa#
Per entendre l'avantatge pràctic, compara el mateix processament de dades amb arrays PHP i amb Collections:
// Amb funcions PHP natives: verbose, propens a errors, difícil de llegir
$users = [
['name' => 'Joan', 'email' => 'joan@test.com', 'active' => true, 'role' => 'admin'],
['name' => 'Maria', 'email' => 'maria@test.com', 'active' => false, 'role' => 'editor'],
['name' => 'Pere', 'email' => 'pere@test.com', 'active' => true, 'role' => 'editor'],
['name' => 'Anna', 'email' => 'anna@test.com', 'active' => true, 'role' => 'admin'],
];
$activeEmails = array_map(
fn ($user) => $user['email'],
array_filter($users, fn ($user) => $user['active'])
);
$activeEmails = array_values($activeEmails); // Re-indexar claus
sort($activeEmails);
// Amb Collections: clar, encadenable, expressiu
$activeEmails = collect($users)
->filter(fn ($user) => $user['active'])
->pluck('email')
->sort()
->values();La versió amb Collections es llegeix gairebé com una frase en anglès: "agafa els usuaris, filtra els actius, extreu els correus, ordena'ls i re-indexa les claus". Cada pas és una transformació explícita que retorna una nova Collection, permetent encadenar el següent pas sense variables intermediàries.
Crear Collections#
Hi ha diverses maneres de crear una Collection. La més comuna és la funció helper collect(), que accepta qualsevol valor iterable i el converteix en una instància de Illuminate\Support\Collection:
use Illuminate\Support\Collection;
// Amb la funció helper collect()
$numbers = collect([1, 2, 3, 4, 5]);
// Amb el constructor estàtic
$names = Collection::make(['Joan', 'Maria', 'Pere']);
// A partir d'un rang
$range = collect(range(1, 100));
// Collection buida
$empty = collect();
// A partir d'un array associatiu
$config = collect([
'driver' => 'mysql',
'host' => 'localhost',
'port' => 3306,
]);Una de les integracions més potents és que Eloquent retorna els resultats de les consultes com a Collections. Quan fas User::all() o User::where('active', true)->get(), el resultat no és un array: és una instància de Illuminate\Database\Eloquent\Collection, que estén la Collection base amb mètodes específics per a models:
// Eloquent retorna Collections automàticament
$users = User::all(); // Eloquent Collection
$activeUsers = User::where('active', true)->get(); // Eloquent Collection
// Pots encadenar mètodes de Collection directament
$emails = User::where('active', true)
->get()
->pluck('email')
->sort();La funció collect() i Collection::make() són equivalents. La funció helper és simplement una drecera més còmoda que es fa servir amb més freqüència al codi del dia a dia.
Mètodes de transformació#
Els mètodes de transformació permeten convertir cada element d'una Collection en una forma diferent. Són el cor del treball funcional amb dades i retornen una nova Collection amb els elements transformats, sense modificar l'original.
map()#
El mètode map() aplica una funció a cada element de la Collection i retorna una nova Collection amb els resultats. És el mètode de transformació més fonamental i s'utilitza constantment en el desenvolupament amb Laravel:
$names = collect(['joan', 'maria', 'pere']);
$capitalized = $names->map(fn ($name) => ucfirst($name));
// ['Joan', 'Maria', 'Pere']
// Transformar objectes en arrays
$users = User::all();
$userData = $users->map(fn ($user) => [
'id' => $user->id,
'full_name' => $user->name . ' ' . $user->surname,
'registered' => $user->created_at->format('d/m/Y'),
]);El callback de map() rep l'element actual com a primer argument i la clau com a segon. La Collection original roman intacta: $names segueix sent ['joan', 'maria', 'pere'] després de cridar map().
flatMap()#
El mètode flatMap() combina map() i flatten() en una sola operació. Aplica una funció a cada element i després aplana el resultat un nivell. Això és útil quan la funció de transformació retorna un array per a cada element i vols un sol array pla com a resultat:
$courses = collect([
['name' => 'Laravel', 'topics' => ['routing', 'eloquent', 'blade']],
['name' => 'Vue', 'topics' => ['components', 'directives', 'vuex']],
]);
$allTopics = $courses->flatMap(fn ($course) => $course['topics']);
// ['routing', 'eloquent', 'blade', 'components', 'directives', 'vuex']Sense flatMap(), si fessis servir map(), obtindries un array d'arrays: [['routing', 'eloquent', 'blade'], ['components', 'directives', 'vuex']]. El flatMap() aplana automàticament aquest resultat en una sola llista.
mapWithKeys()#
El mètode mapWithKeys() transforma cada element en un parell clau-valor, permetent construir arrays associatius a partir de qualsevol Collection:
$employees = collect([
['id' => 1, 'name' => 'Joan', 'department' => 'Engineering'],
['id' => 2, 'name' => 'Maria', 'department' => 'Marketing'],
['id' => 3, 'name' => 'Pere', 'department' => 'Engineering'],
]);
$directory = $employees->mapWithKeys(fn ($employee) => [
$employee['id'] => $employee['name'],
]);
// [1 => 'Joan', 2 => 'Maria', 3 => 'Pere']
// Exemple pràctic: crear un array per a un select de formulari
$options = User::all()->mapWithKeys(fn ($user) => [
$user->id => "{$user->name} ({$user->email})",
]);pluck()#
El mètode pluck() extreu els valors d'una sola columna o propietat. És un dels mètodes més utilitzats perquè sovint necessites obtenir una llista de valors d'un camp específic. Opcionalment, pots passar un segon argument per definir les claus del resultat:
$users = collect([
['id' => 1, 'name' => 'Joan', 'email' => 'joan@test.com'],
['id' => 2, 'name' => 'Maria', 'email' => 'maria@test.com'],
['id' => 3, 'name' => 'Pere', 'email' => 'pere@test.com'],
]);
// Extreure una sola columna
$names = $users->pluck('name');
// ['Joan', 'Maria', 'Pere']
// Extreure amb claus personalitzades
$emailsByName = $users->pluck('email', 'name');
// ['Joan' => 'joan@test.com', 'Maria' => 'maria@test.com', 'Pere' => 'pere@test.com']
// Amb notació de punt per a dades niuades
$collection = collect([
['user' => ['name' => 'Joan', 'address' => ['city' => 'Andorra la Vella']]],
['user' => ['name' => 'Maria', 'address' => ['city' => 'Escaldes']]],
]);
$cities = $collection->pluck('user.address.city');
// ['Andorra la Vella', 'Escaldes']transform()#
A diferència de tots els mètodes anteriors, transform() és mutable: modifica la Collection original en lloc de retornar-ne una de nova. Utilitza'l amb precaució i només quan realment vols modificar la Collection existent:
$prices = collect([10.5, 20.3, 15.7]);
// transform() modifica la Collection original
$prices->transform(fn ($price) => round($price * 1.21, 2)); // Afegir IVA
// $prices ara és [12.71, 24.56, 19.0]transform() és l'excepció a la regla d'immutabilitat de les Collections. Mentre que map() retorna una nova Collection deixant l'original intacta, transform() modifica directament la Collection sobre la qual es crida. En general, prefereix map() per mantenir la immutabilitat.
flatten()#
El mètode flatten() aplana una Collection multinivell en una sola dimensió. Pots especificar la profunditat d'aplanament:
$nested = collect([
[1, 2, 3],
[4, [5, 6]],
[7, 8],
]);
$flat = $nested->flatten();
// [1, 2, 3, 4, 5, 6, 7, 8]
// Aplanar només un nivell
$oneLevel = $nested->flatten(1);
// [1, 2, 3, 4, [5, 6], 7, 8]collapse()#
El mètode collapse() uneix un array d'arrays en un sol array pla. A diferència de flatten(), collapse() només treballa un nivell de profunditat:
$pages = collect([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]);
$all = $pages->collapse();
// [1, 2, 3, 4, 5, 6, 7, 8, 9]La diferència entre collapse() i flatten() és subtil: collapse() combina els elements de primer nivell que siguin arrays, mentre que flatten() aplana recursivament tots els nivells de profunditat (a menys que especifiquis una profunditat).
zip()#
El mètode zip() fusiona la Collection amb un o més arrays, agrupant els elements per posició. Cada element del resultat és un array amb els valors corresponents de cada input:
$names = collect(['Joan', 'Maria', 'Pere']);
$ages = [28, 34, 45];
$combined = $names->zip($ages);
// [['Joan', 28], ['Maria', 34], ['Pere', 45]]
// Útil per combinar dades de fonts diferents
$labels = collect(['Nom', 'Email', 'Rol']);
$values = ['Joan', 'joan@test.com', 'admin'];
$fields = $labels->zip($values)->map(fn ($pair) => [
'label' => $pair[0],
'value' => $pair[1],
]);combine()#
El mètode combine() utilitza els elements de la Collection com a claus i els combina amb un altre array com a valors:
$keys = collect(['name', 'email', 'role']);
$values = ['Joan', 'joan@test.com', 'admin'];
$user = $keys->combine($values);
// ['name' => 'Joan', 'email' => 'joan@test.com', 'role' => 'admin']Mètodes de filtratge#
Els mètodes de filtratge permeten seleccionar subconjunts d'elements d'una Collection basant-se en condicions. Com amb les transformacions, retornen una nova Collection sense modificar l'original.
filter() i reject()#
El mètode filter() manté els elements que compleixen una condició, mentre que reject() fa exactament el contrari: elimina els elements que la compleixen. Quan es crida filter() sense arguments, elimina tots els valors falsy (null, false, '', 0):
$numbers = collect([1, null, 3, '', 5, 0, 7, false]);
// filter() sense arguments elimina valors falsy
$truthy = $numbers->filter();
// [1, 3, 5, 7]
// filter() amb condició
$users = collect([
['name' => 'Joan', 'age' => 28, 'active' => true],
['name' => 'Maria', 'age' => 17, 'active' => true],
['name' => 'Pere', 'age' => 45, 'active' => false],
['name' => 'Anna', 'age' => 32, 'active' => true],
]);
$activeAdults = $users->filter(
fn ($user) => $user['active'] && $user['age'] >= 18
);
// Joan (28, actiu), Anna (32, activa)
// reject() és l'invers de filter()
$inactive = $users->reject(fn ($user) => $user['active']);
// Pere (45, inactiu)Fixa't que filter() preserva les claus originals de l'array. Si necessites re-indexar les claus després de filtrar, encadena ->values() al final.
where(), whereIn(), whereBetween(), whereNull()#
Aquests mètodes proporcionen una sintaxi més declarativa per a filtratges comuns, inspirada en la sintaxi del Query Builder d'Eloquent. Són especialment útils quan treballes amb Collections d'arrays o objectes:
$products = collect([
['name' => 'Portàtil', 'category' => 'electrònica', 'price' => 999, 'stock' => null],
['name' => 'Teclat', 'category' => 'electrònica', 'price' => 79, 'stock' => 50],
['name' => 'Cadira', 'category' => 'mobiliari', 'price' => 350, 'stock' => 12],
['name' => 'Monitor', 'category' => 'electrònica', 'price' => 450, 'stock' => 0],
['name' => 'Taula', 'category' => 'mobiliari', 'price' => 600, 'stock' => null],
]);
// where() - filtrar per valor exacte
$electronics = $products->where('category', 'electrònica');
// whereIn() - filtrar per múltiples valors possibles
$selected = $products->whereIn('name', ['Portàtil', 'Monitor']);
// whereBetween() - filtrar per rang
$midRange = $products->whereBetween('price', [100, 500]);
// Teclat (79 no), Cadira (350 sí), Monitor (450 sí)
// whereNull() i whereNotNull()
$noStock = $products->whereNull('stock');
// Portàtil i Taula (stock és null)
$withStock = $products->whereNotNull('stock');
// Teclat, Cadira, Monitor (stock té valor, incloent 0)El mètode where() utilitza comparació laxa per defecte. Si necessites comparació estricta de tipus, utilitza whereStrict(). Per exemple, where('stock', 0) trobaria tant 0 com null i false amb comparació laxa, mentre que whereStrict('stock', 0) només trobaria el valor enter 0.
first(), firstWhere() i last()#
Aquests mètodes retornen un sol element en lloc d'una Collection. Són útils quan busques un element específic:
$users = collect([
['name' => 'Joan', 'role' => 'editor', 'score' => 85],
['name' => 'Maria', 'role' => 'admin', 'score' => 92],
['name' => 'Pere', 'role' => 'editor', 'score' => 78],
['name' => 'Anna', 'role' => 'admin', 'score' => 95],
]);
// first() - primer element que compleix la condició
$firstAdmin = $users->first(fn ($user) => $user['role'] === 'admin');
// ['name' => 'Maria', 'role' => 'admin', 'score' => 92]
// first() amb valor per defecte si no troba res
$superAdmin = $users->first(
fn ($user) => $user['role'] === 'superadmin',
['name' => 'Desconegut', 'role' => 'none', 'score' => 0]
);
// firstWhere() - drecera per a condicions simples
$firstAdmin = $users->firstWhere('role', 'admin');
// last() - últim element que compleix la condició
$lastAdmin = $users->last(fn ($user) => $user['role'] === 'admin');
// ['name' => 'Anna', 'role' => 'admin', 'score' => 95]unique() i duplicates()#
El mètode unique() elimina els elements duplicats de la Collection. Quan treballes amb arrays d'objectes, pots especificar per quina clau determinar la unicitat. El mètode duplicates() fa el contrari: retorna els elements que apareixen més d'una vegada:
// Unicitat simple
$numbers = collect([1, 2, 2, 3, 3, 3, 4]);
$unique = $numbers->unique();
// [1, 2, 3, 4]
// Unicitat per clau
$employees = collect([
['name' => 'Joan', 'department' => 'Engineering'],
['name' => 'Maria', 'department' => 'Marketing'],
['name' => 'Pere', 'department' => 'Engineering'],
['name' => 'Anna', 'department' => 'Marketing'],
]);
$departments = $employees->unique('department');
// Joan (Engineering), Maria (Marketing) - un per departament
// uniqueStrict() per comparació estricta de tipus
$mixed = collect([1, '1', 2, '2']);
$mixed->unique(); // [1, 2] - '1' es considera igual que 1
$mixed->uniqueStrict(); // [1, '1', 2, '2'] - tipus diferents
// duplicates() - trobar elements repetits
$tags = collect(['laravel', 'php', 'laravel', 'vue', 'php', 'php']);
$repeated = $tags->duplicates();
// [2 => 'laravel', 4 => 'php', 5 => 'php']take(), skip() i slice()#
Aquests mètodes permeten seleccionar porcions de la Collection per posició:
$items = collect(['a', 'b', 'c', 'd', 'e', 'f', 'g']);
// take() - agafar els primers N elements
$first3 = $items->take(3);
// ['a', 'b', 'c']
// take() amb valor negatiu - agafar els últims N
$last2 = $items->take(-2);
// ['f', 'g']
// skip() - saltar els primers N elements
$afterFirst2 = $items->skip(2);
// ['c', 'd', 'e', 'f', 'g']
// slice() - extreure una porció (offset, length)
$middle = $items->slice(2, 3);
// ['c', 'd', 'e']Mètodes d'agregació#
Els mètodes d'agregació redueixen una Collection a un sol valor, ja sigui un nombre, un string o qualsevol altre resultat derivat del processament de tots els elements.
sum(), avg(), min(), max(), count()#
Aquests mètodes proporcionen les funcions estadístiques bàsiques. Quan treballes amb Collections d'arrays o objectes, pots especificar la clau sobre la qual calcular:
$orders = collect([
['product' => 'Portàtil', 'quantity' => 1, 'price' => 999],
['product' => 'Teclat', 'quantity' => 3, 'price' => 79],
['product' => 'Ratolí', 'quantity' => 2, 'price' => 45],
]);
$totalPrice = $orders->sum('price'); // 1123
$averagePrice = $orders->avg('price'); // 374.33
$cheapest = $orders->min('price'); // 45
$mostExpensive = $orders->max('price'); // 999
$totalItems = $orders->count(); // 3
// sum() amb callback per a càlculs complexos
$totalRevenue = $orders->sum(fn ($order) => $order['quantity'] * $order['price']);
// (1 * 999) + (3 * 79) + (2 * 45) = 999 + 237 + 90 = 1326median() i mode()#
Aquests mètodes proporcionen mesures estadístiques addicionals. La mediana és el valor central quan els elements estan ordenats, i la moda és el valor que apareix amb més freqüència:
$scores = collect([85, 92, 78, 95, 88, 92, 76]);
$median = $scores->median(); // 88 (valor central)
$mode = $scores->mode(); // [92] (apareix 2 vegades)
// Amb clau per a collections d'arrays
$students = collect([
['name' => 'Joan', 'grade' => 7],
['name' => 'Maria', 'grade' => 9],
['name' => 'Pere', 'grade' => 8],
]);
$medianGrade = $students->median('grade'); // 8reduce()#
El mètode reduce() és el mètode d'agregació més flexible. Itera sobre tots els elements, acumulant un resultat personalitzat. Accepta un callback amb l'acumulador i l'element actual, i un valor inicial opcional:
$numbers = collect([1, 2, 3, 4, 5]);
// Suma manual amb reduce
$sum = $numbers->reduce(fn ($carry, $item) => $carry + $item, 0);
// 15
// Construir un string a partir d'una llista
$sentence = collect(['Laravel', 'és', 'fantàstic'])->reduce(
fn ($carry, $word) => $carry . ' ' . $word
);
// ' Laravel és fantàstic' (fixat: el primer carry és null)
// Exemple pràctic: calcular el total d'un carretó de compra
$cart = collect([
['product' => 'Samarreta', 'price' => 25, 'quantity' => 2, 'discount' => 0.1],
['product' => 'Pantalons', 'price' => 60, 'quantity' => 1, 'discount' => 0],
['product' => 'Sabates', 'price' => 120, 'quantity' => 1, 'discount' => 0.2],
]);
$total = $cart->reduce(function ($carry, $item) {
$subtotal = $item['price'] * $item['quantity'];
$discounted = $subtotal * (1 - $item['discount']);
return $carry + $discounted;
}, 0);
// (25*2*0.9) + (60*1*1.0) + (120*1*0.8) = 45 + 60 + 96 = 201pipe()#
El mètode pipe() passa la Collection sencera a un callback i retorna el resultat. A diferència de reduce() que itera sobre cada element, pipe() treballa amb tota la Collection d'un cop. Això és útil per aplicar operacions complexes que necessiten veure tots els elements simultàniament:
$result = collect([1, 2, 3, 4, 5])
->pipe(fn ($collection) => $collection->sum() / $collection->count());
// 3 (la mitjana)
// Exemple pràctic: formatar un resum d'estadístiques
$stats = collect($salesData)
->pipe(function ($sales) {
return [
'total' => $sales->sum('amount'),
'average' => round($sales->avg('amount'), 2),
'count' => $sales->count(),
'best_day' => $sales->sortByDesc('amount')->first()['date'],
];
});Agrupació i ordenació#
Aquests mètodes permeten organitzar els elements d'una Collection de maneres específiques: agrupar-los per categories, re-indexar-los, ordenar-los o dividir-los en parts.
groupBy()#
El mètode groupBy() agrupa els elements per una clau o un callback, retornant una Collection de Collections. Cada grup conté tots els elements que comparteixen el mateix valor per a la clau especificada:
$employees = collect([
['name' => 'Joan', 'department' => 'Engineering', 'level' => 'senior'],
['name' => 'Maria', 'department' => 'Marketing', 'level' => 'junior'],
['name' => 'Pere', 'department' => 'Engineering', 'level' => 'junior'],
['name' => 'Anna', 'department' => 'Marketing', 'level' => 'senior'],
['name' => 'Lluís', 'department' => 'Engineering', 'level' => 'senior'],
]);
// Agrupar per departament
$byDepartment = $employees->groupBy('department');
// [
// 'Engineering' => [Joan, Pere, Lluís],
// 'Marketing' => [Maria, Anna],
// ]
// Agrupar per callback per a lògica personalitzada
$byLevel = $employees->groupBy(fn ($employee) => $employee['level']);
// [
// 'senior' => [Joan, Anna, Lluís],
// 'junior' => [Maria, Pere],
// ]
// Agrupar per múltiples nivells
$grouped = $employees->groupBy(['department', 'level']);
// [
// 'Engineering' => ['senior' => [Joan, Lluís], 'junior' => [Pere]],
// 'Marketing' => ['junior' => [Maria], 'senior' => [Anna]],
// ]keyBy()#
El mètode keyBy() re-indexa la Collection utilitzant els valors d'una clau especificada o el resultat d'un callback. Cada element es converteix en el valor d'una clau única:
$users = collect([
['id' => 101, 'name' => 'Joan', 'email' => 'joan@test.com'],
['id' => 102, 'name' => 'Maria', 'email' => 'maria@test.com'],
['id' => 103, 'name' => 'Pere', 'email' => 'pere@test.com'],
]);
$byId = $users->keyBy('id');
// [
// 101 => ['id' => 101, 'name' => 'Joan', ...],
// 102 => ['id' => 102, 'name' => 'Maria', ...],
// 103 => ['id' => 103, 'name' => 'Pere', ...],
// ]
// Ara pots accedir directament per ID
$joan = $byId[101];
// keyBy amb callback
$byEmail = $users->keyBy(fn ($user) => strtolower($user['email']));Si diversos elements comparteixen la mateixa clau, keyBy() només conserva l'últim element amb aquella clau. Assegura't que la clau que utilitzes sigui única, o utilitza groupBy() si necessites agrupar diversos elements sota la mateixa clau.
sortBy(), sortByDesc() i sort()#
Aquests mètodes ordenen els elements de la Collection. sortBy() ordena ascendentment per una clau o callback, sortByDesc() descendentment, i sort() utilitza la comparació natural de PHP:
$products = collect([
['name' => 'Monitor', 'price' => 450, 'rating' => 4.5],
['name' => 'Teclat', 'price' => 79, 'rating' => 4.8],
['name' => 'Portàtil', 'price' => 999, 'rating' => 4.2],
['name' => 'Ratolí', 'price' => 45, 'rating' => 4.8],
]);
// Ordenar per preu (ascendent)
$byPrice = $products->sortBy('price');
// Ratolí (45), Teclat (79), Monitor (450), Portàtil (999)
// Ordenar per preu (descendent)
$expensive = $products->sortByDesc('price');
// Ordenar per múltiples criteris
$sorted = $products->sortBy([
['rating', 'desc'], // Primer per valoració (descendent)
['price', 'asc'], // Després per preu (ascendent) si la valoració és igual
]);
// Teclat (4.8, 79), Ratolí (4.8, 45), Monitor (4.5, 450), Portàtil (4.2, 999)
// Ordenar amb callback
$byNameLength = $products->sortBy(fn ($product) => strlen($product['name']));
// sort() per a col·leccions simples
$numbers = collect([3, 1, 4, 1, 5, 9, 2, 6]);
$sorted = $numbers->sort()->values();
// [1, 1, 2, 3, 4, 5, 6, 9]Fixa't que sortBy() preserva les claus originals. Si necessites claus numèriques consecutives, encadena ->values() al final.
reverse() i shuffle()#
El mètode reverse() inverteix l'ordre dels elements de la Collection, i shuffle() els barreja aleatòriament:
$items = collect([1, 2, 3, 4, 5]);
$reversed = $items->reverse()->values();
// [5, 4, 3, 2, 1]
$shuffled = $items->shuffle();
// [3, 1, 5, 2, 4] (aleatori cada vegada)chunk()#
El mètode chunk() divideix la Collection en grups de mida N. L'últim grup pot tenir menys elements si el total no és divisible per N. Això és útil per a paginació manual, processament per lots o layouts en graella:
$items = collect([1, 2, 3, 4, 5, 6, 7, 8]);
$chunks = $items->chunk(3);
// [[1, 2, 3], [4, 5, 6], [7, 8]]
// Útil per a layouts en graella a Blade
// @foreach ($products->chunk(4) as $row)
// <div class="row">
// @foreach ($row as $product)
// <div class="col-3">{{ $product->name }}</div>
// @endforeach
// </div>
// @endforeachpartition()#
El mètode partition() divideix la Collection en dues parts basant-se en una condició: la primera conté els elements que compleixen la condició i la segona els que no. Retorna una Collection amb exactament dues Collections:
$students = collect([
['name' => 'Joan', 'grade' => 7.5],
['name' => 'Maria', 'grade' => 4.2],
['name' => 'Pere', 'grade' => 8.1],
['name' => 'Anna', 'grade' => 3.8],
['name' => 'Lluís', 'grade' => 6.0],
]);
[$approved, $failed] = $students->partition(fn ($student) => $student['grade'] >= 5);
// $approved: Joan (7.5), Pere (8.1), Lluís (6.0)
// $failed: Maria (4.2), Anna (3.8)
echo "Aprovats: {$approved->count()}, Suspesos: {$failed->count()}";
// Aprovats: 3, Suspesos: 2Mètodes de cerca#
Aquests mètodes permeten comprovar si una Collection conté elements específics o si tots els elements compleixen una condició.
contains() i containsStrict()#
El mètode contains() comprova si la Collection conté un element determinat. Pot acceptar un valor simple, un parell clau-valor o un callback:
$fruits = collect(['poma', 'plàtan', 'taronja', 'kiwi']);
// Comprovar si conté un valor
$fruits->contains('poma'); // true
$fruits->contains('maduixa'); // false
// Amb callback
$fruits->contains(fn ($fruit) => strlen($fruit) > 5); // true ('plàtan', 'taronja')
// Amb clau-valor per a collections d'arrays
$users = collect([
['name' => 'Joan', 'role' => 'admin'],
['name' => 'Maria', 'role' => 'editor'],
]);
$users->contains('role', 'admin'); // true
$users->contains('role', 'viewer'); // false
// containsStrict() per a comparació de tipus
$numbers = collect([1, 2, 3]);
$numbers->contains('1'); // true (comparació laxa)
$numbers->containsStrict('1'); // false (string !== int)has()#
El mètode has() comprova si una clau donada existeix a la Collection. Funciona amb claus numèriques i associatives:
$config = collect([
'driver' => 'mysql',
'host' => 'localhost',
'port' => 3306,
]);
$config->has('driver'); // true
$config->has('password'); // false
$config->has(['driver', 'host']); // true (totes existeixen)
$config->has(['driver', 'ssl']); // false (ssl no existeix)search()#
El mètode search() cerca un valor a la Collection i retorna la seva clau. Si no el troba, retorna false:
$colors = collect(['vermell', 'verd', 'blau', 'groc']);
$key = $colors->search('blau');
// 2
$key = $colors->search('rosa');
// false
// Amb callback
$key = $colors->search(fn ($color) => strlen($color) === 4);
// 1 ('verd' té 4 caràcters)every()#
El mètode every() comprova si tots els elements de la Collection compleixen una condició. Retorna true només si el callback retorna true per a tots els elements:
$ages = collect([25, 30, 18, 45, 22]);
$allAdults = $ages->every(fn ($age) => $age >= 18);
// true
$allSeniors = $ages->every(fn ($age) => $age >= 30);
// false
// Amb collections buides, every() retorna true
collect([])->every(fn ($item) => false);
// true (vacuament cert)some()#
El mètode some() és un àlies de contains() quan s'utilitza amb un callback. Comprova si almenys un element compleix la condició:
$scores = collect([45, 62, 38, 91, 55]);
$hasExcellent = $scores->some(fn ($score) => $score >= 90);
// true (91 >= 90)
$allPassed = $scores->every(fn ($score) => $score >= 50);
// false (45 i 38 no passen)Mètodes de conversió#
Aquests mètodes converteixen una Collection a altres formats o en manipulen l'estructura de claus.
toArray(), toJson() i all()#
Aquests mètodes converteixen la Collection a formats estàndard de PHP o JSON:
$collection = collect([
['name' => 'Joan', 'age' => 28],
['name' => 'Maria', 'age' => 34],
]);
// toArray() - convertir a array PHP natiu (recursivament)
$array = $collection->toArray();
// toJson() - convertir a string JSON
$json = $collection->toJson();
// '[{"name":"Joan","age":28},{"name":"Maria","age":34}]'
// toJson() amb format llegible
$prettyJson = $collection->toJson(JSON_PRETTY_PRINT);
// all() - obtenir l'array subjacent (un sol nivell, no recursiu)
$raw = $collection->all();La diferència entre toArray() i all() és que toArray() converteix recursivament tots els objectes niuats a arrays (incloent models Eloquent i altres Collections), mentre que all() simplement retorna l'array de primer nivell tal com està.
values() i keys()#
El mètode values() re-indexa les claus de la Collection amb nombres consecutius començant per 0, i keys() retorna una Collection amb les claus de la Collection original:
$filtered = collect([0 => 'a', 1 => 'b', 5 => 'c', 8 => 'd']);
$reindexed = $filtered->values();
// [0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd']
$keys = $filtered->keys();
// [0, 1, 5, 8]
// keys() amb Collections associatives
$config = collect(['driver' => 'mysql', 'host' => 'localhost', 'port' => 3306]);
$configKeys = $config->keys();
// ['driver', 'host', 'port']implode() i join()#
El mètode implode() uneix els elements en un string amb un separador. Quan treballes amb Collections d'arrays o objectes, pots especificar quina clau unir. El mètode join() és similar però suporta un separador diferent per a l'últim element (estil "coma d'Oxford"):
$names = collect(['Joan', 'Maria', 'Pere', 'Anna']);
// implode() - unir amb separador
$list = $names->implode(', ');
// 'Joan, Maria, Pere, Anna'
// implode() amb clau per a collections d'objectes
$emails = $users->implode('email', '; ');
// 'joan@test.com; maria@test.com; pere@test.com'
// join() - amb separador final diferent
$sentence = $names->join(', ', ' i ');
// 'Joan, Maria, Pere i Anna'
// join() amb un sol element
collect(['Joan'])->join(', ', ' i ');
// 'Joan'
// join() amb dos elements
collect(['Joan', 'Maria'])->join(', ', ' i ');
// 'Joan i Maria'Operacions de conjunts#
Les operacions de conjunts permeten combinar, comparar i interseccionar Collections, de manera similar a les operacions matemàtiques de conjunts.
$setA = collect([1, 2, 3, 4, 5]);
$setB = collect([3, 4, 5, 6, 7]);
// intersect() - elements comuns
$common = $setA->intersect($setB);
// [3, 4, 5]
// diff() - elements de A que no són a B
$onlyInA = $setA->diff($setB);
// [1, 2]
// union() - combinar mantenint les claus de la primera
$merged = $setA->union($setB);
// [1, 2, 3, 4, 5, 6, 7] (les claus de A tenen prioritat)
// merge() - combinar (les claus de la segona sobreescriuen)
$merged = $setA->merge($setB);
// concat() - afegir elements al final (permet duplicats)
$all = $setA->concat($setB);
// [1, 2, 3, 4, 5, 3, 4, 5, 6, 7]La diferència entre merge() i union() és important amb arrays associatius. Amb merge(), els valors de l'array passat com argument sobreescriuen els valors de la Collection original quan les claus coincideixen. Amb union(), els valors de la Collection original tenen prioritat:
$defaults = collect(['color' => 'blau', 'size' => 'M', 'stock' => 10]);
$custom = collect(['color' => 'vermell', 'weight' => 0.5]);
$defaults->merge($custom)->all();
// ['color' => 'vermell', 'size' => 'M', 'stock' => 10, 'weight' => 0.5]
$defaults->union($custom)->all();
// ['color' => 'blau', 'size' => 'M', 'stock' => 10, 'weight' => 0.5]Higher-order messages#
Els higher-order messages són una sintaxi elegant que permet cridar mètodes sobre cada element d'una Collection sense haver d'escriure un callback explícit. En lloc de $users->each(fn ($user) => $user->notify(...)), pots escriure $users->each->notify(...). Pot semblar màgia, però és simplement sucre sintàctic que Laravel proporciona per fer el codi més net.
$users = User::where('active', true)->get();
// Sense higher-order messages
$users->each(function ($user) {
$user->notify(new WelcomeNotification);
});
// Amb higher-order messages (equivalent)
$users->each->notify(new WelcomeNotification);
// Filtrar amb higher-order messages
$activeUsers = $users->filter->isActive();
// Equivalent a: $users->filter(fn ($user) => $user->isActive())
// Map amb higher-order messages
$names = $users->map->getFullName();
// Equivalent a: $users->map(fn ($user) => $user->getFullName())
// Reject amb higher-order messages
$unverified = $users->reject->hasVerifiedEmail();
// SortBy amb higher-order messages
$sorted = $users->sortBy->getScore();
// Encadenar higher-order amb mètodes normals
$result = $users
->filter->isActive()
->sortBy->name
->values();Els higher-order messages funcionen amb els mètodes següents: average, avg, contains, each, every, filter, first, flatMap, groupBy, keyBy, map, max, min, partition, reject, skipUntil, skipWhile, some, sortBy, sortByDesc, sum, takeUntil, takeWhile i unique.
Els higher-order messages només funcionen quan vols cridar un mètode sense arguments o accedir a una propietat de cada element. Si necessites passar arguments al callback o fer operacions més complexes, has d'utilitzar la sintaxi de callback tradicional.
Encadenament complex#
Un dels avantatges principals de les Collections és la capacitat d'encadenar múltiples operacions en una sola expressió fluida. Vegem un exemple realista de processament de dades que combina diversos mètodes:
// Escenari: processar les comandes d'un mes i generar un informe per comercial
$orders = Order::with(['salesperson', 'items'])
->whereBetween('created_at', [
now()->startOfMonth(),
now()->endOfMonth(),
])
->get();
$report = $orders
// 1. Filtrar comandes completades
->filter(fn ($order) => $order->status === 'completed')
// 2. Agrupar per comercial
->groupBy('salesperson_id')
// 3. Transformar cada grup en un resum
->map(function ($orders, $salespersonId) {
$salesperson = $orders->first()->salesperson;
return [
'name' => $salesperson->name,
'total_orders' => $orders->count(),
'total_revenue' => $orders->sum(
fn ($order) => $order->items->sum('subtotal')
),
'average_order' => round(
$orders->avg(fn ($order) => $order->items->sum('subtotal')),
2
),
'top_product' => $orders
->flatMap->items
->groupBy('product_id')
->map->sum('quantity')
->sortDesc()
->keys()
->first(),
];
})
// 4. Ordenar per ingressos (descendent)
->sortByDesc('total_revenue')
// 5. Re-indexar les claus
->values();Aquest exemple demostra com les Collections permeten expressar processaments de dades complexos com una seqüència de transformacions clares. Cada pas de la cadena té un propòsit específic i el resultat de cada pas alimenta el següent, fent que el flux de dades sigui fàcil de seguir i mantenir.
Lazy Collections#
Les Collections regulars carreguen tots els elements a memòria simultàniament. Quan treballes amb desenes de milers o milions de registres, això pot esgotar la memòria del servidor. Les Lazy Collections resolen aquest problema utilitzant generadors PHP per processar un element a la vegada, mantenint un consum de memòria constant independentment de la mida del dataset.
Com funcionen#
Una Lazy Collection no carrega totes les dades a memòria de cop. En lloc d'això, utilitza un generador que produeix un element cada vegada que se li demana. Això significa que pots processar un fitxer de 10 GB línia per línia sense necessitar 10 GB de RAM:
use Illuminate\Support\LazyCollection;
// Crear una Lazy Collection amb un generador
$lines = LazyCollection::make(function () {
$handle = fopen('huge-log-file.log', 'r');
while (($line = fgets($handle)) !== false) {
yield trim($line);
}
fclose($handle);
});
// Processar milions de línies amb memòria constant
$errorCount = $lines
->filter(fn ($line) => str_contains($line, 'ERROR'))
->count();Lazy Collections amb Eloquent#
Eloquent proporciona el mètode cursor() que retorna una Lazy Collection. En lloc de carregar tots els models a memòria (com fa get()), cursor() utilitza un cursor de base de dades per obtenir un model a la vegada:
// get() carrega TOTS els usuaris a memòria
$users = User::where('active', true)->get(); // 100.000 models a memòria
// cursor() processa un a un amb memòria constant
User::where('active', true)->cursor()->each(function ($user) {
$user->update(['newsletter_sent' => true]);
});
// Encadenar mètodes de Collection amb cursor()
$highValueCustomers = User::cursor()
->filter(fn ($user) => $user->total_purchases > 1000)
->map(fn ($user) => [
'name' => $user->name,
'email' => $user->email,
'total' => $user->total_purchases,
])
->take(100);Comparació de memòria#
La diferència de consum de memòria entre Collections regulars i Lazy Collections pot ser enorme:
// Collection regular: carrega tot a memòria
// Amb 100.000 registres, pot consumir centenars de MB
$all = User::all(); // ~200MB per a 100.000 registres
// Lazy Collection: memòria constant (~2MB)
User::cursor()->each(function ($user) {
// Processa un usuari a la vegada
// La memòria es manté constant independentment del total
});
// Lazy Collection per llegir fitxers CSV grans
LazyCollection::make(function () {
$handle = fopen('exports/users.csv', 'r');
$headers = fgetcsv($handle);
while ($row = fgetcsv($handle)) {
yield array_combine($headers, $row);
}
fclose($handle);
})->chunk(1000)->each(function ($chunk) {
// Processar en blocs de 1000 sense excedir memòria
DB::table('imported_users')->insert($chunk->toArray());
});Les Lazy Collections tenen una limitació important: només es poden iterar una vegada. Si necessites accedir als elements múltiples vegades, hauràs de convertir la Lazy Collection a una Collection regular amb ->collect(), o crear una nova Lazy Collection. A més, mètodes com sort() o shuffle() no funcionen amb Lazy Collections perquè requereixen veure tots els elements simultàniament.
Mètodes disponibles#
La majoria de mètodes de Collection funcionen amb Lazy Collections. Els que requereixen veure tot el dataset (com sort(), reverse(), shuffle()) convertiran internament la Lazy Collection a una Collection regular, perdent l'avantatge de memòria. Els mètodes que processen element a element (filter(), map(), take(), skip(), each(), chunk()) mantenen el comportament lazy.
Col·leccions personalitzades amb macros#
Si necessites un mètode que no existeix a la classe Collection, pots registrar macros personalitzats. Un macro és un mètode que afegeixes dinàmicament a la classe Collection i que estarà disponible a qualsevol instància:
use Illuminate\Support\Collection;
// Registrar al AppServiceProvider::boot()
Collection::macro('toUpper', function () {
return $this->map(fn ($value) => strtoupper($value));
});
Collection::macro('median', function () {
$sorted = $this->sort()->values();
$count = $sorted->count();
if ($count === 0) {
return null;
}
$middle = intdiv($count, 2);
if ($count % 2 === 0) {
return ($sorted[$middle - 1] + $sorted[$middle]) / 2;
}
return $sorted[$middle];
});
// Usar els macros
$names = collect(['joan', 'maria'])->toUpper();
// ['JOAN', 'MARIA']
$mid = collect([1, 3, 5, 7, 9])->median();
// 5Els macros es registren globalment: un cop definits, estan disponibles a totes les instàncies de Collection durant la vida de la petició. El lloc habitual per registrar-los és el mètode boot() de l'AppServiceProvider o un service provider dedicat.
Eloquent Collections vs Support Collections#
Quan treballes amb Eloquent, els resultats de les consultes retornen instàncies de Illuminate\Database\Eloquent\Collection, que estén la Collection base (Illuminate\Support\Collection) amb mètodes específics per a models. Aquesta diferència és important perquè les Eloquent Collections ofereixen funcionalitats addicionals que no tenen sentit en Collections genèriques:
use App\Models\User;
$users = User::where('active', true)->get();
// $users és una Illuminate\Database\Eloquent\Collection
// Mètodes exclusius d'Eloquent Collection:
// find() - trobar un model per ID dins la Collection (sense consulta addicional)
$user = $users->find(42);
// load() - carregar relacions per a tots els models de la Collection
$users->load('articles', 'profile');
// modelKeys() - obtenir un array dels IDs dels models
$ids = $users->modelKeys();
// [1, 5, 12, 28, ...]
// toQuery() - convertir la Collection en un Query Builder
// Útil per fer operacions massives sobre els models de la Collection
$users->toQuery()->update(['newsletter' => true]);
// contains() sobrecarregat - accepta un model o un ID
$users->contains($someUser); // Compara per clau primària
$users->contains(42); // Busca per ID
// diff() sobrecarregat - compara per clau primària
$newUsers = User::where('created_at', '>=', now()->subWeek())->get();
$existingUsers = $users->diff($newUsers);
// makeVisible() / makeHidden() - gestionar atributs visibles
$users->makeVisible('password_changed_at');
$users->makeHidden(['email', 'phone']);
// append() - afegir accessors als models
$users->append('full_name');Quan apliques mètodes de transformació com map() o pluck() sobre una Eloquent Collection, el resultat pot ser una Collection base en lloc d'una Eloquent Collection, perquè els elements resultants poden no ser models. Per exemple, $users->pluck('email') retorna una Support\Collection de strings, no una Eloquent Collection.
Quan utilitzar Collections vs arrays simples#
Les Collections no són sempre la millor opció. Tenen un petit cost de memòria i rendiment comparat amb els arrays nadius de PHP, perquè cada Collection és un objecte que encapsula l'array i cada mètode crea una nova instància. En la majoria de casos, aquest cost és negligible, però val la pena saber quan és adequat:
Utilitza Collections quan:
- Necessites encadenar múltiples operacions de transformació, filtratge o agrupació
- Treballes amb resultats d'Eloquent (ja els tens com a Collection)
- La llegibilitat i mantenibilitat del codi són prioritàries
- Fas operacions funcionals sobre dades (map, filter, reduce)
- Necessites mètodes especialitzats com
groupBy(),keyBy(),partition()
Utilitza arrays simples quan:
- L'array és estàtic i no necessita transformacions (per exemple, una llista de configuració)
- El rendiment és crític i operes amb milions d'elements en bucles interns
- El processament és senzill i una sola funció
array_*és suficient - Treballes amb funcions que esperen arrays nadius (algunes funcions de PHP)
// Arrays simples: adequat per a dades estàtiques
$statusMap = [
'active' => 'Actiu',
'inactive' => 'Inactiu',
'banned' => 'Bloquejat',
];
// Collections: adequat per a transformacions complexes
$summary = collect($orders)
->filter(fn ($o) => $o->status === 'completed')
->groupBy(fn ($o) => $o->created_at->format('Y-m'))
->map(fn ($group, $month) => [
'month' => $month,
'count' => $group->count(),
'revenue' => $group->sum('total'),
])
->sortByDesc('revenue')
->values();Exemples pràctics#
Transformar una resposta d'API#
// Dades crues d'una API externa
$apiResponse = [
['id' => 1, 'first_name' => 'Joan', 'last_name' => 'Garcia', 'score' => 85, 'dept' => 'eng'],
['id' => 2, 'first_name' => 'Maria', 'last_name' => 'López', 'score' => 92, 'dept' => 'mkt'],
['id' => 3, 'first_name' => 'Pere', 'last_name' => 'Martí', 'score' => 78, 'dept' => 'eng'],
['id' => 4, 'first_name' => 'Anna', 'last_name' => 'Serra', 'score' => 95, 'dept' => 'mkt'],
['id' => 5, 'first_name' => 'Lluís', 'last_name' => 'Pons', 'score' => 88, 'dept' => 'eng'],
];
$result = collect($apiResponse)
->map(fn ($person) => [
'name' => "{$person['first_name']} {$person['last_name']}",
'score' => $person['score'],
'department' => match ($person['dept']) {
'eng' => 'Enginyeria',
'mkt' => 'Màrqueting',
default => 'Altres',
},
'performance' => $person['score'] >= 90 ? 'excel·lent' : ($person['score'] >= 80 ? 'bo' : 'adequat'),
])
->sortByDesc('score')
->groupBy('department')
->map(fn ($group, $dept) => [
'department' => $dept,
'members' => $group->values()->all(),
'average_score' => round($group->avg('score'), 1),
])
->values();Generar un informe de vendes#
$sales = Sale::with('product')
->whereBetween('created_at', [now()->startOfYear(), now()])
->get();
$report = $sales
->groupBy(fn ($sale) => $sale->created_at->format('F'))
->map(function ($monthlySales, $month) {
return [
'month' => $month,
'total_sales' => $monthlySales->count(),
'revenue' => $monthlySales->sum('amount'),
'unique_customers' => $monthlySales->unique('customer_id')->count(),
'top_products' => $monthlySales
->groupBy('product_id')
->map(fn ($productSales) => [
'name' => $productSales->first()->product->name,
'units' => $productSales->sum('quantity'),
'revenue' => $productSales->sum('amount'),
])
->sortByDesc('revenue')
->take(5)
->values(),
];
});Processament de dades CSV amb Lazy Collections#
use Illuminate\Support\LazyCollection;
$imported = LazyCollection::make(function () {
$handle = fopen(storage_path('imports/products.csv'), 'r');
$headers = array_map('trim', fgetcsv($handle));
while (($row = fgetcsv($handle)) !== false) {
yield array_combine($headers, array_map('trim', $row));
}
fclose($handle);
})
->filter(fn ($row) => !empty($row['sku']) && is_numeric($row['price']))
->map(fn ($row) => [
'sku' => $row['sku'],
'name' => $row['name'],
'price' => (float) $row['price'],
'stock' => (int) ($row['stock'] ?? 0),
'updated_at' => now(),
'created_at' => now(),
])
->chunk(500)
->each(function ($chunk) {
DB::table('products')->upsert(
$chunk->toArray(),
['sku'],
['name', 'price', 'stock', 'updated_at']
);
});Les Collections són una de les eines més potents de Laravel. Dominar-les transforma la manera com escrius codi PHP: en lloc de bucles imperius amb variables temporals i condicions niuades, descrius transformacions funcionals clares i encadenables que expressen la intenció del codi de manera molt més directa.