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

La premessa alla spiegazione che seguirà è che già sappiate cosa sono le classi, i metodi e le proprietà, e l'ereditarietà delle classi.

In termini tecnici, i trait permettono di definire uno o più metodi che possono essere ereditati (utilizzati) da una classe senza la necessità di usare la parola chiave "extend", 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.

Facciamo un semplice esempio.

Creiamo un file al cui interno creiamo un trait, con nome "EsempioTrait", che contiene due metodi pubblici con nome "msg" e "msg2"

Creiamo anche la classe Welcome, che include il trait utilizzando la parola chiave use, e quindi eredita i suoi metodi.

NOTA: per comodità ho inserito trait e classi nello stesso file, ma normalmente i trait vanno inseriti in file separati.

<?php

trait EsempioTrait
{
    public function msg() {
        return "hai chiamato il metodo msg<br />";
    }

    public function msg2() {
        return "hai chiamato il metodo msg2<br />";
   }
}

class Welcome {
  use EsempioTrait;
}

$obj = new Welcome();
echo $obj->msg1();
echo $obj->msg2();
?>

In questo file abbiamo istanziato un oggetto ($obj) di classe Welcome, che include il trait "EsenpioTrait" e questo ci consente di accedere sia al metodo "msg" che al metodo "msg2"

Traits multipli

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

Vediamo un nuovo esempio. Abbiamo due trait, "Hello" e "User", e una classe "Greating" che include i due trait.

<?php

trait Hello 
{
    public function sayHello() {
        echo 'Ciao';
    }
}

trait User 
{
    public function sayName() {
        echo 'Giulio';
    }
}

class Greeting 
{
    use Hello, User;
}

$g = new Greeting();
$g->sayHello() ." ". $g->sayName();

?>

Abbiamo istanziato un oggetto ($g) di classe Greeting, che include sia il trait "User" che il trait "Hello", e, grazie a questa inclusione, possiamo accedere ai metodi "sayHello" e "sayName" contenuti nei due trait.

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: 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, sia 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 im 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
  • eflectionClass::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