W3docs

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
// views

Trait 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.

Pratica

Pratica
Quali sono le caratteristiche dei trait PHP?
Quali sono le caratteristiche dei trait PHP?
Was this page helpful?