Cosa sono le Collection
Illuminate\Support\Collection è un wrapper fluido per array di dati.
Invece di chiamare separatamente le funzioni array di PHP, con una catena di metodi trasformi i dati in modo intuitivo.
// Con funzioni array
$names = array_filter (
array_map ( fn ( $user ) => $user [ 'name' ], $users ),
fn ( $name ) => $name !== null
);
// Con Collection
$names = collect ( $users )
-> pluck ( 'name' )
-> filter ()
-> values ();
Le Collection sono immutabili . Ogni metodo restituisce una nuova istanza senza modificare l’originale.
Creazione delle Collection
Helper collect()
use Illuminate\Support\ Collection ;
$users = collect ([
[ 'name' => 'Mario Rossi' , 'age' => 28 , 'role' => 'admin' ],
[ 'name' => 'Giulia Bianchi' , 'age' => 34 , 'role' => 'editor' ],
[ 'name' => 'Luca Verdi' , 'age' => 22 , 'role' => 'viewer' ],
]);
Collection::make()
$collection = Collection :: make ([ 1 , 2 , 3 ]);
Collection::fromJson()
$collection = Collection :: fromJson ( '[{"name":"Mario"},{"name":"Giulia"}]' );
Metodi più usati
$users = collect ([
[ 'name' => 'Mario Rossi' , 'email' => '[email protected] ' ],
[ 'name' => 'Giulia Bianchi' , 'email' => '[email protected] ' ],
]);
$masked = $users -> map ( function ( array $user ) {
$parts = explode ( '@' , $user [ 'email' ]);
return [
'name' => $user [ 'name' ],
'email' => substr ( $parts [ 0 ], 0 , 2 ) . '***@' . $parts [ 1 ],
];
});
filter / reject
$products = collect ([
[ 'name' => 'Laptop' , 'price' => 1200 , 'in_stock' => true ],
[ 'name' => 'Mouse' , 'price' => 35 , 'in_stock' => false ],
[ 'name' => 'Tastiera' , 'price' => 80 , 'in_stock' => true ],
]);
// Solo disponibili
$inStock = $products -> filter ( fn ( $product ) => $product [ 'in_stock' ]);
// Escludi i non disponibili
$available = $products -> reject ( fn ( $product ) => ! $product [ 'in_stock' ]);
// filter() senza argomenti rimuove i valori falsy
$names = collect ([ 'Mario' , '' , null , 'Giulia' , false ]) -> filter () -> values ();
// ['Mario', 'Giulia']
Dopo filter() gli indici possono avere “buchi”. Chiama values() per riottenere una sequenza da 0.
first / last
$orders = collect ([
[ 'id' => 1 , 'status' => 'shipped' , 'amount' => 50 ],
[ 'id' => 2 , 'status' => 'pending' , 'amount' => 120 ],
[ 'id' => 3 , 'status' => 'pending' , 'amount' => 35 ],
]);
$nextOrder = $orders -> first ( fn ( $order ) => $order [ 'status' ] === 'pending' );
$order = $orders -> first ( fn ( $order ) => $order [ 'status' ] === 'cancelled' , null );
$latest = $orders -> last ();
pluck
$users = collect ([
[ 'id' => 1 , 'name' => 'Mario' , 'department' => 'Sviluppo' ],
[ 'id' => 2 , 'name' => 'Giulia' , 'department' => 'Vendite' ],
]);
$names = $users -> pluck ( 'name' ); // ['Mario', 'Giulia']
$nameById = $users -> pluck ( 'name' , 'id' ); // [1 => 'Mario', 2 => 'Giulia']
groupBy
$byDepartment = $users -> groupBy ( 'department' );
$byFirstChar = $users -> groupBy ( fn ( $user ) => mb_substr ( $user [ 'name' ], 0 , 1 ));
sortBy / sortByDesc
$cheapFirst = $products -> sortBy ( 'price' );
$expensiveFirst = $products -> sortByDesc ( 'price' );
$sorted = $products -> sortBy ([
[ 'price' , 'asc' ],
[ 'name' , 'asc' ],
]);
each — esegui su ogni elemento
$orders -> each ( function ( array $order ) {
\ Log :: info ( "Processing #{ $order ['id']}" , [ 'amount' => $order [ 'amount' ]]);
});
// Restituendo false esci dal ciclo
$orders -> each ( function ( array $order ) {
if ( $order [ 'amount' ] > 100 ) {
return false ;
}
});
flatMap
$users = collect ([
[ 'name' => 'Mario' , 'tags' => [ 'php' , 'laravel' ]],
[ 'name' => 'Giulia' , 'tags' => [ 'javascript' , 'vue' ]],
]);
$allTags = $users -> flatMap ( fn ( $user ) => $user [ 'tags' ]);
// ['php', 'laravel', 'javascript', 'vue']
reduce — aggrega
$total = $orders -> reduce (
fn ( $carry , $order ) => $carry + ( $order [ 'price' ] * $order [ 'quantity' ]),
0
);
Per somme semplici puoi usare sum().
reduceInto — aggrega su oggetto
Simile a reduce, ma la callback non deve restituire nulla. Utile per modificare direttamente un oggetto.
class OrderStats
{
public int $total = 0 ;
public int $count = 0 ;
}
$stats = $orders -> reduceInto ( new OrderStats , function ( OrderStats $stats , array $order ) {
$stats -> total += $order [ 'amount' ];
$stats -> count ++ ;
});
Per accumulare su array/scalari usa il passaggio per riferimento.
$even = $collection -> reduceInto ([], function ( array & $result , int $value ) {
if ( $value % 2 === 0 ) {
$result [] = $value ;
}
});
chunk — dividi in batch
$users = collect ( range ( 1 , 100 )) -> map ( fn ( $i ) => [ 'id' => $i , 'name' => "Utente { $i }" ]);
$chunks = $users -> chunk ( 10 );
$chunks -> each ( function ( \Illuminate\Support\ Collection $batch ) {
// Elabora il batch
});
Concatenazione dei metodi
$result = $orders
-> filter ( fn ( $order ) => $order [ 'status' ] === 'completed' )
-> filter ( fn ( $order ) => $order [ 'category' ] === 'elettronica' )
-> sortByDesc ( 'amount' )
-> map ( fn ( $order ) => [
'customer' => $order [ 'customer' ],
'amount' => number_format ( $order [ 'amount' ]) . ' EUR' ,
])
-> values ();
Integrazione con Eloquent
I risultati delle query Eloquent sono sempre istanze di Illuminate\Database\Eloquent\Collection, che estende Collection.
use App\Models\ User ;
use App\Models\ Order ;
$users = User :: where ( 'is_active' , true ) -> get (); // Collection
$adminEmails = User :: all ()
-> filter ( fn ( $user ) => $user -> role === 'admin' )
-> pluck ( 'email' );
$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' ),
]);
Fai filtri e ordinamenti dove possono essere fatti dal DB (con query builder), non con le Collection: altrimenti carichi in memoria dati inutili.
Metodi specifici di Eloquent\Collection
$users = User :: all ();
$user = $users -> find ( 1 );
$ids = $users -> modelKeys ();
$users -> load ( 'orders' , 'profile' );
$diff = $users -> diff ( $otherUsers );
$intersect = $users -> intersect ( $otherUsers );
Lazy Collection
Le Collection normali caricano tutti i dati in memoria. LazyCollection usa i generator PHP per processare un elemento alla volta. Utile con decine di migliaia di record.
use Illuminate\Support\ LazyCollection ;
// In memoria tutto
$users = User :: all ();
// Uno alla volta
User :: cursor () -> each ( function ( User $user ) {
// In memoria un utente per volta
});
Creare una LazyCollection
use Illuminate\Support\ LazyCollection ;
$lazy = LazyCollection :: make ( function () {
$handle = fopen ( 'large-file.csv' , 'r' );
while (( $line = fgetcsv ( $handle )) !== false ) {
yield $line ;
}
fclose ( $handle );
});
$lazy = User :: where ( 'is_active' , true ) -> cursor ();
Batch di grandi volumi
use App\Models\ Order ;
Order :: cursor ()
-> filter ( fn ( $order ) => $order -> amount > 100 )
-> each ( fn ( $order ) => $order -> sendConfirmationEmail ());
Order :: where ( 'status' , 'completed' )
-> cursor ()
-> filter ( fn ( $order ) => $order -> amount > 100 )
-> each ( fn ( $order ) => $order -> sendConfirmationEmail ());
cursor() recupera un record alla volta dal DB, risparmiando memoria; la connessione al DB resta aperta fino alla fine.
Paginazione con take e skip
$lazy = LazyCollection :: make ( function () {
foreach ( range ( 1 , 1000000 ) as $i ) {
yield $i ;
}
});
$page = $lazy -> skip ( 1000 ) -> take ( 100 ) -> values ();
Riepilogo
Metodo Descrizione collect($array)Crea una Collection map($callback)Trasforma ogni elemento filter($callback)Filtra reject($callback)Esclude first($callback)Primo che soddisfa la condizione pluck($key)Estrai una chiave groupBy($key)Raggruppa sortBy($key)Ordina crescente sortByDesc($key)Ordina decrescente each($callback)Esegui per ogni elemento flatMap($callback)Mappa e appiattisci reduce($callback, $initial)Aggrega in un valore chunk($size)Suddividi values()Rinumera indici sum($key)Somma count()Conta
Collection vs funzioni array
Le Collection migliorano molto la leggibilità. // Con funzioni array
$result = array_values ( array_filter (
array_map ( fn ( $u ) => $u [ 'name' ], $users ),
fn ( $name ) => strlen ( $name ) > 2
));
// Con Collection
$result = collect ( $users )
-> pluck ( 'name' )
-> filter ( fn ( $name ) => strlen ( $name ) > 2 )
-> values ()
-> all ();
Collection vs LazyCollection
Collection : centinaia/migliaia di record, semplice.
LazyCollection : decine di migliaia, memoria contenuta; combinala con cursor().
get() restituisce una Collection, cursor() una LazyCollection.