giovedì 2 agosto 2012

PHP 5.4: I Traits

I Traits (tratto in italiano), sono implementati a partire da PHP 5.4, e sono realizzati quale metodo per il riuso del codice.

Il sistema dell'ereditarietà della classi di PHP permette ad un classe genitrice, o superclass, di avere zero, una o più classi figlie, o subclass. Al contrario ogni classe può essere figlia di al più una classe genitrice. Alcune delle limitazioni dovute a questo tipo di gestione dell'ereditarietà, per altro molto diffusa in diversi linguaggi di programmazione, possono essere ridotte grazie ai Trait, che permettono il libero riutilizzo di insiemi di metodi in classi diverse appartenenti a diverse gerarchie.

Un Trait è per certi versi simile ad una classe, ma andrebbe utilizzato solo per raggruppare funzionalità di granularità fine e in modo consistente. Un Trait diversamente da una classe non può essere istanziato, ma è un'aggiunta all'ereditarietà tradizionale che permette una composizione orizzontale di comportamenti; ossia l'applicazione di membri della classe senza necessitare dell'ereditarietà.



Le Precedenze

Data una superclass ed una subclass che eredita dalla prima e fa uso di un Trait si ha, nel caso in cui superclass e Trait definiscano lo stesso metodo, che il Trait ridefinisce il metodo della superclass e lo può richiamare tramite il costrutto parent::metodo().
Se anche la subclass ridefinisce lo stesso metodo allora parent::metodo() fa sempre riferimento al metodo della superclass, mentre il metodo del Trait diventa inaccessibile alla classe a meno che non si ricorra al costrutto as per dargli un nome alternativo.

Esempio:

<?php
class superclass{
    public function A(){return __CLASS__;}
}

trait aTrait{
    public function A(){return 'aTrait - '.parent::A();}
}

class subclass extends superclass{
    use aTrait{A as TraitA;}
    public function A(){return __CLASS__.' - '.$this->TraitA();}
}
//Output: subclass - Trait - superclass
echo (new subclass())->A();
?>


Traits multipli

E' possibile fare uso di più Traits in una classe elencandoli dopo il costrutto use e separandoli con una virgola. Per un esempio vedere la sezione successiva.

Risoluzione dei Conflitti e Cambio di Visibilità

Se due o più Taits definiscono lo stesso metodo, si ottiene un fatal error qualora il conflitto non sia esplicitamente risolto.
Per risolvere i conflitti di nomi fra Traits utilizzati nella stessa classe, si ricorre all'operatore insteadof con cui si indica il membro da utilizzare seguito dall'elenco dei Traits da ignorare per quello stesso membro. E' anche possibile utilizzare l'operatore as per ridefinire il nome del metodo di un Trait. Ciò al fine di utilizzare un membro anche se escluso con il costrutto insteadof. In più il costrutto as può anche essere utilizzato per modificare l'accessibilità del membro nella classe che ne fa uso.

Esempio:

<?php
// I tre Traits definiscono tutti gli stessi metodi A e B
trait Uno{    
    public function A(){return 'hello';}
    public function B(){return 'world';}
}

trait Due{    
    public function A(){return 'HELLO';}
    public function B(){return 'WORLD';}
}

trait Tre{
    public function A(){return 'HeLlO';}
    public function B(){return 'WoRlD';}    
}

//La classe conflitto fa uso di tutti e tre i Trait che confliggono
class Conflitto {

    use Uno,Due,Tre {        
        //Per A utilizzo quello di Uno al posto di quello di Due e Tre     
        Uno::A insteadof Due, Tre;
        //Per B utilizzo quello di Tre al posto di quello di Uno e Due
        Tre::B insteadof Uno, Due;
        //Recupero in metodo A di Due con nome AA e lo rendo privato
        Due::A as private AA;
        //Recupero il metodo B di Due con nome BB
        Due::B as BB;
        //Rendo protetto il metodo A che per quanto detto prima è quello di Uno
        A as protected;
    }

    public function PrivateAA() {
        return $this->AA(); //Il metodo AA non è accessibile dall'esterno
    }

    public function PrivateA() {
        return $this->A(); //Il metodo A non è accessibile dall'esterno
    }
}

$conf = new Conflitto();
echo $conf->PrivateA(), ' ', $conf->B(), '<br>', $conf->PrivateAA(), ' ', $conf->BB();
?>


Traits di Traits

Un Trait al suo interno può fare uso, come le classi, del costrutto use e quindi di altri Traits.

Traits e Metodi Astratti

I Trait possono definire dei metodi astratti di tipo pubblico o protetto (ma non privato) al fine di obbligare l'utilizzatore del Trait a definire un certo metodo magari utilizzato dal trait stesso facendo quindi uso del polimorfismo (il comportamento del Trait cambia a seconda della classe che lo usa) o late-binding dei metodi (il comportamento del metodo è definito solo nel momento in cui si istanza una classe che fa uso del trait) come avviene per le classi astratte non degenerate in interfacce.

Esempio:

<?php
trait unSaluto{
    abstract protected function dammiNome();
    public function saluta(){return 'Ciao '.$this->dammiNome();}
}

class Saluto {
    use unSaluto;    
    protected function dammiNome(){return 'Ciro';}
}

$saluto = new Saluto();
echo $saluto->saluta();
?>

Static e Variabili Membro

In un Trait possono essere definiti metodi statici. Non possono invece essere definite variabili di classe o variabili statiche. Variabili statiche possono invece essere definite all'interno dei metodi del Trait, ma ovviamente non è la stessa cosa che definire una variabile membro statica. Una variabile membro definita in un Trait non può essere ridefinita nella classe, provocando un fatal error o un E_STRICT error se definita dello stesso tipo e inizializzata con lo stesso valore.