Menu

Traits en PHP: use, varios traits e insteadof

Un trait de PHP es un bloque reutilizable de métodos y propiedades que copias en una clase con use TraitName;. Aprende a usar varios traits, resolver conflictos de nombres con insteadof y as, los miembros abstractos y estáticos de un trait, qué método gana y la diferencia entre trait, interfaz y clase abstracta.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un trait de PHP es un bloque reutilizable de métodos y propiedades. Lo declaras con trait Name { ... } y lo incorporas a cualquier clase con use Name; dentro del cuerpo de la clase. PHP copia los miembros del trait en la clase, así que funcionan exactamente como si los hubieras escrito ahí.

User y Robot no tienen un padre común, y aun así los dos tienen ahora hello(). Los traits existen para este caso: las clases de PHP solo pueden extender una clase, y un trait comparte código en horizontal, entre clases que no están relacionadas.

Traits con propiedades

Un trait puede traer propiedades junto con sus métodos. Los métodos usan $this como cualquier método de la clase, porque una vez copiado el trait, son métodos de la clase:

Una clase no puede declarar una propiedad con el mismo nombre que una propiedad del trait salvo que las dos declaraciones sean idénticas (misma visibilidad, tipo y valor por defecto); si no, es un error fatal.

Usar varios traits

Enumera varios traits en una sentencia use, separados por comas, o escribe varias líneas use. La clase recibe los miembros de todos ellos:

Resolver conflictos con insteadof y as

Cuando dos traits definen un método con el mismo nombre, PHP se niega a adivinar y se detiene con un error fatal. Dentro de un bloque use, insteadof elige el ganador y as conserva el método perdedor con otro nombre:

Con un simple use EnglishGreeting, FrenchGreeting; y sin bloque, el script se detiene antes de imprimir nada:

PHP Fatal error:  Trait method FrenchGreeting::greet has not been applied as Host::greet, because of collision with EnglishGreeting::greet in /home/index.php on line 12

as también puede cambiar la visibilidad. use Loggable { log as protected; } hace protegido el log() del trait solo en esta clase, y use Loggable { log as private writeLog; } añade un alias privado.

Miembros abstractos y estáticos en un trait

Un trait puede exigir algo a la clase que lo usa declarando un método abstracto. También puede tener métodos estáticos y propiedades estáticas. Cada clase que usa el trait recibe su propia copia de una propiedad estática:

Car y Bike cuentan por separado, porque la propiedad estática se copia en cada clase. Es distinto de una propiedad estática declarada en una clase padre compartida, que comparten todas las hijas; la página de static muestra ese caso.

Qué método gana: clase, trait o padre

Si el mismo nombre de método existe en la propia clase, en un trait que usa y en su clase padre, PHP elige en este orden: el método propio de la clase, luego el del trait y luego el del padre. Dentro del trait, parent:: sigue llegando a la versión del padre:

Un trait no es un tipo

instanceof nunca coincide con un trait, y un parámetro con el nombre de un trait como tipo rechaza cualquier objeto con un TypeError. Para preguntar "¿esta clase tiene ese comportamiento?", combina el trait con una interfaz, o inspecciona la clase con class_uses():

El patrón de arriba es el habitual: la interfaz Exportable es lo que usan las funciones como declaración de tipo y lo que comprueba instanceof, y el trait ExportsProperties es una implementación lista que las clases pueden usar o sustituir. Un trait por sí solo es código compartido sin contrato; cuando te descubras comprobando class_uses() en la lógica de negocio, el trait necesitaba una interfaz al lado. Para código compartido por una familia de clases relacionadas, una clase abstracta puede ser la opción más sencilla.

Preguntas frecuentes

¿Qué es un trait en PHP?

Un trait es un grupo con nombre de métodos (y propiedades) declarado con trait Name { } que una clase incorpora con use Name;. PHP copia los miembros del trait en la clase, así que se comportan como si los hubieras escrito ahí. Es una forma de compartir código entre clases que no tienen un padre común.

¿Una clase de PHP puede usar varios traits?

Sí: use Timestamps, SoftDeletes; o varias líneas use. Si dos traits definen un método con el mismo nombre, debes elegir uno con insteadof, por ejemplo use A, B { A::hello insteadof B; }, y puedes conservar el otro con un nombre nuevo mediante B::hello as helloB;.

¿Cuál es la diferencia entre un trait y una interfaz en PHP?

Una interfaz es un tipo sin código: dice qué métodos debe tener una clase, y instanceof funciona con ella. Un trait es código sin tipo: pega cuerpos de métodos en una clase, pero $obj instanceof SomeTrait siempre es falso. Suelen combinarse: una interfaz para el tipo y un trait con una implementación por defecto.

¿Un trait puede tener constructor o métodos abstractos?

Sí a las dos cosas. Un trait puede declarar abstract public function name(): string; para exigir que la clase que lo usa aporte ese método, y puede definir __construct, aunque un constructor en un trait choca fácilmente con el de la propia clase y rara vez es buena idea.

¿Qué método gana si una clase y un trait definen el mismo método?

El método propio de la clase gana sobre el del trait, y el del trait gana sobre un método heredado de la clase padre. Así que el orden es: clase, luego trait, luego padre.

Ilustración de los lenguajes de programación de Coddy

Aprende a programar con Coddy

COMENZAR