Curso de PHP

Traits en PHP: reutilizar código entre clases

Por Víctor Peña · Publicado el

Traits en PHP

Hola, ¿cómo están? Continuando con el curso de PHP, hoy veremos los traits: el mecanismo para compartir código entre clases que no están emparentadas.

¡Empecemos!

El problema que resuelven

Ya sabemos dos cosas de las lecciones anteriores:

  • Una clase solo puede extender una clase.
  • Una interfaz define métodos pero no aporta código.

Entonces, ¿qué pasa si Usuario, Producto y Factura necesitan todas un mismo comportamiento —por ejemplo, registrar cuándo se modificaron— pero no tienen ninguna relación entre sí y ya extienden clases distintas?

Copiar y pegar el mismo método tres veces. O usar un trait.

Qué es un trait

Un trait es un bloque de métodos y propiedades reutilizable que se inserta dentro de una clase. No es una clase: no se puede instanciar ni extender.

<?php
trait RegistraTiempo
{
    private ?string $modificadoEn = null;

    public function marcarModificado(): void
    {
        $this->modificadoEn = date('Y-m-d H:i:s');
    }

    public function ultimaModificacion(): ?string
    {
        return $this->modificadoEn;
    }
}

Usar un trait

Se incorpora con la palabra use dentro de la clase:

<?php
class Usuario
{
    use RegistraTiempo;

    public function __construct(public string $nombre) {}
}

class Producto
{
    use RegistraTiempo;

    public function __construct(public string $titulo) {}
}

$u = new Usuario("Víctor");
$u->marcarModificado();
echo $u->ultimaModificacion();

Ojo con esto: el use de un trait va dentro de la clase y no tiene nada que ver con el use de importar namespaces, que va arriba del archivo. Se escriben igual y significan cosas distintas.

Varios traits en la misma clase

<?php
trait RegistraTiempo { /* ... */ }
trait Exportable { /* ... */ }
trait Validable { /* ... */ }

class Factura
{
    use RegistraTiempo, Exportable, Validable;
}

Aquí está la diferencia con la herencia: puedes componer una clase con tantos traits como necesites.

Resolver conflictos

Si dos traits definen un método con el mismo nombre, PHP no adivina: falla. Hay que decidir explícitamente con insteadof:

<?php
trait Archivo
{
    public function guardar(): string
    {
        return "Guardado en archivo";
    }
}

trait BaseDeDatos
{
    public function guardar(): string
    {
        return "Guardado en base de datos";
    }
}

class Documento
{
    use Archivo, BaseDeDatos {
        BaseDeDatos::guardar insteadof Archivo;
        Archivo::guardar as guardarEnArchivo;
    }
}

$doc = new Documento();
echo $doc->guardar();            // Guardado en base de datos
echo $doc->guardarEnArchivo();   // Guardado en archivo
  • insteadof elige cuál gana.
  • as conserva el otro bajo un alias.

También puedes usar as para cambiar la visibilidad de un método:

<?php
class Documento
{
    use Archivo {
        guardar as protected guardarInterno;
    }
}

El orden de precedencia

Cuando un mismo método existe en varios lugares, PHP aplica este orden:

  1. El método de la propia clase gana siempre.
  2. Si no está, el del trait.
  3. Si tampoco, el heredado de la clase padre.
<?php
class Base
{
    public function saludar(): string { return "Desde el padre"; }
}

trait Saludo
{
    public function saludar(): string { return "Desde el trait"; }
}

class Hijo extends Base
{
    use Saludo;

    public function saludar(): string { return "Desde la clase"; }
}

echo (new Hijo())->saludar();   // Desde la clase

Si borras el método de Hijo, gana el trait. Si borras también el trait, gana el padre.

Traits con métodos abstractos

Un trait puede exigir que la clase implemente algo:

<?php
trait Describible
{
    abstract public function obtenerNombre(): string;

    public function describir(): string
    {
        return "Elemento: " . $this->obtenerNombre();
    }
}

class Producto
{
    use Describible;

    public function __construct(private string $titulo) {}

    public function obtenerNombre(): string
    {
        return $this->titulo;
    }
}

Es un patrón muy útil: el trait aporta la lógica común y delega en la clase la parte que solo ella conoce.

Traits con propiedades estáticas y constructores

Un trait puede tener propiedades estáticas, y cada clase que lo use tendrá su propia copia:

<?php
trait Contador
{
    private static int $instancias = 0;

    public static function contar(): int
    {
        return self::$instancias;
    }
}

class A { use Contador; }
class B { use Contador; }

A::contar() y B::contar() llevan cuentas independientes.

Trait, interfaz o clase abstracta

Los tres sirven para compartir, pero cosas distintas:

Aporta código Define contrato Cuántos por clase
Interfaz No Varias
Clase abstracta Una
Trait No Varias

La combinación que verás en código profesional es interfaz + trait: la interfaz declara el contrato para que el tipado funcione, y el trait aporta la implementación por defecto.

<?php
interface Exportable
{
    public function exportar(): string;
}

trait ExportaComoJson
{
    public function exportar(): string
    {
        return json_encode(get_object_vars($this));
    }
}

class Producto implements Exportable
{
    use ExportaComoJson;

    public function __construct(
        public string $nombre,
        public float $precio,
    ) {}
}

echo (new Producto("Teclado", 120))->exportar();

Producto cumple el contrato —así que puedes tiparlo como Exportable— sin haber escrito el método.

Cuándo evitarlos

Los traits tienen mala fama en algunos círculos, y no sin motivo. Los problemas aparecen cuando se abusa:

  • Un trait que necesita propiedades que no declara. Si el trait usa $this->conexion esperando que la clase la tenga, has creado una dependencia invisible que nadie puede ver leyendo la clase.
  • Demasiados traits en una clase. Con seis traits, saber de dónde sale cada método requiere abrir seis archivos.
  • Traits que guardan estado propio. Complican mucho el seguimiento.

La regla práctica: un trait debería ser un comportamiento pequeño, autocontenido y sin sorpresas. Si necesita saber cosas de la clase, declara esos requisitos como métodos abstractos.

Y antes de crear uno, pregúntate si no bastaría con composición: que la clase tenga una propiedad con un objeto que haga ese trabajo. Suele ser más claro.

Un ejemplo práctico

<?php
declare(strict_types=1);

trait RegistraActividad
{
    private array $historial = [];

    protected function registrar(string $accion): void
    {
        $this->historial[] = [
            'accion' => $accion,
            'fecha' => date('Y-m-d H:i:s'),
        ];
    }

    public function verHistorial(): array
    {
        return $this->historial;
    }
}

trait Validable
{
    abstract protected function reglas(): array;

    public function esValido(): bool
    {
        foreach ($this->reglas() as $campo => $regla) {
            if (!$regla($this->$campo ?? null)) {
                return false;
            }
        }

        return true;
    }
}

class Pedido
{
    use RegistraActividad, Validable;

    public function __construct(
        public string $cliente = '',
        public float $total = 0,
    ) {
        $this->registrar('Pedido creado');
    }

    protected function reglas(): array
    {
        return [
            'cliente' => fn($v) => !empty($v),
            'total' => fn($v) => $v > 0,
        ];
    }
}

$pedido = new Pedido("Ana Torres", 450);
var_dump($pedido->esValido());   // true
print_r($pedido->verHistorial());

Dos comportamientos independientes, combinados en una clase que no hereda de nada.

Errores comunes

  • Confundir el use del trait con el de namespaces. Uno va dentro de la clase, el otro arriba del archivo.
  • No resolver un conflicto de nombres. PHP falla y hay que usar insteadof.
  • Intentar instanciar un trait. No es una clase.
  • Traits que dependen de propiedades no declaradas. Usa métodos abstractos para hacer explícito el requisito.
  • Usar traits donde la composición sería más clara.

Para cerrar

Los traits resuelven un problema concreto: compartir código entre clases sin parentesco, cuando la herencia no es una opción. Son útiles y potentes, pero se vuelven en tu contra si los conviertes en un cajón donde meter cualquier cosa.

Mantenlos pequeños, autocontenidos y explícitos sobre lo que necesitan.

En la siguiente lección veremos las excepciones.

Saludos y éxitos.

Norvic Software

Desarrollamos el software que tu empresa necesita

Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp