+39 3662317539/+34 620899163
info@simonecosci.com

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

Sviluppo Siti Web a Tenerife

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 get che
    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 get viene 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à static né 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':
    il get restituisce una copia, non un riferimento. Serve un hook &get, oppure — meglio — un
    metodo aggiungi() che dica chiaramente cosa fa.
  • Effetti collaterali nascosti. Un set che 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à.