Traits em PHP – herança horizontal

Antevejo que no futuro, o PHP como uma linguagem iterativa e incremental que é, adicionará ao seu núcleo um recurso muito valioso na categoria orientação à objetos.

Antevejo ele, os Traits. E mais, consigo prever que será algo assim:

<?php
trait Hello {
 public function ola() {
   echo "Olá";
 }
}

class Mundo {
 use Hello;

 public function world() {
   echo "{$this->ola()} trait !";
  }
}

$ola = new Mundo();
$ola->world();
// Olá trait !

Ok, não sou bidu. Realmente os Traits estão na lista do possível-futuro PHP 5.4 e eu, baixei a build de 31/03/2011 para testar.

Traits

O PHP não suporta herença multipla – amém – e por este motivo, às vezes ficamos limitados em algumas decisões de projeto (design). O maior problema neste ponto é que toda herença até agora (PHP 5.3.x) é vertical, ou seja, se eu precisar de um nível de abstração diferente no meio do processo, ou precisarei adicionar a nova abstração e reescrever tudo abaixo dela, ou ainda, terei que duplicar o código pois não conseguirei satisfazer a herença.

É nesse ponto que os Traits aparecem. Diferentemente da herença (vertical), os Traits possibilitam-nos criar herenças horizontais.

Exemplo, dada a necessidade:  haverão Funcionários, Supervisores, Gestores e Diretores no sistema e estes possui em comum o fato de receber salário. (capitalismo). Uma possibilidade seria:

Pessoa > Funcionario
Pessoa > Supervisores
Pessoa > Gestores
Pessoa > Diretores

Tudo perfeito. Porém, chega outra necessidade: Gestores e Diretores tem a capacidade de demitir. Gestores precisam gerenciar cronogramas, assim como os Supervisores fazem.

Pessoa.demitir(pessoa) não faz sentido, pois afetiria todos.
Gestor.demitir(pessoa) e Diretor.demitir(pessoa) atende, mas duplicaria o código.

Pessoa > Supervisor > Gestor
Pessoa > Diretor

Shi, apareceu outro nível hierarquico. Traits auxilia exatamente neste ponto ! Vejamos:

Trait Cronograma
Trait Evil (que contém recursos para demitir uma pessoa)
Pessoa > Funcionario
Pessoa > Supervisor : Cronograma
Pessoa > Gestor : Cronograma : Evil
Pessoa >  Diretor : Evil

Note que o Pessoa > Supervisor : Cronograma – os dois pontos para indicar o Trait são de minha autoria – era o Pessoa > Supervisor antigo. Isso porque as atribuições para gerenciar cronogramas foram passadas para o Trait.

Neste pequeno exemplo, o Trait nos auxiliou com um problema de domínio. Acredito que ele também aplique-se e muito bem para problemas onde temos domínios transversais, pois, nele ficariam as atividades do outro domínio pode o domínio principal teria acesso aos recursos sem quebrar a SRP.

Traits em PHP

Como disse no começo, o futuro-provável PHP 5.4 virá com o Trait. Por enquanto essa implementação funcionaria:

<?php
trait Cronograma {
 private static $ATRASADO = "sempre";

 public function cobrarDe(Pessoa $funcionario) {
   $funcionario->definirPressao(self::$ATRASADO);
   $this->youTube();
 }

 private function youTube() {

 }
}

class Funcionario extends Pessoa {
}

class Supervisor extends Pessoa {
 use Cronograma;
}

$colaborador = new Funcionario();
$controlador = new Supervisor();
$controlador->cobrarDe($colaborador);

Os Traits possuem boas capacidades com métodos de instância e estáticos. Vamos explorar um pouco mais:

<?php
trait Hello {
 public function sayHello() {
   echo "Olá";
 }
}

trait World {
 public function sayWorld() {
   echo "Mundo";
 }
}

class View {
 use Hello, World;
}

$view = new View();
$view->sayHello();
$view->sayWorld();
// exibe: Olá Mundo

Pegando informações da Classe

<?php
trait QuemSou {
 public function eu() {
   return get_class($this);
 }
}

class Foo {
 use QuemSou;
}

$foo = new Foo();
echo $foo->eu();
// exibe: Foo

Usando Variáveis de instância da classe

<?php
trait World {
 public function help() {
   return "{$this->hello} world";
 }
}

class Hello {
 private $hello = "Hello";
 use World;
}

$hello = new Hello();
echo $hello->help();
// exibe: Hello world

Modificadores de acesso dos traits na classe

<?php
trait Hello {
 public function ola() {
   echo "Olá mundo";
 }
}

class Modificadores {
 use Hello { ola as private };
}

$ola = new Modificadores();
$ola->ola();
// Fatal Error....

Modificadores de acesso com alias para nome de método

<?php
trait Hello {
 private function ola() {
   echo "Olá mundo";
 }
}

class Modificadores {
 use Hello { ola as public aloha };
}

$ola = new Modificadores();
$ola->aloha();
// Olá mundo

Há outros vários recursos como métodos abstratos nos Traits, precedência, métodos estáticos, late static bindings, métodos mágicos, alias para nome de método do Trait ao usá-lo na classe. Para Rubistas ou Scalistas (?) as possibilidades são as mesmas dos Mixins e Traits de Ruby e Scala respectivamente.

Fork me on GitHub
9c4b837dbcf39efb1ed3e0617a2487acdelicious


6 comentários para “Traits em PHP – herança horizontal”

  1. Traits, use com moderação. Excelente post Hélio.

  2. Ismael Vacco says:

    Conforme já haviamos conversado, pessoalmente, acho problemático a utilização de Traits, principalmente porque diminui a ortogonalidade do código, ou em outras palavras, aumenta o acoplamento. Traits não é herança multipla porém pode trazer os mesmos problemas q a herança multipla trás. Por exemplo, quando se herda dois ou três métodos iguais, qual método terá validade?
    Quando se pensaram na estrutura de orientação a objetos do Java, caparam a herança multipla e colocaram a idéia de interface, que evita ter esses tipos de problemas citados.
    O pessoal do PHP, por um momento utilizou a estrutura de orientação a objeto similar a do java, porem, agora, estão caminhando no sentido contrário, retornando aos mesmos problemas que existem em C++
    Al invés do pessoal do PHP ficar pensando em paradigmas de orientação a objeto, eles deveriam implementar multi-threads na linguagem, melhorar as bibliotecas de soap, conexão com db, etc. Isso é bem mais útil,

    • hlegius says:

      São pontos válidos, Isma!

      No caso de “conflitos” de nomes em Traits diferentes, você é obrigado a declará-las na classe utilizando alias, caso contrário, Fatal Error é lançado.

      Foi como o Lucas postou. Use-a com cuidado. É um recurso interessante e pode-nos poupar um bom tempo de manutenção. Hipoteticamente, imagine um Repositório qualquer. Tente imaginar como seria dentro dele, buscar uma conexão com o banco para utilizar nas pesquisas. Dependency Injection seria uma saída, porém, causaria duplicação de código em várias chamadas. Uma factory poderia auxiliar, mas um Trait caberia melhor neste ponto.

      O PHP está seguindo para uma linha mais Scala/Ruby do que C++ ao meu ver. Tanto que a herença múltipla não existe – amém – ao invés, estão dando atenção aos Traits – já existentes nas citadas anteriormente.

      O pessoal do core PHP não está importando-se tanto com OO, tanto que é um desenvolvedor fora do core que publicou a RFC da implementação. A Zend gostou e tudo indica que entrará no PHP 5.4 que virá brevemente.

      Só para fechar: SOAP é falho e precisa de maior atenção, mesmo eu utilizando REST(ful) nos últimos tempos.

      Abração rapaz !

      • Ismael Vacco says:

        Mesmo RESTFUL no PHP não é tão bem implementado. Ficar lendo php://input para capturar dados PUT e DELETE é uma coisa da época das pedras. Python, Java, Ruby tem implementações bem melhores para capturar os dados.
        Outro problema é a caracteristica global que os dados de entrada tem. Pensando em OO, manter $_POST e $_GET com escopo global é bem feio.
        É o nosso ganha-pão o PHP e creio que as críticas são feitas visando melhorar a linguagem. Mas merecem atenção do time de desenvolvimento da linguagem.

  3. [...] Suporte a Traits (Leia mais na RFC para Horizontal Reuse e no site do @hlegius) [...]

  4. Ótimo post, Hélio. No começo achei meio confuso, mas depois deu pra pegar bem a idéia. Tomada que venha realmente no PHP 5.4, pois é de bastante utilidade. Abraço!

Comente !