¿Qué son los traits en PHP?
Descubre qué son los traits de PHP y cómo puedes emplearlos para reutilizar código

Con la versión 5.4 de PHP (ha llovido ya un poco) apareció una nueva característica conocida como traits que permitía reutilizar código entre distintas clases sin recurrir a la herencia. He decidido crear este artículo porque creo que los traits es de esas características que si no has profundizado mucho en el lenguaje pasan desapercibidas hasta que un día ves dentro de una clase algo de este estilo:
class A { use Something;}
y te preguntas por qué está declarando ahí un use
en vez de hacerlo al comienzo del archivo donde habitualmente estamos acostumbrados.
Así que en este artículo realizaré una introducción a los traits en PHP y sus posibles usos. ¡Espero que os sirva el artículo!
Una breve introducción
Como sabréis, en PHP una clase únicamente puede heredar de otra a diferencia de otros lenguajes como C++ en el que se permite la herencia múltiple:

Si bien podríamos discutir hasta qué punto recurrir a la herencia múltiple es o no una buena práctica, es innegable que en ciertas ocasiones nos puede ayudar a seguir el principio DRY (don’t repeat yourself). Es aquí donde el concepto de los traits nos puede ser útil, permitiéndonos reutilizar código entre distintas clases pese a no disponer de herencia múltiple. Veamos cómo funcionan.
¿Cómo funcionan los traits en PHP?
Como decía al principio del artículo, los traits fueron introducidos en la versión 5.4 de PHP. Son muy similares a una clase (pero que no podemos instanciar) y su objetivo es agrupar funcionalidad muy específica y de manera coherente. Por ejemplo:
<?phptrait Timestampable { protected $createdAt; public function getCreatedAt() { return $this->createdAt; } public function setCreatedAt(\DateTimeInterface $createdAt) { $this->createdAt = $createdAt; return $this; }}
Una vez declarado un trait, podemos reutilizarlo tantas veces como queramos de la siguiente forma:
class A { use Timestampable;}class B { use Timestampable;}
De este modo, tanto la clase A
como la clase B
obtendrían la propiedad createdAt
así como sus métodos getter y setter declarados en el trait. Fácil, ¿verdad?
Características de los traits
Entre las principales características de los traits se encuentran las siguientes:
Pueden definir métodos y propiedades con cualquier nivel de acceso tal y como hemos visto en el ejemplo anterior.
Pueden definir constructores y destructores, por ejemplo:
trait T { public $prop = null; public function __construct($prop) {
echo "Constructor called\n";
$this->prop = $prop;
} public function __destruct() {
echo "Destructor called\n";
}
}class A {
use T;
}$a = new A("Hello World\n"); // Constructor calledecho $a->prop; // Hello World// Destructor called
Pueden definir métodos abstractos que fuercen a las clases que emplean el trait a implementarlos:
trait Foo { public function sayHello() {
echo 'Hi'. $this->getName();
} abstract public function getName();}
class Bar {
private $name;
use Foo; public function getName() {
return $this->name;
} public function setName($name) {
$this->name = $name;
}
}
Pueden también definir propiedades y métodos estáticos:
trait Foo {
public static function doFoo() {
echo 'do foo';
}
}
class Bar {
use Foo;
}
Bar::doFoo(); // do foo
Una clase puede emplear tantos traits como quiera:
trait Foo {
public function doFoo() {
echo 'do foo';
}
}trait Bar {
public function doBar() {
echo 'do bar';
}
}class Zeta {
use Foo, Bar;
}
$zeta = new Zeta();$zeta->doFoo();$zeta->doBar();
Los traits además pueden componerse con otros:
trait Foo {
public function doFoo() {
echo 'foo';
}
}
trait Bar {
public function doBar() {
echo 'bar';
}
}
trait Zeta {
use Foo, Bar;
}
class A {
use Zeta;
}
$o = new A();
$o->doFoo();
$o->doBar();
Precedencia en los traits
Como regla general los miembros de la clase que emplea el trait sobrescriben los miembros definidos en el trait que a su vez (y esto es importante) sobrescribe los heredados. Es decir:
class Foo {
public function doFoo() {
echo 'do foo';
}
}
trait Bar {
public function doFoo() {
echo 'do bar';
}
}
class Zeta extends Foo {
use Bar;
}
$o = new Zeta();
$o->doFoo(); // 'do bar'
Es decir, el método doFoo
de la clase Foo
es sobrescrito por el trait Bar
. Sin embargo:
class Foo {
public function doFoo() {
echo 'do foo';
}
}
trait Bar {
public function doFoo() {
echo 'do bar';
}
}
class Zeta extends Foo {
use Bar; public function doFoo() {
echo 'do foo again';
}
}
$o = new Zeta();
$o->doFoo(); // 'do foo again'
La clase que usa el trait sobrescribe los métodos definidos en el trait.
En el caso de que dos traits definan el mismo método se producirá un error fatal salvo que resolvamos explícitamente el conflicto mediante la instrucción insteadof
del siguiente modo:
trait Foo {
public function doFoo() {
echo 'do foo';
}
}trait AnotherFoo {
public function doFoo() {
echo 'do another foo';
}
}class Zeta { use Foo, AnotherFoo { Foo::doFoo insteadof AnotherFoo; }}$zeta = new Zeta();$zeta->doFoo(); // do foo
Conclusiones
Como veis, los traits nos dan una gran flexibilidad a la hora de reutilizar código sin tener que recurrir a la herencia múltiple, pues con ellos podemos encapsular una cierta funcionalidad (como por ejemplo la característica de posee un atributo createdAt
en la clase) y reusarla siempre que queramos. De hecho, si trabajáis con las DoctrineExtensions de Gedmo es probable que ya os suenen algunas de las que se definen en esta librería, por lo que su uso no es algo tan residual como cabría pensar.
Sin embargo, antes de precipitarnos a crear una trait, mi recomendación es que primero penséis si no existe algún patrón de diseño en PHP que encaje más con el resultado que buscáis. Por ejemplo, recordad que para añadir funcionalidad a una clase sin recurrir a la herencia también existe el patrón Decorador (el cual “funciona” mediante composición respetando el principio Open-Closed) y que además es posible implementar directamente en el container de Symfony:
https://medium.com/@ger86/c%C3%B3mo-decorar-nuestros-repositorios-en-symfony-2353960ba125
¿Quieres recibir más artículos como este?
Suscríbete a nuestra newsletter: