Un trait PHP è un blocco riutilizzabile di metodi e proprietà. Lo dichiari con trait Name { ... } e lo includi in qualsiasi classe con use Name; dentro il corpo della classe. PHP copia i membri del trait nella classe, quindi funzionano esattamente come se li avessi scritti lì.
User e Robot non hanno un padre comune, eppure ora entrambi hanno hello(). I trait esistono per questo caso: le classi PHP possono estendere una sola classe, e un trait condivide codice in orizzontale, tra classi che non sono correlate.
Trait con proprietà
Un trait può portare con sé proprietà insieme ai suoi metodi. I metodi usano $this come qualsiasi metodo della classe, perché una volta copiati sono metodi della classe:
Una classe non può dichiarare una proprietà con lo stesso nome di una proprietà del trait a meno che le due dichiarazioni non siano identiche (stessa visibilità, tipo e valore di default); altrimenti è un errore fatale.
Usare più trait
Elenca più trait in un'unica istruzione use, separati da virgole, oppure scrivi più righe use. La classe riceve i membri di tutti:
Risolvere i conflitti con insteadof e as
Quando due trait definiscono un metodo con lo stesso nome, PHP si rifiuta di indovinare e si ferma con un errore fatale. Dentro un blocco use, insteadof sceglie il vincitore, e as mantiene il metodo perdente con un altro nome:
Con un semplice use EnglishGreeting, FrenchGreeting; senza blocco, lo script si ferma prima di stampare qualsiasi cosa:
PHP Fatal error: Trait method FrenchGreeting::greet has not been applied as Host::greet, because of collision with EnglishGreeting::greet in /home/index.php on line 12
as può anche cambiare la visibilità. use Loggable { log as protected; } rende protected il log() del trait solo in questa classe, e use Loggable { log as private writeLog; } aggiunge un alias privato.
Membri astratti e statici in un trait
Un trait può richiedere qualcosa alla classe che lo usa dichiarando un metodo astratto. Può anche avere metodi e proprietà statici. Ogni classe che usa il trait riceve la propria copia di una proprietà statica:
Car e Bike contano separatamente, perché la proprietà statica viene copiata in ogni classe. È diverso da una proprietà statica dichiarata in una classe padre comune, condivisa da tutte le figlie; la pagina su static mostra quel caso.
Quale metodo vince: classe, trait o padre
Se lo stesso nome di metodo esiste nella classe stessa, in un trait che usa e nella sua classe padre, PHP sceglie in questo ordine: il metodo della classe, poi quello del trait, poi quello del padre. Dentro il trait, parent:: raggiunge ancora la versione del padre:
Un trait non è un tipo
instanceof non corrisponde mai a un trait, e un parametro tipizzato con il nome di un trait rifiuta ogni oggetto con un TypeError. Per chiedere "questa classe ha il comportamento", abbina il trait a un'interfaccia, oppure esamina la classe con class_uses():
Lo schema qui sopra è quello abituale: l'interfaccia Exportable è ciò che le funzioni usano come type hint e ciò che verifica instanceof, e il trait ExportsProperties è un'implementazione pronta che le classi possono usare o sostituire. Un trait da solo è codice condiviso senza contratto; quando ti accorgi di controllare class_uses() nella logica applicativa, il trait voleva un'interfaccia accanto. Per il codice condiviso da una famiglia di classi correlate, una classe astratta può essere la scelta più semplice.
Domande frequenti
Cos'è un trait in PHP?
Un trait è un gruppo con nome di metodi (e proprietà) dichiarato con trait Name { } che una classe include con use Name;. PHP copia i membri del trait nella classe, quindi si comportano come se li avessi scritti lì. È un modo per condividere codice tra classi che non hanno un padre comune.
Una classe PHP può usare più trait?
Sì: use Timestamps, SoftDeletes; oppure più righe use. Se due trait definiscono un metodo con lo stesso nome, devi sceglierne uno con insteadof, per esempio use A, B { A::hello insteadof B; }, e puoi mantenere l'altro con un nuovo nome con B::hello as helloB;.
Qual è la differenza tra un trait e un'interfaccia in PHP?
Un'interfaccia è un tipo senza codice: dice quali metodi deve avere una classe, e instanceof funziona con essa. Un trait è codice senza tipo: incolla corpi di metodi in una classe, ma $obj instanceof SomeTrait è sempre falso. Spesso si combinano: un'interfaccia per il tipo, un trait con un'implementazione di default.
Un trait può avere un costruttore o metodi astratti?
Sì a entrambe le cose. Un trait può dichiarare abstract public function name(): string; per richiedere che la classe che lo usa fornisca quel metodo, e può definire __construct, anche se un costruttore in un trait entra facilmente in conflitto con quello della classe e raramente è una buona idea.
Quale metodo vince se una classe e un trait definiscono lo stesso metodo?
Il metodo della classe stessa vince su quello del trait, e il metodo del trait vince su un metodo ereditato dalla classe padre. Quindi l'ordine è: classe, poi trait, poi padre.