Traits
Esta aula explora o conceito de traits em PHP, um mecanismo de reuso horizontal de código que permite compartilhar métodos entre classes independentes. Aborda a sintaxe de declaração e uso, resolução de conflitos com o operador insteadof e aliases, e discute cenários apropriados para seu uso. Ao final, o aluno será capaz de implementar traits de forma eficaz e evitar armadilhas comuns.
Traits são um mecanismo de reuso de código em PHP que permite compartilhar métodos entre classes que não pertencem à mesma hierarquia de herança. Eles resolvem o problema de herança única do PHP, possibilitando o reuso horizontal de código sem os problemas de acoplamento da herança múltipla. Nesta aula, vamos aprender como declarar, usar e gerenciar conflitos entre traits.
Diferentemente de classes abstratas ou interfaces, traits fornecem implementações concretas de métodos que podem ser 'inseridos' em classes. Eles são especialmente úteis para comportamentos transversais (cross-cutting concerns) como logs, cache, serialização, etc.
Reuso horizontal de código
Em PHP, uma classe pode herdar de apenas uma classe pai (herança única). Isso limita o reuso de código quando precisamos compartilhar funcionalidades entre classes não relacionadas. Traits oferecem uma alternativa: você pode agrupar um conjunto de métodos em um trait e então 'usá-lo' em várias classes, independentemente da hierarquia.
Exemplo: suponha que temos classes Carro, Bicicleta e Barco. Todas precisam de um método ligar() e desligar(). Em vez de duplicar o código ou forçar uma herança artificial, podemos criar um trait Motor com esses métodos e usá-lo em cada classe.
trait Motor {
public function ligar() {
echo "Motor ligado.\n";
}
public function desligar() {
echo "Motor desligado.\n";
}
}
class Carro {
use Motor;
}
class Bicicleta {
use Motor;
}
class Barco {
use Motor;
}
$carro = new Carro();
$carro->ligar(); // Motor ligado.
$bicicleta = new Bicicleta();
$bicicleta->desligar(); // Motor desligado.Isso promove reuso sem acoplamento rígido. Cada classe mantém sua própria hierarquia, mas ganha os métodos do trait.
use em classes
A palavra-chave use dentro de uma classe importa um ou mais traits. A sintaxe é use NomeDoTrait;. Você pode usar múltiplos traits separando-os por vírgula.
trait A {
public function metodoA() { echo "A\n"; }
}
trait B {
public function metodoB() { echo "B\n"; }
}
class MinhaClasse {
use A, B;
}
$obj = new MinhaClasse();
$obj->metodoA(); // A
$obj->metodoB(); // BTraits também podem usar outros traits (composição de traits). Basta usar a mesma sintaxe dentro do trait:
trait C {
use A, B;
public function metodoC() { echo "C\n"; }
}
class OutraClasse {
use C;
}
$obj2 = new OutraClasse();
$obj2->metodoA(); // A
$obj2->metodoC(); // CConflitos
Quando dois traits fornecem métodos com o mesmo nome, ocorre um conflito fatal. Para resolver, usamos o operador insteadof para escolher qual método usar, e as para criar um alias.
trait A {
public function conflito() { echo "A\n"; }
}
trait B {
public function conflito() { echo "B\n"; }
}
class MinhaClasse {
use A, B {
B::conflito insteadof A; // usa o método de B no lugar de A
A::conflito as conflitoA; // cria um alias para o método de A
}
}
$obj = new MinhaClasse();
$obj->conflito(); // B
$obj->conflitoA(); // ATambém é possível alterar a visibilidade de um método importado usando as com modificador de acesso:
trait A {
public function metodoPublico() { echo "público\n"; }
}
class MinhaClasse {
use A {
metodoPublico as private; // torna o método privado
}
}
$obj = new MinhaClasse();
// $obj->metodoPublico(); // Erro: método privadoQuando usar
Traits são ideais para comportamentos compartilhados entre classes não relacionadas, como logging, cache, validação, formatação, etc. Evite traits quando o comportamento é intrinsicamente ligado à hierarquia da classe (use herança ou interfaces). Também não abuse de traits para criar 'herança múltipla' artificial, pois pode tornar o código difícil de entender.
Boas práticas:
- Mantenha traits pequenos e focados em uma única responsabilidade.
- Documente os métodos do trait e quaisquer dependências (ex: métodos que o trait espera que a classe tenha).
- Prefira interfaces para contratos e traits para implementações reutilizáveis.
- Evite traits que definem propriedades, pois podem causar conflitos de nomes.
- Use traits para código que não muda frequentemente; para comportamentos dinâmicos, considere estratégias como composição.
Referências
- PHP Manual - Traits
- Resolução de conflitos em traits
- Alterando visibilidade de métodos de traits
- PSR-1: Basic Coding Standard (boas práticas)
- Traits vs Interfaces (Stack Overflow)
Exercícios
Crie um trait chamado
Logavelcom um métodolog($mensagem)que imprime a mensagem prefixada com a data atual. Em seguida, use-o em uma classeUsuarioe chame o método.✓ Resposta:trait Logavel { public function log($mensagem) { echo date('Y-m-d H:i:s') . ' - ' . $mensagem . "\n"; } } class Usuario { use Logavel; } $user = new Usuario(); $user->log('Usuário logado.');Declare dois traits:
TraitAcom métodofoo()eTraitBcom métodofoo(). Crie uma classe que use ambos e resolva o conflito para que o métodofoo()chame a implementação deTraitBe crie um alias para o método deTraitAcomofooA().✓ Resposta:trait TraitA { public function foo() { echo "TraitA\n"; } } trait TraitB { public function foo() { echo "TraitB\n"; } } class MinhaClasse { use TraitA, TraitB { TraitB::foo insteadof TraitA; TraitA::foo as fooA; } } $obj = new MinhaClasse(); $obj->foo(); // TraitB $obj->fooA(); // TraitACrie um trait
Singletonque implemente o padrão Singleton (instância única) com método estáticogetInstance(). Use-o em uma classeConfige demonstre que ambas as variáveis apontam para a mesma instância.✓ Resposta:trait Singleton { private static $instance = null; public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } } class Config { use Singleton; private $dados = []; public function set($chave, $valor) { $this->dados[$chave] = $valor; } public function get($chave) { return $this->dados[$chave] ?? null; } } $config1 = Config::getInstance(); $config1->set('db_host', 'localhost'); $config2 = Config::getInstance(); var_dump($config1 === $config2); // true echo $config2->get('db_host'); // localhostDefina um trait
Comparavelcom um método abstratoequals($outro)que deve ser implementado pela classe que o usar. Crie uma classePessoacom propriedadesnomeeidade, implemente o métodoequalspara comparar nome e idade. Teste com duas pessoas.✓ Resposta:trait Comparavel { abstract public function equals($outro): bool; } class Pessoa { use Comparavel; private $nome; private $idade; public function __construct($nome, $idade) { $this->nome = $nome; $this->idade = $idade; } public function equals($outro): bool { if (!$outro instanceof Pessoa) return false; return $this->nome === $outro->nome && $this->idade === $outro->idade; } } $p1 = new Pessoa('João', 30); $p2 = new Pessoa('João', 30); $p3 = new Pessoa('Maria', 25); var_dump($p1->equals($p2)); // true var_dump($p1->equals($p3)); // falseCrie um trait
Timestampableque adiciona métodosgetCreatedAt()egetUpdatedAt()e define propriedades privadas$createdAte$updatedAt. Use-o em uma classePoste inicialize as propriedades no construtor (dica: o trait pode definir um métodoinitializeTimestamps()chamado no construtor).✓ Resposta:trait Timestampable { private $createdAt; private $updatedAt; public function initializeTimestamps() { $this->createdAt = new DateTime(); $this->updatedAt = new DateTime(); } public function getCreatedAt(): DateTime { return $this->createdAt; } public function getUpdatedAt(): DateTime { return $this->updatedAt; } public function updateTimestamp() { $this->updatedAt = new DateTime(); } } class Post { use Timestampable; private $titulo; public function __construct($titulo) { $this->titulo = $titulo; $this->initializeTimestamps(); } public function setTitulo($titulo) { $this->titulo = $titulo; $this->updateTimestamp(); } } $post = new Post('Meu post'); echo $post->getCreatedAt()->format('Y-m-d H:i:s'); $post->setTitulo('Novo título'); echo $post->getUpdatedAt()->format('Y-m-d H:i:s');