Rasgos (Traits)

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() { }
function
getReturnDescription() { }
}

class
ezcReflectionMethod extends ReflectionMethod {
use
ezcReflectionReturnInfo;

}

class
ezcReflectionFunction extends ReflectionFunction {
use
ezcReflectionReturnInfo;

}
?>

Precedencia

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! 

Multiples Traits

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! 

Resolución de Conflictos

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

Modificando la Visibilidad de los Métodos

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; }
}
?>

Traits Compuestos de Traits

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! 

Miembros Abstractos de Traits

Los traits soportan el uso de métodos abstractos para imponer requisitos a la clase a la que se exhiban.

Precaución

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;
}
}
?>

Miembros Estáticos en Traits

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();
?>

Propiedades

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
}
?>
To Top