Desde PHP 5.4.0, PHP implementa una metodología de reutilización de código llamada Traits.
Los traits (rasgos) son un mecanismo de reutilización de código en lenguajes de herencia simple, como PHP. El objetivo de un trait es el de reducir las limitaciones propias de la herencia simple permitiendo que los desarrolladores reutilicen a voluntad conjuntos de métodos sobre varias clases independientes y pertenecientes a clases jerárquicas distintas. La semántica a la hora combinar Traits y clases se define de tal manera que reduzca su complejidad y se eviten los problemas típicos asociados a la herencia múltiple y a los Mixins.
Un Trait es similar a una clase, pero con el único objetivo de agrupar funcionalidades muy específicas y de una manera coherente. No se puede instanciar directamente un Trait. Es por tanto un añadido a la herencia tradicional, y habilita la composición horizontal de comportamientos; es decir, permite combinar miembros de clases sin tener que usar herencia.
Ejemplo #1 Ejemplo de Trait
<?php
trait ezcReflectionReturnInfo {
function getReturnType() { /*1*/ }
function getReturnDescription() { /*2*/ }
}
class ezcReflectionMethod extends ReflectionMethod {
use ezcReflectionReturnInfo;
/* ... */
}
class ezcReflectionFunction extends ReflectionFunction {
use ezcReflectionReturnInfo;
/* ... */
}
?>
Los miembros heredados de una clase base se sobrescriben cuando se inserta otro miembro homónimo desde un Trait. De acuerdo con el orden de precedencia, los miembros de la clase actual sobrescriben los métodos del Trait, que a su vez sobrescribe los métodos heredados.
Ejemplo #2 Ejemplo de Orden de Precedencia
Se sobrescribe un miembro de la clase base con el método insertado en MiHolaMundo a partir del Trait DecirMundo. El comportamiento es el mismo para los métodos definidos en la clase MiHolaMundo. Según el orden de precedencia los métodos de la clase actual sobrescriben los métodos del Trait, a la vez que el Trait sobrescribe los métodos de la clase base.
<?php
class Base {
public function decirHola() {
echo '¡Hola ';
}
}
trait DecirMundo {
public function decirHola() {
parent::decirHola();
echo 'Mundo!';
}
}
class MiHolaMundo extends Base {
use DecirMundo;
}
$o = new MiHolaMundo();
$o->decirHola();
?>
El resultado del ejemplo sería:
¡Hola Mundo!
Ejemplo #3 Ejemplo de Orden de Precedencia #2
<?php
trait HolaMundo {
public function decirHola() {
echo '¡Hola Mundo!';
}
}
class ElMundoNoEsSuficiente {
use HolaMundo;
public function decirHola() {
echo '¡Hola Universo!';
}
}
$o = new ElMundoNoEsSuficiente();
$o->decirHola();
?>
El resultado del ejemplo sería:
¡Hola Universo!
Se pueden insertar múltiples Traits en una clase, mediante una lista separada por comas en la sentencia use.
Ejemplo #4 Uso de Múltiples Traits
<?php
trait Hola {
public function decirHola() {
echo 'Hola ';
}
}
trait Mundo {
public function decirMundo() {
echo 'Mundo';
}
}
class MiHolaMundo {
use Hola, Mundo;
public function decirAdmiración() {
echo '!';
}
}
$o = new MiHolaMundo();
$o->decirHola();
$o->decirMundo();
$o->decirAdmiración();
?>
El resultado del ejemplo sería:
Hola Mundo!
Si dos Traits insertan un método con el mismo nombre, se produce un error fatal, siempre y cuando no se haya resuelto explicitamente el conflicto.
Para resolver los conflictos de nombres entre Traits en una misma clase, se debe usar el operador insteadof para elejir unívocamente uno de los métodos conflictivos.
Como esto sólo permite excluir métodos, se puede usar el operador as para permitir incluir uno de los métodos conflictivos bajo otro nombre.
Ejemplo #5 Resolución de Conflictos
En este ejemplo, Talker utiliza los traits A y B. Como A y B tienen métodos conflictos, se define el uso de la variante de smallTalk del trait B, y la variante de bigTalk del traita A.
Aliased_Talker hace uso del operador as para poder usar la implementación de bigTalk de B, bajo el alias adicional talk.
<?php
trait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}
trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}
class Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
?>
Al usar el operador as, se puede también ajustar la visibilidad del método en la clase exhibida.
Ejemplo #6 Modificar la Visibilidad de un Método
<?php
trait HolaMundo {
public function decirHola() {
echo 'Hola Mundo!';
}
}
// Cambiamos visibilidad de decirHola
class MiClase1 {
use HolaMundo { decirHola as protected }
}
// Método alias con visibilidad cambiada
// La visibilidad de decirHola no cambia
class MiClase2 {
use HolaMundo { hacerHolaMundo as private decirHola }
}
?>
Al igual que las clases, los Traits también pueden hacer uso de otros Traits. Al usar uno o más traits en la definición de un trait, éste puede componerse parcial o completamente de miembros de finidos en esos otros traits.
Ejemplo #7 Traits Compuestos de Traits
<?php
trait Hola {
public function decirHola() {
echo 'Hola ';
}
}
trait Mundo {
public function decirMundo() {
echo 'Mundo!';
}
}
trait HolaMundo {
use Hola, Mundo;
}
class MiHolaMundo {
use HolaMundo;
}
$o = new MiHolaMundo();
$o->decirHola();
$o->decirMundo();
?>
El resultado del ejemplo sería:
Hola Mundo!
Los traits soportan el uso de métodos abstractos para imponer requisitos a la clase a la que se exhiban.
Ejemplo #8 Expresar Resquisitos con Métodos Abstractos
<?php
trait Hola {
public function decirHolaMundo() {
echo 'Hola'.$this->obtenerMundo();
}
abstract public function obtenerMundo();
}
class MiHolaMundo {
private $mundo;
use Hola;
public function obtenerMundo() {
return $this->mundo;
}
public function asignarMundo($val) {
$this->mundo = $val;
}
}
?>
Los métodos de un trait pueden referenciar a variables estáticas, pero no puede ser definido en el trait. Los traits, sin embargo, pueden definir método estáticos a la clase a la que se exhiben.
Ejemplo #9 Variables estáticas
<?php
trait Contador {
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
}
class C1 {
use Contador;
}
class C2 {
use Contador;
}
$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>
Ejemplo #10 Métodos Estáticos
<?php
trait EjemploEstatico {
public static function hacerAlgo() {
return 'Hacer algo';
}
}
class Ejemplo {
use EjemploEstatico;
}
Ejemplo::hacerAlgo();
?>
Los traits también pueden definir propiedades.
Ejemplo #11 Definir Propiedades
<?php
trait PropiedadesTrait {
public $x = 1;
}
class EjemploPropiedades {
use PropiedadesTrait;
}
$ejemplo = new EjemploPropiedades;
$ejemplo->x;
?>
Si un trait define una propiedad una clase no puede definir una propiedad con
el mismo nombre, si no se emitirá un error.Éste de tipo
E_STRICT
si la definición de la clase es compatible (misma
visibilidad y valor inicial) o de otro modo un error fatal.
Ejemplo #12 Resolución de Conflictos
<?php
trait PropiedadesTrait {
public $misma = true;
public $diferente = false;
}
class EjemploPropiedades {
use PropiedadesTrait;
public $misma = true; // Estándares estrictos
public $diferente = true; // Error fatal
}
?>