Le funzioni PHP che sostituiscono un foreach

Nel codice PHP che mi capita di leggere, il foreach è il martello che va bene per tutto. Estrarre
una colonna: foreach. Costruire una mappa id → nome: foreach. Controllare se un array ha ancora
gli indici in ordine: foreach con un contatore.
Funziona. Ma un ciclo racconta come si arriva al risultato, e chi legge deve ricostruire da solo
cosa stai ottenendo. Tre funzioni di libreria standard fanno lo stesso lavoro dichiarandolo.
array_column: prendere una colonna, o costruire una mappa
Il caso classico: hai un risultato di query, ti servono solo gli id.
// Prima
$ids = [];
foreach ($righe as $riga) {
$ids[] = $riga['id'];
}
// Dopo
$ids = array_column($righe, 'id');
Il secondo parametro è la colonna da estrarre. Ma c’è un terzo parametro che si usa poco e che
risolve il caso più frequente di tutti — la mappa chiave → valore:
// id => nome, in una riga
$nomi = array_column($righe, 'nome', 'id');
// [12 => 'Rossi', 7 => 'Bianchi', ...]
E se passi null come colonna, tieni la riga intera e la indicizzi:
$perId = array_column($righe, null, 'id');
// [12 => ['id' => 12, 'nome' => 'Rossi', ...], ...]
Quest’ultima è la forma che uso più spesso: trasforma una lista in una lookup table prima di un
ciclo che altrimenti farebbe una ricerca lineare a ogni giro.
Due cose da sapere. array_column funziona anche su array di oggetti, leggendo le proprietà
pubbliche (e passando da __get/__isset se ci sono). E se una riga non ha quella chiave, viene
semplicemente saltata: non hai un errore, hai un array più corto. Se la cosa ti importa, confronta
i count().
array_combine: due liste, un array associativo
Quando le chiavi e i valori arrivano da due fonti separate — tipico con l’output di fgetcsv, dove
la prima riga sono le intestazioni:
$intestazioni = fgetcsv($fh); // ['codice', 'descrizione', 'prezzo']
while (($riga = fgetcsv($fh)) !== false) {
$record = array_combine($intestazioni, $riga);
// ['codice' => 'A100', 'descrizione' => '...', 'prezzo' => '12.50']
}
Senza, servirebbe un ciclo con l’indice numerico e la lettura incrociata dei due array — codice che
funziona ma che va riletto due volte per capire cosa produce.
L’attenzione qui è una sola: i due array devono avere lo stesso numero di elementi. Da PHP 8.0 se
non ce l’hanno viene lanciata una ValueError, non un warning con false di ritorno come in
passato. Su file CSV veri, con righe malformate, è un caso che capita: o lo intercetti prima
(count($intestazioni) === count($riga)) o metti un try.
array_is_list: la domanda giusta invece di quella comoda
Arrivata con PHP 8.1. Risponde a una domanda precisa: le chiavi di questo array sono 0, 1, 2 …
senza buchi e nell’ordine?
array_is_list([1, 2, 3]); // true
array_is_list([1 => 'a', 0 => 'b']); // false, l'ordine conta
array_is_list([]); // true
Serve nel punto in cui un array deve diventare JSON e ti importa se esce [...] o {...}. È il
bug classico delle API: filtri una lista con array_filter, che conserva le chiavi, e quello
che era un array JSON diventa un oggetto con chiavi numeriche appena salta un elemento in mezzo.
$attivi = array_filter($utenti, fn($u) => $u['attivo']);
// se l'utente in posizione 1 non è attivo, le chiavi sono [0, 2, 3]
// json_encode($attivi) => {"0":{...},"2":{...}} ← non è più una lista
if (!array_is_list($attivi)) {
$attivi = array_values($attivi);
}
In pratica, dopo un array_filter destinato a json_encode, array_values va messo sempre. E
array_is_list è il modo per scriverlo in un test e dimostrare che il problema esisteva.
Cosa non fare
Non sostituire un ciclo solo per far sparire il ciclo. Un foreach che dentro fa tre cose diverse
non migliora spezzato in tre passate di array_map e array_filter: ci sono tre iterazioni al
posto di una e la logica è più sparpagliata, non più chiara.
Il criterio è un altro: se il corpo del ciclo fa una cosa sola e quella cosa ha un nome
(«estrai una colonna», «indicizza per id», «accoppia chiavi e valori»), allora esiste probabilmente
una funzione che si chiama così, e usarla toglie una cosa da leggere. Se il ciclo fa un lavoro suo,
lascialo dov’è.
