Trait no PHP – Onde e quando usar

Em março de 2012 Foi lançado o PHP 5.4 e com ele veio um recurso chamado trait.
Agora, em 2019, isso não é novo, mas eu vejo esse recurso sendo muito pouco usado e até pouco tempo não tinha muita ideia de como usá-lo, então resolvi escrever esse artigo para demonstrar o uso que faço desse bom recurso.

O que é trait

Trait é uma classe quase como as outras, sendo diferente pelo fato de não poder ser instanciado.
Esse recurso veio para resolver uma limitação que o PHP tem: herança múltipla não é permitida.

Por que simplesmente não implementar “extends A, B”?

O problema da herança múltipla aparece quando duas classes diferentes implementam métodos com mesma assinatura e uma outra classe estende essas duas classes.
A classe A contém o método method, e a classe B também tem o método method.
Aí vem a classe C, que estende A e B:
Qual implementação de method deverá ser usada?

O artigo da Wikipedia acima não diz exatamente isso, mas quer dizer a mesma coisa.
Linguagens que suportam herança múltipla como Python tem seus jeitos de resolver esse problema, mas o PHP implementa os traits, o que eu acho bem legal.

Onde usar trait

Padrões de projeto são legais, responsabilidade única e programar para interfaces. Mas chega um momento que é inevitável a repetição de código.
Dez classes do seu projeto precisam de um objeto logger e essas classes tem duas opções:

  • cada uma implementa getLogger e setLogger
  • Estende uma classe abstrata que tem esses dois métodos implementados

Bom, nem preciso comentar a primeira opção. Escrever 10 vezes

private $logger;

public function getLogger()
{
    return $this->logger;
}

public function setLogger(LoggerInterface $logger)
{
    $this->logger = $logger;

    return $this;
}

É tão tedioso quanto escrever HTML, mesmo se for copiando e colando. 🙂

Quanto a segunda opção, já é um caso a se pensar:

abstract class Loggable
{
    protected $logger;

    public function getLogger()
    {
        return $this->logger;
    }

    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;

        return $this;
    }
}

abstract class MyBaseClass extends Loggable
{
    //...
}

final class MyClass extends MyBaseClass
{
    //...
}

É uma ótima opção, claro… Mas, se em algum lugar nessa cadeia tiver alguma classe que precisa das funcionalidades de MyBaseClass mas não precisa de um logger?
É bom termos em nossas classes somente o que precisamos, sem propriedades ou métodos extras.
Sendo assim, somente estendemos de MyBaseClass quando realmente formos usar alguma de suas funcionalidades.

Como usar trait

Seguindo na linha do logger vamos ver a solução para nossos problemas:

//Um trait tem métodos e propriedades, como qualquer classe
trait Loggable
{
    protected $logger;

    public function getLogger()
    {
        return $this->logger;
    }

    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;

        return $this;
    }
}

Depois de criado esse trait, agora nossas classes podem ser escritas assim:

abstract class MyBaseClass
{
    //... Funcionalidades de MyBaseClass
}

final class MyClass extends MyBaseClass
{
    //Essa classe precisa de um logger, então vamos requerê-lo
    use Loggable;
    //...
}

Assim, só e somente MyClass terá os métodos getLogger e setLogger, pois ela precisa de um logger.

De certa forma, um trait é como copiar e colar; veja:

interface MyInterface
{
    public function method();
}

trait MyTrait
{
    public function method()
    {
        echo "oioi!";
    }
}

class MyClass implements MyInterface
{
    use MyTrait;
}

$obj = new MyClass();
var_dump($obj instanceof MyInterface); //true

Resolvendo conflito de métodos

Bom, não é só porquê usamos traits que os métodos com assinaturas iguais deixaram de existir.
Para resolver isso, temos os operadores insteadof para especificar que eu quero um método ao invés do outro, e as para criar aliases para os métodos.

trait MyTrait
{
    public function method()
    {
        echo "oioi";
    }
}

trait MyOtherTrait
{
    public function method()
    {
        echo "ola";
    }
}

class MyClass
{
    //Digamos que dentro das chaves estão as definições de como os
    //traits usados irão se comportar
    use MyTrait, MyOtherTrait
    {
        //Quando for chamado "method", use o de "MyOtherTrait"
        MyOtherTrait::method insteadof MyTrait;
        //Se quiser acessar o método de MyTrait, chame-o como myMethod
        MyTrait::method as myMethod;
    }
}

$obj = new MyClass();
$obj->method(); //ola
$obj->myMethod(); //oioi

Um outro ponto sobre o operador as é que através dele também é possível mudar a visibilidade de um método:

class MyClass
{
    use MyTrait
    {
        protectedMethod as public publicMethod; //Muda a visibilidade de protected para public, e o nome de "protectedMethod" para "publicMethod"
        //protectedMethod as public; só muda a visibilidade para public
    }
}

Sumário

Usar trait é bom porquê

  • Evita repetição de código
  • Classes não são sobrecarregadas com funcionalidades que nunca serão usadas
  • Evita usar herança para criar uma árvore genealógica

Características de trait

  • Traits são como classes abstratas, mas pode ser usado mais de um
  • Trait é criado com a palavra-chave trait
  • A palavra-chave use é utilizada também para “colar” o corpo do trait na classe
  • Os operadores insteadof (ao invés de) e as são usados para resolver conflitos de nomes de métodos

Fim

Bom, é isso. Qualquer dúvida, sugestão, reclamação ou erro, não pense duas vezes antes de comentar.
Aproveite também para falar como você aplica trait em seus projetos!

Rodapé

O exemplo mostrado nesse post é macivamente aplicado no ZF2, quando uma classe precisa de um EventManager, um Logger ou um adaptador de banco de dados.

Basta implementar a interface XAwareInterface, e ao invés de escrever aquele código chato basta usar XAwareTrait.

Isso, claro, se estiver usando PHP 5.4+, se não… não tem jeito.

Leia também

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *