Hai mai passato ore a rincorrere un bug perché una proprietà di un oggetto è stata modificata da un metodo che non doveva toccarla? Succede a tutti. Soprattutto quando lavori con DTO, modelli di dominio o configurazioni che dovrebbero restare fisse dopo l'inizializzazione. PHP 8.1 ha introdotto le proprietà readonly, e PHP 8.2 ha fatto un passo avanti con le classi readonly. Non sono funzionalità di contorno: cambiano il modo in cui scrivi codice, riducono gli effetti collaterali e rendono i tuoi oggetti prevedibili. Noi, di Meteora Web, le usiamo quotidianamente nei progetti Laravel e nelle nostre piattaforme proprietarie. In questa guida ti mostriamo come usarle per davvero, non solo a parole.
Perché l'immutabilità conta anche in PHP
PHP è storicamente un linguaggio dinamico e permissivo. Puoi modificare una proprietà pubblica da qualsiasi parte del codice. Questo è comodo per scrivere veloce, ma è un disastro quando il progetto cresce. Un oggetto che rappresenta un ordine o un utente non dovrebbe cambiare dopo essere stato creato, a meno che non sia esplicitamente previsto.
L'immutabilità ti dà garanzie: una volta che un valore è stato impostato, nessun metodo, nessuna funzione, nessun framework può alterarlo per sbaglio. Il risultato? Meno bug, codice più facile da testare, comportamento deterministico. E non serve un pattern complesso: PHP ti dà gli strumenti a livello di linguaggio.
Sponsored Protocol
Readonly Properties: Come funzionano (e cosa non fanno)
Le proprietà readonly sono state introdotte in PHP 8.1. La sintassi è semplice: basta anteporre readonly alla dichiarazione. Il vincolo è che la proprietà può essere inizializzata solo una volta — tipicamente nel costruttore o direttamente nella dichiarazione (se è una costante di classe non statica). Dopo non può più essere modificata.
class UserDTO {
public readonly string $name;
public readonly string $email;
public function __construct(string $name, string $email) {
$this->name = $name;
$this->email = $email;
}
}
$user = new UserDTO('Mario Rossi', 'mario@example.com');
echo $user->name; // Mario Rossi
$user->name = 'Luca'; // Errore: Cannot modify readonly propertyCose da sapere:
- Le proprietà readonly devono avere un tipo dichiarato (type hint). Non puoi scrivere
public readonly $name;senza tipo. - Non possono essere
static. Le readonly sono per istanze. - Il valore può essere un oggetto mutabile. La proprietà readonly impedisce di cambiare il riferimento, ma non impedisce di modificare l'oggetto stesso se è mutabile. Esempio:
public readonly array $items;— puoi fare$user->items[] = 'new';? No, perché[] =modifica l'array, ma l'array è un tipo valore in PHP? In realtà,$user->items[] = 'new'tenta di modificare la proprietà readonly — errore. Ma se l'array è un oggetto comeCollection, puoi chiamare metodi che lo modificano. Quindi attenzione: readonly protegge il riferimento, non il contenuto.
Errore comune: provare a inizializzare una proprietà readonly in un metodo diverso dal costruttore o al di fuori della dichiarazione. Non funziona. L'unico momento in cui puoi assegnare è nella dichiarazione stessa (con valore costante) o nel costruttore.
Sponsored Protocol
Readonly properties in classi con costruttori promossi
La combinazione più usata: dichiarare le proprietà readonly direttamente nel costruttore. PHP 8 permette di unire dichiarazione e assegnazione. È pulito e compatto.
class ProductDTO {
public function __construct(
public readonly string $sku,
public readonly float $price
) {}
}Con una riga hai tutto: costruttore, proprietà, tipo, immutabilità. Meno boilerplate, più leggibilità.
Classi Readonly in PHP 8.2: immutabilità di livello superiore
PHP 8.2 ha introdotto le classi readonly. Aggiungi readonly prima di class e automaticamente tutte le proprietà diventano readonly. In più, non puoi aggiungere proprietà non readonly. Il costruttore promosso (o esplicito) con readonly è implicito.
readonly class OrderDTO {
public function __construct(
public string $orderId,
public float $total
) {}
}
// Equivale a scrivere ogni proprietà come readonly
// Non puoi dichiarare proprietà normali in questa classeVantaggi pratici:
Sponsored Protocol
- Zero ripetizioni: non devi scrivere
readonlysu ogni proprietà. - Intenzione chiara: chi legge capisce subito che la classe è immutabile.
- Il compilatore (opcache) può ottimizzare meglio, ma il guadagno è marginale nella pratica.
Attenzione: una classe readonly non può estendere una classe non readonly, e viceversa. L'ereditarietà è permessa solo tra classi readonly. Inoltre, le proprietà di una classe readonly non possono essere static.
Immutabilità pratica: esempi reali dal nostro lavoro
Noi usiamo le classi readonly per modellare DTO, comandi CQRS, eventi di dominio e risposte API. Un caso concreto: in una piattaforma di gestione ordini che abbiamo sviluppato per un cliente, ogni ordine viene rappresentato con una classe OrderSnapshot che viene passata tra i servizi. Grazie a readonly, siamo certi che nessun servizio modifichi i dati dell'ordine dopo la creazione. Se un servizio deve aggiornare lo stato, restituisce una nuova istanza. Questo rende il flusso prevedibile.
readonly class OrderSnapshot {
public function __construct(
public int $id,
public string $status,
public float $total,
public array $items
) {}
// Factory method per creare una nuova istanza con stato cambiato
public function withStatus(string $newStatus): self {
return new self(
id: $this->id,
status: $newStatus,
total: $this->total,
items: $this->items
);
}
}In questo modo, lo stato originale non viene mai perso, e puoi tracciare le modifiche confrontando le istanze.
Sponsored Protocol
Serializzazione e readonly: cosa cambia
Le proprietà readonly possono essere serializzate con serialize() e json_encode() normalmente. Tuttavia, quando deserializzi con unserialize() o ReflectionProperty::setValue(), PHP non rispetta il vincolo readonly: può assegnare il valore anche a una proprietà readonly. Questo è un comportamento noto e voluto per consentire la deserializzazione. Se vuoi una protezione rigorosa anche in fase di deserializzazione, devi gestirla manualmente (ad esempio, rendendo le proprietà private e fornendo un factory method).
Reflection e readonly: limiti da conoscere
Con ReflectionProperty puoi modificare una proprietà readonly chiamando setValue(). Funziona, ma è considerato un uso avanzato per librerie di ORM o serializzatori. Se stai scrivendo codice normale, non dovresti mai fare affidamento su questo. Le readonly sono un contratto per lo sviluppatore, non una barriera di sicurezza.
Quando usare readonly e quando no
Usa readonly:
- DTO (Data Transfer Object) che trasportano dati tra layer
- Eventi di dominio
- Comandi CQRS
- Configurazioni immutabili (ad esempio, parametri di connessione, impostazioni di sistema)
- Oggetti valore (Value Object)
Non usare readonly:
Sponsored Protocol
- Entità Doctrine o Eloquent che devono essere modificate dall'ORM (a meno che non usi event sourcing o snapshot)
- Oggetti che per natura cambiano frequentemente (es. una sessione utente, uno stato transitorio)
- Classi con dipendenze di servizi iniettate via costruttore (non è sbagliato, ma spesso non serve se il servizio è immutabile)
In sintesi — cosa fare adesso
- Identifica i DTO nei tuoi progetti. Ogni classe che serve solo per trasportare dati tra controller e servizio, o tra servizi, è candidata perfetta per
readonly. - Riscrivi le prime tre classi DTO con
readonly classo con proprietà readonly. Usa i costruttori promossi per ridurre la verbosità. - Aggiungi un'eccezione di tipo se una proprietà readonly viene modificata — in realtà PHP lo fa per te, ma puoi documentare perché.
- Confronta il codice prima e dopo. Noterai che il costruttore diventa l'unico punto di scrittura, e il resto è sola lettura. Questo riduce il carico mentale.
- Aggiorna le tue code review: quando vedi una proprietà pubblica mutabile in una classe che sembra un DTO, chiedi se può essere readonly.
L'immutabilità non è una moda: è una disciplina che paga nel lungo periodo. PHP 8 te la mette a disposizione con una sintassi pulita. Noi, di Meteora Web, abbiamo iniziato a usarla su tutti i nuovi progetti e non torniamo indietro. Prova anche tu.