Traits são um mecanismo de reuso de código em PHP que permite compartilhar métodos entre classes independentes, superando a limitação da herança simples. Eles funcionam como 'pedaços' de comportamento que podem ser incorporados em classes, promovendo a composição em vez de herança vertical.

Nesta aula, veremos como declarar e usar traits, resolver conflitos quando múltiplos traits definem o mesmo método, e discutiremos quando é apropriado utilizar esse recurso.

Reuso horizontal de código

Em PHP, uma classe só pode estender uma única classe pai (herança simples). Traits permitem o reuso horizontal: você pode compor uma classe com vários traits, cada um fornecendo um conjunto de métodos e propriedades. Isso evita duplicação de código e permite que comportamentos comuns sejam compartilhados entre classes não relacionadas hierarquicamente.

Um trait é declarado com a palavra-chave trait, seguido por um bloco que pode conter métodos (abstratos ou concretos), propriedades e até constantes. Traits não podem ser instanciados diretamente; eles são 'usados' por classes.

trait Logger {
    public function log(string $message): void {
        echo "[LOG]: $message\n";
    }
}

trait Timestamp {
    public function getTimestamp(): string {
        return date('Y-m-d H:i:s');
    }
}

No exemplo acima, Logger e Timestamp são traits que podem ser injetados em qualquer classe. Isso promove reuso sem acoplamento vertical.

use em classes

Para utilizar um trait em uma classe, usamos a palavra-chave use dentro do corpo da classe. A classe pode usar múltiplos traits separados por vírgula.

class User {
    use Logger, Timestamp;
    
    private string $name;
    
    public function __construct(string $name) {
        $this->name = $name;
        $this->log("User $name created at " . $this->getTimestamp());
    }
}

$user = new User('Alice');
// Saída: [LOG]: User Alice created at 2025-03-28 12:00:00

Quando um trait é usado, seus métodos são incorporados à classe como se tivessem sido definidos nela. A classe pode sobrescrever métodos do trait, se necessário, definindo um método com o mesmo nome. A precedência é: método da classe > método do trait > método da classe pai.

Conflitos

Se dois traits usados por uma classe definirem um método com o mesmo nome, ocorre um conflito. O PHP exige que a classe resolva explicitamente esse conflito, usando os operadores insteadof e as.

  • insteadof: escolhe qual método de qual trait será usado, ignorando os outros.
  • as: permite renomear um método herdado de um trait para evitar conflito ou criar um alias.
trait A {
    public function sayHello(): void {
        echo "Hello from A\n";
    }
}

trait B {
    public function sayHello(): void {
        echo "Hello from B\n";
    }
}

class MyClass {
    use A, B {
        // Usar o método sayHello de A em vez de B
        A::sayHello insteadof B;
        // Mas também quero ter acesso ao método de B com um alias
        B::sayHello as sayHelloFromB;
    }
}

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

Além disso, traits podem exigir que a classe que os use implemente certos métodos abstratos, garantindo um contrato mínimo.

Quando usar

Traits são úteis quando você precisa compartilhar funcionalidades entre classes que não compartilham uma hierarquia comum. Exemplos comuns: logging, cache, serialização, manipulação de datas. No entanto, devem ser usados com moderação, pois o uso excessivo pode levar a código difícil de rastrear (problema do 'trait hell').

Boas práticas:

  • Prefira composição (injeção de dependência) sobre traits para comportamentos que podem variar em tempo de execução.
  • Mantenha traits pequenos e coesos, com responsabilidade única.
  • Evite traits que dependam de propriedades privadas da classe; prefira métodos protegidos ou públicos.
  • Documente claramente quais traits são usados e quais métodos eles fornecem.

Em resumo, traits são uma ferramenta poderosa para reuso horizontal, mas devem ser usados com discernimento para manter a clareza e a manutenibilidade do código.

Boas práticas e observações finais

Além das recomendações acima, lembre-se de que traits podem ter propriedades, mas cuidado com conflitos de nomes. Use namespaces para organizar traits. Em versões recentes do PHP (8.0+), traits podem definir métodos abstratos privados, aumentando a flexibilidade. Sempre avalie se a funcionalidade realmente precisa ser compartilhada horizontalmente ou se poderia ser resolvida com herança ou composição.

Referências

Exercícios

  1. Crie um trait chamado Identifiable que forneça um método getId() que retorna um UUID gerado aleatoriamente. Em seguida, crie uma classe Product que use esse trait e teste a saída.
  2. ✓ Resposta:
    trait Identifiable {
        public function getId(): string {
            return bin2hex(random_bytes(16));
        }
    }
    
    class Product {
        use Identifiable;
    }
    
    $product = new Product();
    echo $product->getId();
  3. Explique o que acontece se dois traits usados por uma classe definirem um método com o mesmo nome e a classe não resolver o conflito. Qual erro é gerado?
  4. ✓ Resposta: O PHP lançará um erro fatal (Fatal error) informando que houve um conflito de métodos e que é necessário resolvê-lo usando insteadof ou as.
  5. Crie uma classe FileLogger que usa um trait Logger (que define log($msg)) e sobrescreva o método log para escrever em um arquivo. Mostre o código.
  6. ✓ Resposta:
    trait Logger {
        public function log(string $msg): void {
            echo $msg;
        }
    }
    
    class FileLogger {
        use Logger;
        
        public function log(string $msg): void {
            file_put_contents('log.txt', $msg . PHP_EOL, FILE_APPEND);
        }
    }
    
    $logger = new FileLogger();
    $logger->log('Teste');
  7. Utilizando os traits do exemplo de conflito (A e B), crie uma classe que use ambos e faça com que o método sayHello de B seja acessível como greet. Escreva o código.
  8. ✓ Resposta:
    trait A {
        public function sayHello(): void {
            echo "Hello from A\n";
        }
    }
    
    trait B {
        public function sayHello(): void {
            echo "Hello from B\n";
        }
    }
    
    class MyClass {
        use A, B {
            A::sayHello insteadof B;
            B::sayHello as greet;
        }
    }
    
    $obj = new MyClass();
    $obj->sayHello(); // Hello from A
    $obj->greet();    // Hello from B
  9. Discuta em um parágrafo por que traits podem ser preferíveis à herança para compartilhar comportamentos como logging entre classes que não têm relação de 'é um'. Cite uma desvantagem.
  10. ✓ Resposta: Traits permitem reuso horizontal sem forçar uma hierarquia, o que é ideal para funcionalidades ortogonais como logging. Uma desvantagem é que o uso excessivo pode tornar o código menos intuitivo, pois a origem dos métodos fica espalhada em vários traits, dificultando a rastreabilidade.