Traits
Esta aula apresenta traits em PHP, um mecanismo de reuso horizontal de código que resolve limitações da herança simples. Aborda declaração, uso em classes, resolução de conflitos e boas práticas, com exemplos práticos e exercícios.
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:00Quando 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 BAlé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
- PHP Manual: Traits
- PHP Manual: Resolução de conflitos
- PHP-FIG PSRs (boas práticas)
- Stack Overflow: Exemplos reais de traits
- PHP Manual: Métodos abstratos em traits
Exercícios
- Crie um trait chamado
Identifiableque forneça um métodogetId()que retorna um UUID gerado aleatoriamente. Em seguida, crie uma classeProductque use esse trait e teste a saída. - 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?
- Crie uma classe
FileLoggerque usa um traitLogger(que definelog($msg)) e sobrescreva o métodologpara escrever em um arquivo. Mostre o código. - Utilizando os traits do exemplo de conflito (A e B), crie uma classe que use ambos e faça com que o método
sayHellode B seja acessível comogreet. Escreva o código. - 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.
trait Identifiable {
public function getId(): string {
return bin2hex(random_bytes(16));
}
}
class Product {
use Identifiable;
}
$product = new Product();
echo $product->getId();insteadof ou as.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');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