Programmazione Orientata agli Oggetti in PHP: Capire i Trait
Impara i trait PHP: dichiarali e usali, combinane più di uno, risolvi i conflitti di metodi con insteadof e as, e usa membri astratti e statici.
Un trait è un blocco riutilizzabile di metodi (e proprietà) che puoi mescolare in qualsiasi classe. I trait risolvono un problema specifico in PHP: una classe può estendere solo un genitore, quindi quando due classi non correlate devono condividere lo stesso comportamento, l'ereditarietà singola non è sufficiente. I trait ti permettono di condividere quel comportamento orizzontalmente — tra classi che non hanno una relazione genitore-figlio.
Questo capitolo tratta cosa sono i trait, come dichiararli e usarli, come combinarne diversi, come PHP risolve i conflitti di nomi di metodi, e le insidie (metodi astratti, membri statici, proprietà) che causano problemi.
Cos'è un trait?
Un trait assomiglia quasi esattamente a una classe, ma viene dichiarato con la parola chiave trait invece di class. Le differenze principali:
- Un trait non può essere istanziato da solo — non esiste
new MyTrait(). - Un trait viene usato all'interno di una classe con la parola chiave
use. I suoi metodi vengono quindi copiati nella classe come se li avessi scritti lì. - Una classe può usare un numero qualsiasi di trait, e un trait può usare altri trait.
Pensa a un trait come a un copia-incolla assistito dal compilatore: il codice del trait viene letteralmente appiattito in ogni classe che lo utilizza.
Dichiarare e usare un trait
Dichiara un trait con trait, poi importalo in una classe con use:
<?php
trait Greetable
{
public function greet(): string
{
return "Hello, my name is {$this->name}.";
}
}
class User
{
public function __construct(public string $name) {}
use Greetable;
}
$user = new User("Ada");
echo $user->greet();
// Output: Hello, my name is Ada.Nota che il trait fa riferimento a $this->name anche se non lo definisce. Un trait viene eseguito nel contesto della classe che lo usa, quindi può fare affidamento su proprietà e metodi forniti da quella classe.
Usare più trait
Una classe può usare più trait contemporaneamente. Separali con virgole, oppure elenca ciascuno con la propria istruzione use:
<?php
trait Loggable
{
public function log(string $message): string
{
return "[LOG] " . $message;
}
}
trait Jsonable
{
public function toJson(): string
{
return json_encode(get_object_vars($this));
}
}
class Order
{
use Loggable, Jsonable;
public function __construct(public int $id, public float $total) {}
}
$order = new Order(7, 49.99);
echo $order->log("created") . PHP_EOL;
echo $order->toJson();
// Output:
// [LOG] created
// {"id":7,"total":49.99}Risolvere i conflitti con insteadof e as
Se due trait definiscono un metodo con lo stesso nome, PHP genera un errore fatale a meno che tu non indichi quale tenere. Usa insteadof per scegliere il vincitore e as per mantenere il perdente sotto un alias:
<?php
trait FileStorage
{
public function save(): string
{
return "Saved to a file.";
}
}
trait DatabaseStorage
{
public function save(): string
{
return "Saved to the database.";
}
}
class Report
{
use FileStorage, DatabaseStorage {
DatabaseStorage::save insteadof FileStorage;
FileStorage::save as saveToFile;
}
}
$report = new Report();
echo $report->save() . PHP_EOL; // DatabaseStorage wins
echo $report->saveToFile(); // still reachable via the alias
// Output:
// Saved to the database.
// Saved to a file.La parola chiave as può anche modificare la visibilità di un metodo, ad esempio protected reset as private — utile quando vuoi che un metodo del trait sia disponibile internamente ma non come parte dell'API pubblica.
Membri astratti e statici
I trait possono fare più che contenere metodi concreti di istanza.
- I metodi astratti permettono a un trait di richiedere alla classe che lo usa di implementare qualcosa. È così che un trait dichiara una dipendenza dal suo host.
- I metodi e le proprietà statici si comportano come normali membri statici, ma ogni classe che usa il trait ottiene la propria copia di qualsiasi proprietà statica.
<?php
trait Counter
{
private static int $count = 0;
public static function increment(): int
{
return ++self::$count;
}
// The using class MUST provide this:
abstract public function label(): string;
}
class PageView
{
use Counter;
public function label(): string
{
return "views";
}
}
echo PageView::increment() . PHP_EOL; // 1
echo PageView::increment() . PHP_EOL; // 2
echo (new PageView())->label(); // views
// Output:
// 1
// 2
// viewsTrait vs. ereditarietà e interfacce
Scegli lo strumento giusto:
- Ereditarietà (
extends) modella una relazione "è-un" e ti dà un solo genitore. Usala quando le classi condividono genuinamente una gerarchia di tipi. Vedi PHP Inheritance. - Le interfacce definiscono un contratto — quali metodi esistono — ma non contengono implementazione. Vedi PHP Interfaces.
- I trait forniscono implementazione che puoi mescolare in classi non correlate. Un pattern comune è un'interfaccia + un trait: l'interfaccia pubblicizza la capacità, il trait fornisce il codice predefinito.
Se sei nuovo a questi concetti, inizia con PHP Classes and Objects e PHP Abstract Classes.
Insidie comuni
- Le collisioni di nomi sono silenziose finché non lo sono. Combinare trait che definiscono lo stesso metodo è un errore fatale; risolvi sempre con
insteadof/as. - I trait vengono appiattiti, non ereditati. Un metodo definito direttamente nella classe sovrascrive la versione del trait, e la versione del trait sovrascrive qualsiasi cosa ereditata da una classe genitore.
- Le proprietà statiche sono per classe. Due classi che usano lo stesso trait non condividono il suo stato statico — ciascuna ottiene una copia separata.
- Non abusarne. Un trait che necessita di molte proprietà dal suo host è spesso un segnale che la composizione (un oggetto reale) o l'ereditarietà si adattano meglio.
Conclusione
I trait offrono a PHP un modo pulito per condividere implementazioni di metodi tra classi non correlate, aggirando la limitazione dell'ereditarietà singola. Dichiarali con trait, importali con use, risolvi i conflitti con insteadof e as, e ricorda che il codice del trait viene appiattito in ogni classe che lo usa. Usati con giudizio, i trait mantengono il comportamento trasversale — logging, serializzazione, contatori — in un unico posto invece di copie sparse.