CodeBlogLogo CodeBlog

Como e Quando usar Traits no PHP?

Como e Quando usar Traits no PHP?

Traits são reuso de código, que pode nos auxiliar nos casos de "copiar e colar" determinados trechos de códigos em diferentes classes.

177
Allain Estevam

Na herança as implementações e as especificações descem na "árvore":

Como e Quando usar Traits no PHP?

Representando isso em código, temos:

class Animal
{
   // TODO
}

class Gato extends Animal
{
   // TODO
}

class Coelho extends Animal
{
   // TODO
}

class Cachorro extends Animal
{
   // TODO
}

Herança é normalmente utilizada para se atingir o reuso de código. Mas, em essência, ela não é sobre reuso. É sobre definir uma hierarquia estrita de tipo. Reuso é a consequência de se usar herança. Quando se pensa em herança apenas com o objetivo de reuso, tendemos a cometer algumas distorções conceituais.

Traits

Traits, por outro lado, são pura e simplesmente para reuso de código, que pode nos auxiliar nos casos em que fatalmente precisamos cair no comportamento de "copiar e colar" determinados trechos de códigos em diferentes classes.

 Alguns autores denominam que Traits são um mecanismo de “herança horizontal”, que seria o contrário da "herança vertical" (a herança clássica, que envolve hierarquia e tipo, comentado anteriormente). Mas, na realidade, se levarmos para um entendimento mais estrito, Traits não possuem nenhuma relação com herança, ademais, elas não se tornam parte e tipo da hierarquia da classe. Entender Traits como "herança horizontal" facilita o entendimento (não é pecado), mas não é conceitualmente fiel.

Então, na prática, Traits podem ser úteis para que compartilhemos determinadas funcionalidades (códigos) entre classes que não são relacionadas (de hierarquias/tipos diferentes).

Supondo que estamos trabalhando com duas classes que precisam de um método slug() para a montagem de URL a partir de um atributo nome:

 
class Curso
{
    public function slug(): string
    {
        return strtolower(
            preg_replace('/[^A-Za-z0-9-]+/', '-', $this->nome)
        );
    }
}

class Formacao
{
    public function slug(): string
    {
        return strtolower(
            preg_replace('/[^A-Za-z0-9-]+/', '-', $this->nome)
        );
    }
}

A gente já consegue identificar logo de cara o problema da duplicação de código. O método slug() é o mesmo nas duas classes.

A primeira solução imaginada quando se dá muita importância para o reuso de código oferecido pela herança, seria criar uma classe base, por exemplo:

class BaseClassWithSlug
{
    public function slug(): string
    {
        return strtolower(
            preg_replace('/[^A-Za-z0-9-]+/', '-', $this->nome)
        );
    }
}

class Curso extends BaseClassWithSlug
{
    //
}

class Formacao extends BaseClassWithSlug
{
    //
}

Você já deve ter notado que isso não é uma boa ideia. Resolvemos o problema da duplicação, mas criamos outro: herança só faz sentido quando se tem a necessidade de representar um tipo. Se a nossa classe não é do tipo da outra, herança não deve ser utilizada. Quando estendemos uma classe, não herdamos apenas a sua implementação e sua especificação, herdamos também o seu tipo. Uma classe herdada passa a ser do tipo da outra e, a partir dela, novos sub-tipos podem ser compostos.

Essa é uma opção que a grosso modo funciona, mas que nos leva a muitas inconsistências, mais que isso, pode nos levar a um ciclo vicioso de sempre criar uma classe base para ser estendida no intuito de evitar duplicação de código. Composição também tem um grande mecanismo de reuso e deve ser considerado preferencialmente em detrimento à herança (para grande parte dos casos).

O que precisamos nesse caso é uma forma de extrair essa funcionalidade e importar nas classes que precisam dela. É aí que entra um possível caso de uso de Trait:

trait SlugNome
{
    public function slug(): string
    {
        return strtolower(
            preg_replace('/[^A-Za-z0-9-]+/', '-', $this->nome)
        );
    }
}

class Curso
{
    use SlugNome;
}

class Formacao
{
    use SlugNome;
}

Resolvemos de uma forma um pouco mais elegante, sem precisar definir um tipo genérico e fazê-lo ser herdado. Uma Trait é importada para uma classe usando a sintaxe use. Mais de uma Trait pode ser importada a partir da mesma declaração use separando os nomes por vírgulas ou através de mais de uma declaração use. Instanciando tais objetos você conseguirá acessar o método slug().

As opções abaixo de importação são equivalentes: 

 
class TestClass
{
    use TraitOne, TraitTwo;
}

// Ou

class TestClass
{
    use TraitOne;
    use TraitTwo;
}

Mas nem tudo são flores. Quando uma classe passa a usar uma Trait, você precisa garantir que tudo o que for importado vai funcionar corretamente. Por exemplo, a nossa Trait precisa de um atributo nome para funcionar. Uma possível solução para evitar bugs ao utilizá-la em outras classes é validar se o atributo nome existe, caso contrário, lançar uma exceção:

 
trait SlugNome
{
    public function slug(): string
    {
        if ( ! property_exists($this, 'nome')) { 
            throw new RuntimeException('Faltando o atributo nome para o retorno do slug.');
        }

        return strtolower(
            preg_replace('/[^A-Za-z0-9-]+/', '-', $this->nome)
        );
    }
}

class Curso
{
    use SlugNome;
}

class Formacao
{
    use SlugNome;
}

Ou, claro, talvez você tenha bons testes para garantir que todas as classes que importem essa Trait funcionem corretamente.

Uma classe pode importar quantas Traits precisar e as Traits possuem acesso aos membros privados da classe, o que pode ser bom ou ruim, dependendo do ponto de vista de quanto a Trait interage com ela.

Concluindo

Frameworks como Symfony e Laravel, principalmente esse último, fazem um uso bem relevante de Traits, com destaque para a parte que toca testes unitários.

O Laravel, além do caso de uso padrão, tem outro para as Traits, que é o de organizar melhor o código dos seus componentes. Por exemplo, no componente Http tem um namespace chamado Concerns onde o framework extraiu para Traits comportamentos que estavam todos embutidos na classe Request, pois estavam tornando-a muito grande e de difícil manutenção. Esse tipo de uso (com o único intuito de organizar melhor o código) deve ser a exceção e não a regra em nossos projetos, senão caímos na “armadilha” de ver as Traits como solução para outro problema: classes “gordas” demais, com muitas responsabilidades que poderiam ser melhor decompostas. No final, é sempre uma questão de bom senso.

Como você deve ter percebido, o objetivo desse artigo não foi o de masterizar o uso de Traits, todas as sintaxes possíveis e tudo mais. Outros detalhes podem ser vistos na própria documentação oficial. O que fizemos aqui foi uma reflexão do que normalmente acontece no desenvolvimento e sobre como Traits podem nos ajudar em alguns casos. Lembrando que nada é solução completa e perfeita para tudo. A comunicação entre objetos, composição, padrões, herança etc, é a integração de todas essas coisas no momento “adequado” que faz a “mágica” de uma aplicação orientada a objetos acontecer.

Até a próxima!

COMENTÁRIOS

Posts Relacionados!

Hoje venho trazer uma forma simples de gerar Hash com PHP. Muitas vezes precisamos de funções...