A causa delle sue caratteristiche strutturali PHP è un linguaggio ad ereditarietà singola, in pratica una classe può ereditare da una sola classe attraverso un sistema di estensione verticale che mette in condivisione metodi e proprietà associati agli oggetti; con la versione 5.4, PHP cerca di superare il problema relativo al mancato supporto dell'ereditarietà multipla introducendo una nuova funzionalità, i traits, essi forniscono infatti dei metodi riutilizzabili all'interno di classi che potranno sfruttarli attraverso una sorta di meccanismo basato sull'estensione orizzontale: se un trait presenta un metodo, una classe potrà utilizzarlo richiamando il trait stesso.
In questa breve trattazione verranno proposti alcuni semplici esempio di utilizzo dei traits e di riutilizzo dei relativi metodi.
Utilizzo di un trait in una classe
A livello sintattico, i traits funzionano in modo molto simile alle classi, nel caso specifico però essi vengono inizializzati attraverso la keyword trait
seguita dal nome del trait che potrà essere scelto arbitrariamente; i metodi associati verranno dichiarati all'interno di parentesi graffe e potranno essere richiamati in una classe utilizzando la parola chiave use
seguita dal nome del trait. Il seguente esempio mostra un semplice caso di definizione e impiego di un trait:
<?php
// definizione di un trait
trait GeneraIdUnico
{
// introduzione dei metodi
public function setIntro() {
echo "Codice: ";
}
public function getId() {
echo uniqid();
}
}
// definizione di una classe
class UsaTrait
{
// utilizzo del trait
use GeneraIdUnico;
}
// generazione dell'oggetto di classe
$data = new UsaTrait();
// utilizzo dei metodi del trait
$data->setIntro();
$data->getId();
?>
L'esempio proposto presenta innanzitutto l'inzializzazione di un trait, denominato nel caso specifico GeneraIdUnico
, esso integra due metodi, il primo destinato a stampare una semplice stringa di testo, il secondo associato al compito di generare un identificatore univoco basato sul momento corrente espresso in millisecondi.
Dopo il trait viene introdotta una classe (UsaTrait
), essa non presenta dei metodi propri, ma li acquisisce dal trait precedentemente definito che viene reso disponibile attraverso la parola chiave use
; l'oggetto di classe viene generato tramite istanza della stessa, tale oggetto potrà essere utilizzato per richiamare i metodi associati al trait che sono stati ereditati dalla classe.
Da notare come non sia prevista l'istanza diretta per la generazione un oggetto del trait tramite new
, tale procedura continua invece ad essere specifica per le classi.
Utilizzo di trait multipli in una classe
Data una classe, questa potrà richiamare più di un trait riutilizzandone tutti i metodi; la sintassi prevista è basata anche in questo caso sulla parola chiave use
, essa dovrà essere seguita dai nomi dei trait separati da una virgola.
L'esempio seguente presenta una classe che richiama tre diversi traits:
<?php
// definizione dei traits e dei relativi metodi
trait DataCorrente
{
public function getData() {
echo date("d-m-Y");
}
}
trait SeparaDateOrari
{
public function setSepratore() {
echo " ";
}
}
trait OraCorrente
{
public function getOra() {
echo date("H:i:s");
}
}
// definizione della classe
class DataOra
{
// utilizzo di trait multipli
use DataCorrente, SeparaDateOrari, OraCorrente;
}
// istanza e utilizzo dei metodi
$data = new DataOra();
$data->getData();
$data->setSepratore();
$data->getOra();
?>
Non è necessario che i traits vengano richiamati tramite use
nello stesso ordine con il quale sono stati inizializzati, tale disposizione è comunque consigliabile per rendere il sorgente maggiormente leggibile.
L'esempio mostrato rigurda tre traits associati ad altrettanti metodi, il primo destinato a stampare la data corrente, il secondo a generare un semplice separatore e il terzo a restituire l'orario corrente; eseguendo l'applicazione si nota come tutti i metodi vengono ereditati dalla classe e divengono utilizzabili senza conflitti attraverso l'istanza della stessa.
Utilizzo di trait all'interno di trait
La parola chiave use
può essere utilizzata anche da un trait per l'impiego dei metodi associati ad un altro trait; naturalmente, anche in questo caso i metodi potranno essere sfruttati soltanto in seguito alla generazione di un oggetto di classe; tale meccanismo è evidenziato dalla seguente applicazione:
<?php
// definizione dei traits
trait CalcolaSha1Hash
{
public function getSha1Hash() {
echo sha1(rand());
}
}
trait CalcolaSha1AndMd5Hash
{
// uso del primo trait all'interno di un secondo trait
use CalcolaSha1Hash;
public function getMd5Hash() {
echo md5(rand());
}
}
class CalcolaHash
{
// utilizzo del trait che richiama il primo trait
use CalcolaSha1AndMd5Hash;
}
$data = new CalcolaHash();
$data->getSha1Hash();
echo "<br />";
$data->getMd5Hash();
?>
Nell'esempio proposto viene inizializzato un primo trait associato ad un metodo il cui compito è quello di calcolare l'hash sha1 di una stringa numerica generata casualmente. Il secondo trait ha invece una doppia funzione: la prima è quella di ereditare il metodo del trait precedente, la seconda quella di mettere a disposizione un metodo in grado di calcolare l'hash md5 di un valore numerico intero prodotto casulmente.
In questo caso la successiva classe dovrà richiamare unicamente il secondo trait per poter utilizzare i metodi di entrambi i traits inizializzati; la generazione dell'oggetto di classe permetterà il riutilizzo di tutti i metodi disponibili.
Dichiarazione dei metodi astratti nei trait
Un caso particolarmente interessante di utilizzo dei traits è quello relativo alla dichiarazione di metodi astratti; in pratica, una classe che voglia utilizzare un trait in cui è definito un metodo di questo tipo dovrà necessariamente implementarlo per poterlo adottare. Anche in questo caso un semplice esempio aiuterà a comprendere meglio il concetto:
<?php
trait TraitA
{
public function metodoA() {
echo "Esecuzione primo metodo.";
}
// definizione di un metodo astratto
abstract public function metodoB();
}
class MetodoA
{
// uso del trait
use TraitA;
// implementazione del metodo astratto del trait
public function metodoB()
{
echo "Esecuzione secondo metodo.";
}
}
$data = new MetodoA();
echo "<br />";
$data->metodoB();
?>
Come è noto, un metodo astratto è per sua natura un metodo non implementato che rappresenta un'operazione di tipo generale. Nell'esempio proposto viene definito un trait che presenta un metodo a cui è associata una funzione (la stampa di una stringa) e un metodo astratto; quest'ultimo viene implementato successivamente da una classe che richiama il trait tramite use
; l'istanza della classe permetterà di utilizzare immediatmente il primo metodo del trait, mentre il metodo astratto successivamente implementato potrà essere richiamato tramite l'oggetto generato dall'istanza. Per cui l'esecuzione delle script porterà alla stampa delle seguenti stringhe di testo:
Esecuzione primo metodo. Esecuzione secondo metodo.
Risoluzione dei conflitti nei traits
La risoluzione dei conflitti tra metodi è un'altro meccanismo interessante dei traits di PHP 5.4, a questo proposito è possibile proporre due casi, il primo riguarda un possibile conflitto tra due metodi aventi lo stesso nome:
<?php
// definizione di due traits contenenti metodi omonimi
trait TraitA
{
public function metodoA()
{
echo "Metodo del TraitA.";
}
}
trait TraitB
{
public function metodoA()
{
echo "Metodo del TraitB.";
}
}
class UsaTrait
{
use TraitA, TraitB
{
// selezione del metodo da utilizzare
TraitA::metodoA insteadof TraitB;
}
}
$data = new UsaTrait();
$data->metodoA();
?>
Sia il primo che il secondo trait presentano un metodo chiamato metodoA
, entrambi svolgono la stessa funzione, cioè la stampa di una stringa di testo, le due stringhe sono però differenti.
Cosa accadrebbe nel caso in cui una classe richiamasse entrambi i traits? Tutti e due i metodi verrebbero si ereditati, ma quale dei due sarebbe utilizzato in seguito ad una chiamata? Tale conflitto viene risolto tramite la parola chiave insteadof
che dove essere seguita dal nome del trait il cui metodo non verrà utilizzato, nel caso specifico viene quindi impiegato il metodoA
del TraitA
e non quello omonimo del secondo trait
Un secondo caso di conflitto riguarda un'eventualità differente, cioè la necessità di poter utilizzare simultaneamente due metodi aventi lo stesso nome; la risoluzione di tale problematica può essere esemplificata proponendo una variante dell'applicazione introdotta in precedenza:
<?php
// definizione di due traits contenenti metodi omonimi
trait NuovoTraitA
{
public function nuovoMetodoA()
{
echo "Metodo del NuovoTraitA.";
}
}
trait NuovoTraitB
{
public function nuovoMetodoA()
{
echo "Metodo del NuovoTraitB.";
}
}
class UsaNuovoTrait
{
use NuovoTraitA, NuovoTraitB
{
// selezione del metodo da utilizzare
NuovoTraitA::nuovoMetodoA insteadof NuovoTraitB;
// risoluzione del conflitto dovuto all'omonimia
NuovoTraitB::nuovoMetodoA as nuovoMetodoB;
}
}
$data = new UsaNuovoTrait();
$data->nuovoMetodoA();
echo "<br />";
$data->nuovoMetodoB();
?>
Anche in questo caso la classe utilizza entrambi i traits precedentemente definiti, qui però il conflitto viene gestito in modo più articolato: insteadof
permette ancora una volta di decidere quale dei due metodi omonimi utilizzare conservandone il nome originale, grazie alla parola chiave as
si può quindi stabilire quale nuovo nome associare al metodo precedentemente escluso; in questo modo sarà possibile utilizzarli entrambi tramite una chiamata effettuata attraverso l'oggetto generato dall'istanza di classe.
Conclusioni
I traits sono una delle novità più importanti tra quelle introdotte con la versione 5.4 di PHP perché mettono a disposizione delle classi dei metodi riutilizzabili, in questo modo viene parzialmente risolto il problema della mancanza di un meccanismo di eredità multipla nel linguaggio; nel corso di questa trattazione sono state descritte le sintassi necessarie per l'inizializzazione e l'utilizzo di tali costrutti e dei metodi ad essi associati, sono state inoltre approfondite alcune casistiche particolari come l'uso simultaneao di più traits, l'utilizzo di traits da parte di altri traits, la gestione dei metodi astratti e la risoluzione di conflitti dovuti ad omonimie tra i metodi.