Traits: reutilizando código sem herança

1. Introdução aos Traits em PHP

Em PHP, a herança única sempre foi uma limitação significativa. Uma classe pode estender apenas uma classe pai, o que muitas vezes força os desenvolvedores a criar hierarquias complexas ou duplicar código para compartilhar funcionalidades entre classes não relacionadas.

Os Traits surgiram como uma solução elegante para esse problema. Um Trait é um mecanismo de reutilização de código horizontal — você pode "injetar" métodos e propriedades em qualquer classe, independentemente de sua posição na hierarquia de herança.

Diferentemente da herança, onde a classe filha herda todo o comportamento da classe pai, os Traits permitem uma composição granular de funcionalidades. Enquanto as interfaces definem contratos (o que deve ser implementado), os Traits fornecem implementações concretas (como fazer).

2. Sintaxe básica e declaração de Traits

A declaração de um Trait é simples e utiliza a palavra-chave trait. Para usá-lo em uma classe, empregamos use.

<?php

trait Loggable {
    public function log(string $message): void {
        echo "[LOG]: " . $message . PHP_EOL;
    }
}

class User {
    use Loggable;

    public function __construct(private string $name) {}

    public function save(): void {
        // Lógica para salvar o usuário
        $this->log("Usuário {$this->name} salvo com sucesso.");
    }
}

class Product {
    use Loggable;

    public function __construct(private string $title) {}

    public function updatePrice(float $newPrice): void {
        $this->log("Preço do produto {$this->title} atualizado para R$ {$newPrice}.");
    }
}

$user = new User("João");
$user->save(); // [LOG]: Usuário João salvo com sucesso.

$product = new Product("Notebook");
$product->updatePrice(3500.00); // [LOG]: Preço do produto Notebook atualizado para R$ 3500.

Observe como User e Product, que não têm relação de herança entre si, compartilham o método log() graças ao Trait Loggable.

3. Propriedades e métodos em Traits

Traits podem conter tanto propriedades quanto métodos, com diferentes níveis de visibilidade.

<?php

trait Timestampable {
    protected DateTime $createdAt;
    protected DateTime $updatedAt;

    public function initializeTimestamps(): void {
        $this->createdAt = new DateTime();
        $this->updatedAt = new DateTime();
    }

    public function touch(): void {
        $this->updatedAt = new DateTime();
    }

    public function getCreatedAt(): string {
        return $this->createdAt->format('Y-m-d H:i:s');
    }

    abstract public function getUpdatedAt(): string;
}

class Post {
    use Timestampable;

    public function __construct(private string $title) {
        $this->initializeTimestamps();
    }

    public function getUpdatedAt(): string {
        return $this->updatedAt->format('Y-m-d H:i:s');
    }
}

Métodos abstratos em Traits forçam a classe que os utiliza a implementá-los, garantindo que comportamentos específicos sejam definidos onde o Trait é usado.

4. Resolução de conflitos entre Traits

Quando uma classe utiliza múltiplos Traits que possuem métodos com o mesmo nome, ocorre um conflito. PHP oferece duas ferramentas para resolver isso: insteadof e as.

<?php

trait A {
    public function sayHello(): void {
        echo "Hello from Trait A!" . PHP_EOL;
    }
}

trait B {
    public function sayHello(): void {
        echo "Hello from Trait B!" . PHP_EOL;
    }

    public function sayGoodbye(): void {
        echo "Goodbye from Trait B!" . PHP_EOL;
    }
}

class MyClass {
    use A, B {
        B::sayHello insteadof A; // Usa o método de B
        A::sayHello as sayHelloFromA; // Cria um alias para o método de A
    }
}

$obj = new MyClass();
$obj->sayHello(); // Hello from Trait B!
$obj->sayHelloFromA(); // Hello from Trait A!

O operador insteadof resolve o conflito escolhendo qual implementação usar, enquanto as permite criar aliases para acessar métodos que seriam sobrescritos.

5. Composição de múltiplos Traits

Uma classe pode utilizar quantos Traits forem necessários, promovendo uma composição rica de comportamentos.

<?php

trait SoftDeletes {
    protected ?DateTime $deletedAt = null;

    public function delete(): void {
        $this->deletedAt = new DateTime();
        echo "Registro marcado para exclusão." . PHP_EOL;
    }

    public function restore(): void {
        $this->deletedAt = null;
        echo "Registro restaurado." . PHP_EOL;
    }
}

trait Validatable {
    public function validate(array $data): bool {
        // Lógica de validação
        return !empty($data);
    }
}

class Article {
    use Timestampable, SoftDeletes, Validatable;

    public function __construct(private string $title) {
        $this->initializeTimestamps();
    }
}

A ordem de precedência em PHP é: método da classe atual > método do Trait > método da classe pai. Isso significa que a classe pode sobrescrever qualquer método vindo de um Trait.

6. Traits com métodos abstratos e estáticos

Traits podem declarar métodos abstratos que obrigam a classe implementadora a fornecer a lógica, e também podem conter métodos estáticos.

<?php

trait Singleton {
    private static ?self $instance = null;

    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    abstract protected function __construct();
}

class Database {
    use Singleton;

    private function __construct() {
        echo "Conexão com banco estabelecida." . PHP_EOL;
    }

    public function query(string $sql): void {
        echo "Executando: $sql" . PHP_EOL;
    }
}

$db1 = Database::getInstance();
$db2 = Database::getInstance();
var_dump($db1 === $db2); // bool(true)

Métodos estáticos em Traits funcionam como esperado, mas é importante notar que propriedades estáticas em Traits podem ter comportamentos inesperados quando usadas em múltiplas classes, pois cada classe mantém sua própria cópia da propriedade estática.

7. Traits e modificadores de visibilidade

O operador as também permite alterar a visibilidade de métodos importados de um Trait.

<?php

trait SecureOperations {
    public function sensitiveOperation(): void {
        echo "Operação sensível executada." . PHP_EOL;
    }

    protected function internalCheck(): bool {
        return true;
    }
}

class AdminPanel {
    use SecureOperations {
        sensitiveOperation as private; // Torna o método privado
        internalCheck as public checkAccess; // Altera visibilidade e cria alias
    }

    public function execute(): void {
        // $this->sensitiveOperation(); // Erro! Método agora é privado
        if ($this->checkAccess()) {
            echo "Acesso permitido." . PHP_EOL;
        }
    }
}

$panel = new AdminPanel();
$panel->execute(); // Acesso permitido.
// $panel->sensitiveOperation(); // Erro! Método privado

Isso permite um controle refinado sobre o que é exposto publicamente, mantendo o encapsulamento.

8. Casos de uso reais e boas práticas

Casos de uso comuns:

<?php

trait Timestampable {
    // Já definido anteriormente
}

trait SoftDeletes {
    // Já definido anteriormente
}

trait Singleton {
    // Já definido anteriormente
}

trait HasSlug {
    public function generateSlug(string $text): string {
        return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $text)));
    }
}

class Page {
    use Timestampable, SoftDeletes, HasSlug;

    public function __construct(private string $title) {
        $this->initializeTimestamps();
    }

    public function getUrl(): string {
        return "/page/" . $this->generateSlug($this->title);
    }
}

Quando usar Traits:
- Para compartilhar comportamentos comuns entre classes não relacionadas
- Para implementar "interfaces com código" (como Timestampable)
- Para evitar duplicação de código em hierarquias paralelas

Quando evitar Traits:
- Quando a funcionalidade é central para o domínio (prefira herança)
- Quando há dependências ocultas entre Traits
- Quando o Trait se torna muito grande ou complexo

Armadilhas comuns:
- Traits que dependem de propriedades ou métodos que não existem na classe
- Conflitos de nomes não resolvidos
- Acoplamento excessivo entre Traits e classes

Traits são ferramentas poderosas, mas como qualquer ferramenta, devem ser usadas com moderação e clareza. Mantenha seus Traits focados em uma única responsabilidade e documente claramente as dependências esperadas.


Referências