• seguici su feed rss
  • seguici su twitter
  • seguici su linkedin
  • seguici su facebook
  • cerca

SEI GIA' REGISTRATO? EFFETTUA ADESSO IL LOGIN.



ricordami per 365 giorni

HAI DIMENTICATO LA PASSWORD? CLICCA QUI

NON SEI ANCORA REGISTRATO ? CLICCA QUI E REGISTRATI !

Cosa sono i trait in PHP. La guida completa con esempi.

di :: 27 ottobre 2020
Cosa sono i trait in PHP. La guida completa con esempi.

I trait, introdotti in PHP dalla versione 5.4, sono degli snippet di codice che vengono inclusi all'interno di una classe per aggiungerne delle funzionalità in maniera analoga a quanto avviene, all'interno di un normale script, quando effettuiamo un include (o un require) di un file php.

L'utilizzo di un trait nelle classi riduce la duplicazione di codice, ed essendo esterno alla classe, per modificarlo ci basta agire solo su di esso.

Come afferma la documentazione ufficiale PHP, se due classi contengono lo stesso codice (se lo copi e incolli da una classe ad un'altra), quel codice è candidato a diventare un trait.

Ad esempio, abbiamo queste due classi Pippo e Pluto che usano lo stesso metodo "hello"

<?php

class Pippo {
 public funtion hello(){
  echo "ciao";
 }
}

class Pluto {
 public funtion hello(){
  echo "ciao";
 }
}

$pippo=new Pippo();
$pippo->hello();

$pluto=new Pluto();
$pluto->hello();

?>

Evitiamo di duplicare il codice dei due metodi: copiamo il metodo da una delle due classi e creiamo un trait come segue

<?php

trait MyTrait {
 public funtion hello(){
  echo "ciao";
 }
}

?>

Bene, adesso possiamo includere il trait nelle due classi X e Y ottenendo un codice più leggero.

<?php

trait MyTrait {
 public funtion hello(){
  echo "ciao";
 }
}

class Pippo {
  use MyTrait;
}

class Pluto {
  use MyTrait;
}

$pippo=new Pippo();
$pippo->hello();

$pluto=new Pluto();
$pluto->hello();

?>

Per comodità ho inserito il trait nello stesso file ma di regola sarebbe bene inserirlo in un file separato.

Questa è stata la premessa, che e sicuramente avrete perfettamente compreso.

Se avete già studiato l'ereditarietà delle classi potreste dire che lo stesso risultato dell'esempio precedente lo avreste potuto ottenere creando una classe parent contenente il metodo "hello" e due classi child che estendono la classe parent.

<?php

class MyTrait {
 public funtion hello(){
  echo "ciao";
 }
}

class Pippo extends MyTrait {
}

class Pluto extends MyTrait {
}

$pippo=new Pippo();
$pippo->hello();

$pluto=new Pluto();
$pluto->hello();

?>

Il metodo è presente solo una volta nella classe parent, per cui anche così il codice è ottimizzato, ma i trait hanno anche uno scopo differente e lo vediamo in questo esempio.

Ipotizziamo di avere una terza classe "Paperino" con dentro il metodo "bye", e vogliamo che questo metodo sia disponibile anche per le altre due classi "Pippo" e "Pluto" per cui lo inseriamo nella classe parent "MyTrait":

<?php

class MyTrait {
   public funtion hello(){
      echo "ciao";
   }

   public funtion bye(){
      echo "bye bye";
   }
}

class Pippo extends MyTrait {
}

class Pluto extends MyTrait {
}

class Paperino extends MyTrait {
   public funtion bye(){
      echo "bye bye";
   }
}

$pippo=new Pippo();
$pippo->hello();

$pluto=new Pluto();
$pluto->hello();

?>

Pippo e Pluto estendono la classe MyTrait e così accedono al metodo "hello" e "bye". Anche Paperino però accede ad "hello", ma Paperino non ne ha bisogno!

Abbiamo quindi un problema di duplicazione del codice, e senza usare i traits questo problema non può essere risolto: lo risolviamo creando due traits diversi (che metteremo in file diversi ma per comodità li mettiamo nello stesso file) e che verranno richiamati dalle classi, e quindi erediteranno i suoi metodi, utilizzando la parola chiave "use",

<?php

trait MyTrait {
   public funtion hello(){
      echo "ciao";
   }
}

trait MyTrait2 {
   public funtion bye(){
      echo "bye bye";
   }
}

class Pippo {
   use MyTrait;
}

class Pluto {
   use MyTrait;
}

class Paperino {
   use MyTrait2;
}

$pippo=new Pippo();
$pippo->hello();

$pluto=new Pluto();
$pluto->hello();

$paperino=new Paperino();
$paperino->bye();

?>

E così abbiamo trovato la soluzione al nostro problema.

Traits multipli

E se la classe Pluto volesse usare anche il metodo "bye"? Semplice!

<?php

trait MyTrait {
   public funtion hello(){
      echo "ciao";
   }
}

trait MyTrait2 {
   public funtion bye(){
      echo "bye bye";
   }
}

class Pippo {
   use MyTrait;
}

class Pluto {
   use MyTrait, MyTrait2;
}

class Paperino {
   use MyTrait2;
}

$pluto=new Pluto();
$pluto->hello()." - ".$pluto->bye();

In questo modo istanziando un oggetto della classe Pluto, possiamo accedere sia al metodo "hello" che "bye".

Possiamo quindi inserire più traits all’interno della stessa classe: in questo caso la parola chiave use dovrà essere seguita dai nomi dei traits separati da una virgola.

Un trait può anche avere proprietà normali e statiche, metodi normali e statici, e metodi astratti (che vedremo tra poco), mentre non possono contenere delle costanti.

Ecco ad esempio di utilizzo di una proprietà normale

<?php

trait MyTrait {

   public $a=10;
   public funtion hello(){
      echo "ciao";
   }
}

class Pippo {
   use MyTrait;
}

$pluto=new Pippo();
echo $pluto->a;

?>

I traits permettono quindi di definire uno o più metodi che possono essere ereditati (utilizzati) da una classe senza la necessità di usare la parola chiave "extends", quindi permettono di ottenere un effetto simile all’ereditarietà multipla senza la complessità di quest’ultima. 

La differenza principale tra ereditarietà multipla e traits sta nel fatto che un trait non viene ereditato ma incluso.

Traits composti da altri Traits

Quando definiamo un trait possiamo includere al suo interno altri trait, incorporandone quindi le funzionalità.

Nel seguente esempio in cui Il trait "User" include il trait "Hello", ed quindi ingloba i metodi del trait Hello.

La classe Greeting in questo caso include il solo trait User.

<?php

trait Hello 
{
    public function sayHello() {
        echo 'Ciao';
    }
}
trait User 
{
    use Hello;
    public function sayName() {
        echo 'Giulio';
    }
}
 
class Greeting 
{
    use User;
}
 
$g = new Greeting();
$g->sayHello() ." ". $g->sayName();

?>

Dopo aver istanziato l'oggetto ($g) di classe Greeting, possiamo accedere ad entrambi i metodi "sayHello" e "sayName" grazie al fatto che il trait "User" include il trait "Hello" e quindi accede anche al metodo "sayHello" contenuto nel trait "Hello".

Metodi astratti

Esattamente come avviene per le classi, all’interno di un trait possiamo definire dei metodi astratti, imponendone così l’implementazione nelle classi che utilizzeranno il trait.

In questo esempio, nel trait "MyGreeting" definiamo il metodo astratto "sayName", che è solo una dichiarazione (non ha contenuto, come deve essere per i metodi astratti): il metodo, qui, non viene implementato, ma va implementato nella classe "Greeting", e che include il trait "MyGreeting",

<?php

trait MyGreeting
{
    public function sayHello() {
        echo 'Ciao';
    }
    abstract public function sayName();
}
 
class Greeting 
{
    use MyGreeting;
    public function sayName() {
        echo 'Giulio';
    } 
}
 
$g = new Greeting();
$g->sayHello()." ".$g->sayName();

?>

Precedenza tra metodi con lo stesso nome

Nei casi in cui, nei trait e nelle classi, si abbiano metodi con le stesso nome, varrà la seguente precedenza:

  • I metodi della classe sovrascrivono i metodi del trait
  • I metodi ereditati da una classe vengono sovrascritti (sono sottoposti ad override) dai metodi inseriti da un trait

Nel seguente esempio abbiamo la classe "Greeting" che estende la classe "Base", e quindi prende i suoi metodi, e che include il trait "MyGreeting".

<?php

trait MyGreeting
{
    public function sayHello()
    {
        echo "Ciao";
    }
 
    public function sayName()
    {
        echo "Luca";
    }         
}

class Base
{
    public function sayHello()
    {
        echo "Hello";
    }       
}
 
class Greeting extends Base
{
    use MyGreeting;
 
    public function sayName()
    {
        echo "Giulio";
    }  
 
    public function sayBaseHello()
    {
        echo parent::sayHello() . $this->sayName();
    }     
}
 
$g = new Greeting();
$g->sayHello()." ". $g->sayName(); //Ciao Giulio
$g->sayBaseHello(); //Hello Giulio

?>

Dopo aver istanziato l'oggetto ($g) di classe "Greeting" avremo che:

  • Il metodo sayHello(), definito nella classe "Base", è sottoposto ad overidde dal metodo omonimo definito nel trait "MyGreeting", questo perchè il metodo "sayHello" della classe base è ereditato dalla classe "Greeting", ma questa classe include il trait "MyGreeting" che contiene il metodo omonimo "sayHello" e questo prevale. Per cui otteniamo un "Ciao".
  • Il metodo sayName(), definito nella classe "Greeting", avrà la precedenza sul metodo omonimo del "MyGreeting", perchè la classe prevale sul trait. Per cui otteniamo un "Giulio".
  • Il metodo sayBaseHello(), definito nella classe "Greeting", dimostra come sia possibile forzare la chiamata al metodo della classe "Base" anzichè a quello del trait che avrebbe invece avuto la precedenza, utilizzando "parent::sayHello()" per utilizzare il metodo "sayHello" della classe parent "Base", e "$this->sayName()" per utilizzare il metodo della stessa classe "Greeting". Otteniamo un "Hello Giulio".

Gestione dei conflitti tra nomi dei metodi

Quando si lavora con traits multipli si possono generare conflitti di denominazione dei metodi con conseguente "errore fatale" dello script.

Fatal error: Trait method XXX has not been applied, because there are collisions with other trait methods on…

Questo avviene quando i traits contengono uno o più metodi con lo stesso nome.

PHP prevede la possibilità di risolvere questi conflitti in due modi:

  • Attraverso l’operatore insteadof si sceglie il metodo di un trait e si esclude l’omonimo dell’altro trait
  • Attraverso l’operatore as possiamo consentire l’inserimento di uno dei metodi in conflitto utilizzando un altro nome (un alias)

In questo esempio la classe "User" usa i traits "Login" e "Logout" , ed entrambi hanno un metodo con lo stesso nome: "log".

<?php

trait Login
{
    function log() 
    {
        echo "Benvenuto!";
    }
}
 
trait Logout
{
    function log() 
    {
        echo "Non sei più loggato";
    }
}
 
class User
{
    use Login, Logout {
        Login::log insteadof Logout;
        Logout::log as logLogout;
    }
}
 
$u = new User();
$u->log(); // Benvenuto!
$u->logLogout(); // Non sei più loggato

?>

Nella questa classe "User":

  • nella prima riga, attraverso l’operatore insteadof, facciamo in modo che il metodo "log" di Logout non venga utilizzato, evitando così il conflitto
  • con la seconda riga (opzionale), attraverso l’operatore as, si permette allo stesso metodo "log" di essere chiamato attraverso l’alias "logLogout" permettendoci così di utilizzare entrambi i metodi in conflitto, il primo con il nome originale, il secondo con un alias.

Proprietà dei traits

Come per i metodi, anche all'interno dei traits possiamo definire delle proprietà.

Però in questo caso, all’interno delle classi che li utilizzeranno, non possiamo definire una proprietà con lo stesso nome altrimenti verrà generato

  • o un warning (nello specifico un errore di tipo "E_STRICT") nel caso in cui la variabile abbia la stessa visibilità e valore iniziale
  • oppure un fatal error

In questo esempio la classe "ClassExample" include il trait "TraitExample". Nella classe sono definite due proprietà "$same" e "$different", e le stesse sono presenti nel trait.

La proprietà "$different" ha stessa visibilità (public) tuttavia valore diverso nella classe e nel trait, e questo genera un "fatal error", mentre la proprietà "$same" ha stessa visibilità (public) e stesso valore per cui otteniamo un "warning"

<?php

trait TraitExample {
    public $same = true;
    public $different = false;
}
 
class ClassExample {
    use TraitExample;
    public $same = true; // Warning
    public $different = true; // Fatal error
}

?>

Inoltre, se un trait contiene proprietà statiche, ogni classe che usa quel trait avrà istanze indipendenti di tali proprietà.

In questo esempio abbiamo due classi, "Hello" e "User" che includono il trait "Test", e quest'ultimo contiene una proprietà statica "$foo"

<?php

trait Test
{
    public static $foo;
}
class Hello
{
    use Test;
}
class User
{
    use Test;
}
 
Hello::$foo = 'Hello';
User::$foo = 'Giulio';   

echo Hello::$foo .' '. User::$foo; // Hello Giulio

?>

Reflection

Dalla versione 5.4 di PHP sono stati introdotti quattro nuovi metodi che consentono di recuperare informazioni sui trait utilizzati nel nostro script:

  • ReflectionClass::getTraits()
    Rende un array di tutti i traits usati nella classe
  • ReflectionClass::getTraitNames()
    Rende un array con i nomi dei traits usati nella classe
  • ReflectionClass::getTraitAliases()
    Rende un array di eventuali alias per i nomi dei metodi
  • ReflectionClass::isTrait()
    Può essere utile per sapere se qualcosa è un trait oppure no

Ecco un esempio con cui, dopo aver istanziato l'oggetto ($rc) di classe ReflectionClass, nel caso in cui (es è così) "User" non è un trait (infatti è una classe), estraggo il primo trait della classe, poi estraggo l'alias "logLogout", ed infine, per ogni trait estraggo il suo nome, ed il nome della sua primo metodo.

<?php

trait Login
{
    function log() 
    {
        echo "Accesso al sistema";
    }
}
 
trait Logout
{
    function log() 
    {
        echo "Uscita dal sistema";
    }
}
 
class User
{
    use Login, Logout {
        Login::log insteadof Logout;
        Logout::log as logLogout;
    }
}
 
$rc = new ReflectionClass('User');
if (!$rc->isTrait()) 
{
    echo $rc->getTraitNames()[0]; //Login
    echo $rc->getTraitAliases()['logLogout']; // Logout::log

    foreach ($rc->getTraits() as $v) 
    {
         echo $v->getMethods()[0]->class .' '. 
         $v->getMethods()[0]->name; // Login log Logout log
    }
}

?>

Abbiamo così concluso la spiegazione dei traits, uno strumento molto importante per ridurre la duplicazione del codice e migliorarne la manutenibilità e la pulizia.

Potrebbe interessarti

 
 
 
 
pay per script

Hai bisogno di uno script PHP personalizzato, di una particolare configurazione su Linux, di una gestione dei tuoi server Linux, o di una consulenza per il tuo progetto?

x

ATTENZIONE