Uma trait PHP é um bloco reutilizável de métodos e propriedades. Você a declara com trait Name { ... } e a incorpora em qualquer classe com use Name; dentro do corpo da classe. O PHP copia os membros da trait para a classe, então eles funcionam exatamente como se você os tivesse escrito ali.
User e Robot não têm uma classe pai em comum, mas agora os dois têm hello(). As traits existem para esse caso: classes PHP só podem estender uma classe, e uma trait compartilha código de lado, entre classes que não têm relação.
Traits com propriedades
Uma trait pode trazer propriedades junto com os métodos. Os métodos usam $this como qualquer método da classe, porque, depois que a trait é copiada, eles são métodos da classe:
Uma classe não pode declarar uma propriedade com o mesmo nome de uma propriedade da trait, a menos que as duas declarações sejam idênticas (mesma visibilidade, tipo e valor padrão); caso contrário é um erro fatal.
Usar várias traits
Liste várias traits numa instrução use, separadas por vírgulas, ou escreva várias linhas use. A classe recebe os membros de todas:
Resolver conflitos com insteadof e as
Quando duas traits definem um método com o mesmo nome, o PHP se recusa a adivinhar e para com um erro fatal. Dentro de um bloco use, insteadof escolhe o vencedor, e as mantém o método perdedor com outro nome:
Com um simples use EnglishGreeting, FrenchGreeting; e sem bloco, o script para antes de imprimir qualquer coisa:
PHP Fatal error: Trait method FrenchGreeting::greet has not been applied as Host::greet, because of collision with EnglishGreeting::greet in /home/index.php on line 12
as também pode mudar a visibilidade. use Loggable { log as protected; } torna o log() da trait protegido só nesta classe, e use Loggable { log as private writeLog; } adiciona um alias privado.
Membros abstratos e estáticos numa trait
Uma trait pode exigir algo da classe que a usa declarando um método abstrato. Ela também pode ter métodos e propriedades estáticos. Cada classe que usa a trait recebe a própria cópia de uma propriedade estática:
Car e Bike contam separadamente, porque a propriedade estática é copiada para cada classe. Isso é diferente de uma propriedade estática declarada numa classe pai compartilhada, que todas as filhas dividem; a página sobre static mostra esse caso.
Qual método vence: classe, trait ou classe pai
Se o mesmo nome de método existe na própria classe, numa trait que ela usa e na classe pai, o PHP escolhe nesta ordem: o método da própria classe, depois o da trait, depois o da classe pai. Dentro da trait, parent:: ainda alcança a versão da classe pai:
Uma trait não é um tipo
instanceof nunca bate com uma trait, e um parâmetro tipado com o nome de uma trait rejeita qualquer objeto com um TypeError. Para perguntar "esta classe tem o comportamento", combine a trait com uma interface, ou inspecione a classe com class_uses():
O padrão acima é o comum: a interface Exportable é o que as funções usam no type hint e o que o instanceof verifica, e a trait ExportsProperties é uma implementação pronta que as classes podem usar ou substituir. Uma trait sozinha é código compartilhado sem contrato; quando você se pegar verificando class_uses() na lógica de negócio, a trait precisava de uma interface ao lado. Para código compartilhado por uma família de classes relacionadas, uma classe abstrata pode ser a escolha mais simples.
Perguntas frequentes
O que é uma trait no PHP?
Uma trait é um grupo nomeado de métodos (e propriedades) declarado com trait Name { } que uma classe incorpora com use Name;. O PHP copia os membros da trait para a classe, então eles se comportam como se você os tivesse escrito ali. É um jeito de compartilhar código entre classes que não têm uma classe pai em comum.
Uma classe PHP pode usar várias traits?
Sim: use Timestamps, SoftDeletes; ou várias linhas use. Se duas traits definem um método com o mesmo nome, você precisa escolher um com insteadof, por exemplo use A, B { A::hello insteadof B; }, e pode manter o outro com um novo nome com B::hello as helloB;.
Qual a diferença entre uma trait e uma interface no PHP?
Uma interface é um tipo sem código: diz quais métodos uma classe precisa ter, e instanceof funciona com ela. Uma trait é código sem tipo: cola corpos de métodos numa classe, mas $obj instanceof SomeTrait é sempre falso. Elas costumam ser combinadas: uma interface para o tipo, uma trait com uma implementação padrão.
Uma trait pode ter construtor ou métodos abstratos?
Sim, as duas coisas. Uma trait pode declarar abstract public function name(): string; para exigir que a classe que a usa forneça esse método, e pode definir __construct, embora um construtor numa trait conflite facilmente com o da própria classe e raramente seja uma boa ideia.
Qual método vence se uma classe e uma trait definem o mesmo método?
O método da própria classe vence o da trait, e o método da trait vence um método herdado da classe pai. Então a ordem é: classe, depois trait, depois classe pai.