Interfacce e Trait in PHP: quando usare quale
interface implements, Trait per codice riutilizzabile, conflitti tra trait
Nella pillola sull'ereditarietà abbiamo visto che una classe PHP può estendere con extends solo una classe alla volta. Interfacce e trait risolvono due limiti diversi di questa regola: le prime permettono di rispettare più contratti contemporaneamente; i secondi permettono di condividere codice concreto tra classi che non hanno nulla in comune nella loro gerarchia.
Un'interfaccia è come un certificato di conformità: dice «questo prodotto rispetta lo standard X», senza dire come sia stato costruito. Un trait è più simile a un componente prefabbricato: lo stesso pezzo, letteralmente identico, viene montato in prodotti anche molto diversi tra loro.
1. interface: il contratto, senza implementazione
Un'interfaccia dichiara solo le firme dei metodi, mai il loro corpo. Il manuale è netto: tutti i metodi di un'interfaccia sono astratti per natura, e tutti pubblici. Una classe può implementare più interfacce insieme, separate da virgola: qui non c'è il limite dell'ereditarietà singola.
interface Loggabile {
public function log(string $messaggio): void;
}
interface Serializzabile {
public function toArray(): array;
}
class Ordine implements Loggabile, Serializzabile {
public function log(string $messaggio): void { /* ... */ }
public function toArray(): array { /* ... */ }
}
Regola sintattica dal manuale: se una classe estende e implementa allo stesso tempo, extends deve comparire prima di implements.
class Base { /* ... */ }
class Derivata extends Base implements Loggabile {
// extends prima, implements dopo: ordine obbligatorio
}
2. Trait: codice condiviso, non un vero contratto
Un trait contiene implementazione vera, metodi con corpo, non solo firme, pensata per essere mescolata dentro una o più classi. Il manuale usa proprio questa parola: un trait non è ereditato, ma incluso o «mescolato» (mixed in), diventando di fatto parte della classe stessa.
trait Timestampabile {
protected ?\DateTimeImmutable $creatoIl = null;
public function segnaCreazione(): void {
$this->creatoIl = new \DateTimeImmutable();
}
}
class Articolo {
use Timestampabile;
}
class Commento {
use Timestampabile; // stesso codice, classi senza relazione tra loro
}
3. Conflitti tra trait: insteadof e as
Se due trait usati nella stessa classe hanno un metodo con lo stesso nome, PHP non sceglie da solo: genera un errore fatale finché non risolvi tu il conflitto esplicitamente, con insteadof (scegli quale versione tenere) e, se serve, as (dai un alias anche all'altra).
trait Cane { public function saluta() { echo 'Bau'; } }
trait Gatto { public function saluta() { echo 'Miao'; } }
class AnimaleBuffo {
use Cane, Gatto {
Cane::saluta insteadof Gatto; // scegli la versione di Cane
Gatto::saluta as miagola; // ma tieni anche l'altra, con un altro nome
}
}
$a = new AnimaleBuffo();
$a->saluta(); // 'Bau'
$a->miagola(); // 'Miao'
4. Quando usare quale
Usa un'interface quando ti interessa il contratto: «qualunque cosa sia, deve saper fare X», utile per scrivere codice che lavora con oggetti intercambiabili, senza sapere la loro classe esatta.
Usa un trait quando ti interessa il codice: «questa logica concreta serve identica in classi che non hanno nessuna relazione gerarchica tra loro».
Sono spesso usati insieme: un'interfaccia dichiara il contratto, un trait fornisce un'implementazione di default riusabile, e la classe dichiara implements per il contratto e use per prendersi l'implementazione già pronta.

In sintesi
La domanda giusta non è «interfaccia o trait?» come se fossero alternative, ma «sto descrivendo cosa un oggetto deve fare, o sto riusando codice che già sa farlo?». La risposta a quella domanda sceglie lo strumento al posto tuo.
Commenti (0)
Registrati per commentare, fare domande e proporre l'argomento della prossima pillola (oppure accedi se hai già un account).
Nessun commento. Sii il primo a scrivere qualcosa.