Property hooks in PHP 8.4: cosa cambia nel codice di tutti i giorni

Per vent’anni in PHP una proprietà pubblica è stata una scommessa. La dichiari public, il codice
la usa come $user->email, e il giorno in cui ti serve normalizzare il valore o validarlo devi
trasformarla in getEmail() e setEmail(). Da quel momento ogni riga che la leggeva è rotta.
Per non trovarsi in quella situazione si scrivono getter e setter fin dall’inizio, anche quando
non fanno niente. Righe di codice che esistono solo per tenere aperta una porta.
PHP 8.4 chiude la questione con i property hooks: la proprietà resta pubblica e conserva la
stessa sintassi di accesso, ma può eseguire codice in lettura e in scrittura.
Come si scrivono
class Utente
{
public string $email {
set => strtolower(trim($value));
}
}
$u = new Utente();
$u->email = ' Mario@Example.COM ';
echo $u->email; // mario@example.com
Il valore assegnato arriva nell’hook come $value, tipizzato come la proprietà. Nella forma
breve, set => ... significa “quello che ritorno è quello che finisce in memoria”. Se serve fare
di più c’è la forma lunga, con le graffe:
public string $email {
set {
$v = strtolower(trim($value));
if (!filter_var($v, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("email non valida: $v");
}
$this->email = $v;
}
}
Dentro l’hook, $this->email è lo spazio di memoria della proprietà, non una chiamata ricorsiva:
è l’unico punto del codice in cui l’hook viene scavalcato. Da qualunque altro metodo della classe,
$this->email passa dall’hook come farebbe il codice esterno.
Le proprietà virtuali
Il caso più interessante è l’altro: un get che non tocca mai la memoria. In quel caso PHP non
alloca niente, e la proprietà è virtuale — un metodo che si usa come un campo.
class Fattura
{
public function __construct(
public float $imponibile,
public float $aliquota,
) {}
public float $totale {
get => round($this->imponibile * (1 + $this->aliquota), 2);
}
}
$f = new Fattura(100.0, 0.21);
echo $f->totale; // 121
Niente getTotale(), niente valore duplicato da tenere aggiornato quando l’imponibile cambia.
E siccome non c’è memoria dietro, non c’è nemmeno il rischio classico del campo calcolato: che
qualcuno lo scriva a mano e da lì in poi menta.
Gli hook funzionano anche sulle proprietà promosse nel costruttore, e le interfacce possono
finalmente richiedere una proprietà invece di un metodo:
interface HaIdentificativo
{
public string $id { get; }
}
Chi implementa l’interfaccia può soddisfarla con una proprietà normale, con un hook o con una
proprietà virtuale: dal punto di vista di chi la usa non cambia niente.
Quando conviene davvero
Tre casi, concreti:
- Normalizzare in ingresso. Email in minuscolo, partite IVA senza spazi, stringhe con
trim.
Regole che devono valere sempre, non solo quando chi scrive si ricorda di applicarle. - Valori derivati. Totali, nomi completi, stati calcolati da due campi. Meglio un
getche
un campo che va tenuto sincronizzato. - Aggiungere comportamento senza rompere il contratto. Una proprietà pubblica già usata in
giro può diventare hooked senza toccare una sola riga di chi la legge. È il motivo per cui, da
8.4 in poi, scrivere getter e setter “preventivi” non ha più senso.
Dove complicano
Non è una feature da spargere ovunque. Le cose da sapere prima:
- Un
getviene eseguito a ogni lettura. Nessuna cache, nessuna memoizzazione. Una query o
una scansione dentro un hook diventa un problema il giorno in cui quella proprietà finisce
dentro un ciclo. Se il calcolo costa, un metodo esplicito è più onesto: dice a chi legge che
sta succedendo qualcosa. - Niente hook su proprietà
staticné su proprietàreadonly. Sono errori di compilazione,
non casi limite da scoprire in produzione. - Gli array si comportano male. Su una proprietà con hook non puoi fare
$obj->lista[] = 'x':
ilgetrestituisce una copia, non un riferimento. Serve un hook&get, oppure — meglio — un
metodoaggiungi()che dica chiaramente cosa fa. - Effetti collaterali nascosti. Un
setche scrive su un log, invia una mail o tocca il
database trasforma un’assegnazione dall’aria innocua in un’operazione seria. Chi legge
$ordine->stato = 'spedito';non si aspetta che parta un’email.
La regola pratica
Gli hook servono a fare una cosa piccola e prevedibile intorno a un valore: normalizzarlo,
validarlo, calcolarlo da altri campi della stessa classe. Tutto ciò che ha un costo, un effetto
esterno o una logica che merita un nome, resta un metodo.
Detto altrimenti: se dopo l’hook la riga $a->b = $c è ancora leggibile come “assegno un valore”,
va bene. Se è diventata “avvia un processo”, il posto giusto non era la proprietà.
