Leccion 34 de 75 10 min de lectura

Traits

Los traits permiten reutilizar codigo en multiples clases sin usar herencia. Son como "copiar y pegar" inteligente de metodos y propiedades entre clases no relacionadas.

¿Que es un trait?

Un trait es un conjunto de metodos que puedes incluir en cualquier clase. Soluciona el problema de que PHP solo permite herencia simple:

PHP
<?php
declare(strict_types=1);

trait Saludable
{
    public function saludar(): string
    {
        return "Hola, soy {$this->nombre}";
    }
}

class Persona
{
    use Saludable;

    private string $nombre;

    public function __construct(string $nombre)
    {
        $this->nombre = $nombre;
    }
}

class Robot
{
    use Saludable;

    private string $nombre;

    public function __construct(string $nombre)
    {
        $this->nombre = $nombre;
    }
}

$persona = new Persona('Ana');
$robot = new Robot('R2D2');

echo $persona->saludar(); // Hola, soy Ana
echo $robot->saludar();   // Hola, soy R2D2

Usar multiples traits

Una clase puede usar varios traits, combinando funcionalidades:

PHP
<?php
declare(strict_types=1);

trait Timestamps
{
    private string $creadoEn;
    private string $actualizadoEn;

    public function setTimestamps(): void
    {
        $ahora = date('Y-m-d H:i:s');
        $this->creadoEn = $this->creadoEn ?? $ahora;
        $this->actualizadoEn = $ahora;
    }

    public function getCreadoEn(): string
    {
        return $this->creadoEn;
    }
}

trait SoftDelete
{
    private ?string $eliminadoEn = null;

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

    public function estaEliminado(): bool
    {
        return $this->eliminadoEn !== null;
    }
}

class Articulo
{
    use Timestamps, SoftDelete;

    private string $titulo;

    public function __construct(string $titulo)
    {
        $this->titulo = $titulo;
        $this->setTimestamps();
    }
}

$articulo = new Articulo('Mi primer articulo');
echo $articulo->getCreadoEn(); // 2024-01-15 10:30:00

$articulo->eliminar();
var_dump($articulo->estaEliminado()); // true
Traits vs Herencia

Los traits comparten funcionalidad "horizontal" entre clases no relacionadas. La herencia modela relaciones "es un/una" verticales. Usa traits para capacidades compartidas, no para jerarquias.

Traits con propiedades

PHP
<?php
declare(strict_types=1);

trait Identificable
{
    private string $id;

    public function generarId(): void
    {
        $this->id = uniqid('', true);
    }

    public function getId(): string
    {
        return $this->id;
    }
}

class Usuario
{
    use Identificable;

    private string $nombre;

    public function __construct(string $nombre)
    {
        $this->nombre = $nombre;
        $this->generarId();
    }
}

class Producto
{
    use Identificable;

    private string $nombre;

    public function __construct(string $nombre)
    {
        $this->nombre = $nombre;
        $this->generarId();
    }
}

$usuario = new Usuario('Ana');
$producto = new Producto('Laptop');

echo $usuario->getId();  // 65a1b2c3d4e5f6.12345678
echo $producto->getId(); // 65a1b2c3d4e5f7.87654321

Resolver conflictos entre traits

Si dos traits tienen metodos con el mismo nombre, debes resolver el conflicto explicitamente:

PHP
<?php
declare(strict_types=1);

trait A
{
    public function saludar(): string
    {
        return 'Hola desde A';
    }
}

trait B
{
    public function saludar(): string
    {
        return 'Hola desde B';
    }
}

class MiClase
{
    use A, B {
        A::saludar insteadof B; // Usar el de A
        B::saludar as saludarB; // El de B con otro nombre
    }
}

$obj = new MiClase();
echo $obj->saludar();  // Hola desde A
echo $obj->saludarB(); // Hola desde B

Ejemplo practico: Sistema de logging

PHP
<?php
declare(strict_types=1);

trait Loggeable
{
    private array $logs = [];

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

    public function getLogs(): array
    {
        return $this->logs;
    }
}

class Pedido
{
    use Loggeable;

    private string $id;
    private string $estado = 'pendiente';

    public function __construct(string $id)
    {
        $this->id = $id;
        $this->log("Pedido $id creado");
    }

    public function procesar(): void
    {
        $this->estado = 'procesando';
        $this->log("Pedido {$this->id} en proceso");
    }

    public function completar(): void
    {
        $this->estado = 'completado';
        $this->log("Pedido {$this->id} completado");
    }
}

$pedido = new Pedido('P001');
$pedido->procesar();
$pedido->completar();

print_r($pedido->getLogs());
/*
[
    ['fecha' => '2024-01-15 10:30:00', 'mensaje' => 'Pedido P001 creado'],
    ['fecha' => '2024-01-15 10:30:01', 'mensaje' => 'Pedido P001 en proceso'],
    ['fecha' => '2024-01-15 10:30:02', 'mensaje' => 'Pedido P001 completado'],
]
*/

Ejercicios

Ejercicio 1: Trait Serializable

Crea un trait Serializable con metodos toJson(): string y toArray(): array. Usalo en clases Usuario y Producto para serializar sus propiedades.

Ver solucion
PHP
<?php
declare(strict_types=1);

trait Serializable
{
    public function toArray(): array
    {
        return get_object_vars($this);
    }

    public function toJson(): string
    {
        return json_encode($this->toArray(), JSON_PRETTY_PRINT);
    }
}

class Usuario
{
    use Serializable;

    public string $nombre;
    public string $email;

    public function __construct(string $nombre, string $email)
    {
        $this->nombre = $nombre;
        $this->email = $email;
    }
}

class Producto
{
    use Serializable;

    public string $nombre;
    public float $precio;

    public function __construct(string $nombre, float $precio)
    {
        $this->nombre = $nombre;
        $this->precio = $precio;
    }
}

$usuario = new Usuario('Ana', 'ana@email.com');
$producto = new Producto('Laptop', 999.99);

echo $usuario->toJson();
// {"nombre":"Ana","email":"ana@email.com"}

print_r($producto->toArray());
// ['nombre' => 'Laptop', 'precio' => 999.99]

Ejercicio 2: Trait Validable

Crea un trait con metodos validarEmail(string $email): bool y validarTelefono(string $tel): bool. Usalo en una clase Contacto para validar sus datos.

Ver solucion
PHP
<?php
declare(strict_types=1);

trait Validable
{
    public function validarEmail(string $email): bool
    {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    public function validarTelefono(string $tel): bool
    {
        // Solo digitos, minimo 9
        $soloDigitos = preg_replace('/[^0-9]/', '', $tel);
        return strlen($soloDigitos) >= 9;
    }
}

class Contacto
{
    use Validable;

    private string $nombre;
    private string $email;
    private string $telefono;

    public function __construct(string $nombre, string $email, string $telefono)
    {
        $this->nombre = $nombre;

        if (!$this->validarEmail($email)) {
            throw new InvalidArgumentException('Email invalido');
        }
        $this->email = $email;

        if (!$this->validarTelefono($telefono)) {
            throw new InvalidArgumentException('Telefono invalido');
        }
        $this->telefono = $telefono;
    }

    public function getInfo(): string
    {
        return "{$this->nombre} - {$this->email} - {$this->telefono}";
    }
}

$contacto = new Contacto('Ana Garcia', 'ana@email.com', '+34 666 777 888');
echo $contacto->getInfo(); // Ana Garcia - ana@email.com - +34 666 777 888

Ejercicio 3: Traits combinados

Crea traits Cacheable (con metodos get/set cache) y Configurable (con metodos get/set config). Combinalos en una clase Aplicacion.

Ver solucion
PHP
<?php
declare(strict_types=1);

trait Cacheable
{
    private array $cache = [];

    public function setCache(string $key, mixed $value): void
    {
        $this->cache[$key] = $value;
    }

    public function getCache(string $key): mixed
    {
        return $this->cache[$key] ?? null;
    }

    public function hasCache(string $key): bool
    {
        return isset($this->cache[$key]);
    }
}

trait Configurable
{
    private array $config = [];

    public function setConfig(string $key, mixed $value): void
    {
        $this->config[$key] = $value;
    }

    public function getConfig(string $key): mixed
    {
        return $this->config[$key] ?? null;
    }
}

class Aplicacion
{
    use Cacheable, Configurable;

    private string $nombre;

    public function __construct(string $nombre)
    {
        $this->nombre = $nombre;
    }

    public function iniciar(): void
    {
        echo "Iniciando {$this->nombre}...\n";
    }
}

$app = new Aplicacion('MiApp');
$app->setConfig('debug', true);
$app->setConfig('version', '1.0.0');
$app->setCache('usuarios', ['Ana', 'Luis']);

echo $app->getConfig('version'); // 1.0.0
print_r($app->getCache('usuarios')); // ['Ana', 'Luis']

¿Te está gustando el curso?

Tenemos cursos premium con proyectos reales y soporte personalizado.

Descubrir cursos premium