Desde su versión 5.4.0, PHP implementa una metodología de reutilización de código llamada Traits.
Los rasgos («traits» en inglés) son un mecanismo de reutilización de código en lenguajes de herencia simple, como PHP. El objetivo de un rasgo 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 rasgo
<?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 varios rasgos
<?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 elegir unívocamente uno de los métodos conflictivos.
Como esto solamente permite excluir métodos, se puede utilizar el operador as para añadir un alias a uno de los métodos. Observe que el operador as no renombra el método ni afecta a cualquier otro método.
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 trait 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;
}
}
?>
Nota:
Antes de PHP 7.0, definir una propiedad en una clase con el mismo nombre que en un rasgo lanzaba un
E_STRICT
si la definición de la clase era compatible (misma visivilidad y mismo valor inicial).
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 { decirHola as private miPrivadoHola; }
}
?>
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 definidos 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.
Una clase concreta cumple este requisito definiendo un método concreto con el mismo nombre; su firma puede ser diferente.
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 trait pueden definir miembros y métodos estáticos.
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, a menos que sea compatible (misma visibilidad y mismo valor inicial), si no, se emitirá un error fatal. Antes de PHP 7.0, definir una propiedad en una clase con la misma visibilidad y el mismo valor inicial que en el rasgo, emitía un aviso E_STRICT.
Ejemplo #12 Resolución de Conflictos
<?php
trait PropiedadesTrait {
public $misma = true;
public $diferente = false;
}
class EjemploPropiedades {
use PropiedadesTrait;
public $misma = true; // Permitido a partir de PHP 7.0.0; aviso E_STRICT anteriormente
public $diferente = true; // Error fatal
}
?>