Skip to content

9. Os Traits

Os Traits são estruturas análogas às classes. No entanto, não é possível instanciá-los. Destinam-se a ser incluídos em classes. A inclusão de um Trait numa classe tem o mesmo efeito que se tivéssemos copiado o código do Trait para a classe. Coloca-se num Trait código que possa ser reutilizado em várias classes.

9.1. A estrutura hierárquica dos scripts

Image

9.2. Inclusão de um Trait numa classe

O script [traits-01.php] mostra uma utilização básica de um Trait numa classe:


<?php

class Class04 {
  // atributo
  private $name;

  // construtor
  public function __construct(string $name) {
    $this->name = $name;
  }

  // getters e setters
  public function getName(): string {
    return $this->name;
  }

  public function setName(string $name): void {
    $this->name = $name;
  }

}

trait Trait04 {
  // atributo
  private $name;

  // getters e setters
  public function getName(): string {
    return $this->name;
  }

  public function setName(string $name): void {
    $this->name = $name;
  }

}

class Class05 {
  // inclusão do Trait
  use Trait04;

  // construtor
  public function __construct(string $name) {
    $this->name = $name;
  }

}

// teste --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// exibição das duas classes
print_r($class04);
print_r($class05);

Comentários ao código

  • linhas 3-21: definição da classe [Class04] com um atributo, os seus métodos get/set e um construtor;
  • linhas 23-36: retoma-se o código de [Class04] sem o seu construtor e transfere-se-o para o traço [Trait04] tal como está. Não se retoma o construtor, uma vez que um traço não é instanciável;
  • linha 23: é a palavra-chave [trait] que faz com que [Trait04] seja um trait em vez de uma classe;
  • linhas 38-45: define-se uma classe [Class05] que retoma o código do traço [Trait04] (linha 40) e lhe adiciona um construtor (linhas 43-45), idêntico ao da classe [Class04], para tornar a classe instanciável;
  • linha 40: é a palavra-chave [use] que permite a inclusão de um Trait numa classe;
  • linhas 50-56: os testes demonstram que as classes [Class04] e [Class05] funcionam da mesma forma;

Resultados

Tim
Burton
Class04 Object
(
    [name:Class04:private] => Tim
)
Class05 Object
(
    [name:Class05:private] => Burton
)

Os resultados das linhas 3-10 mostram que as classes [Class04] e [Class05] têm o mesmo conteúdo;

Conclusão

A utilização da instrução [use Trait] numa classe equivale a incluir o código de [Trait] na classe.

9.3. Utilizar o mesmo padrão em diferentes classes

Uma das principais vantagens do traço parece ser a reutilização do mesmo código (atributos + métodos) entre diferentes classes. Veremos, no entanto, que é possível atingir o mesmo objetivo utilizando classes simples.

A partilha de um trait entre classes é ilustrada pelo seguinte script [trait-02.php]:


<?php

trait Trait01 {
  // atributo
  private $id = 0;

  // método
  public function doSomething() {
    print "Trait01::doSomething… ($this->id)\n";
  }

}

class Class02 {
  // inclusão Trait01
  use Trait01 {
    // o método [Trait01::doSomething] está acessível
    // na classe com o nome [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }

  // método específico da classe
  public function doSomething(): void {
    // atributo id
    $this->id += 10;
    // utilização do método Trait01
    $this->doSomethingInTrait();
    // exibição local
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // inclusão de Trait01
  use Trait01;

  // método local da classe
  public function doSomethingElse(): void {
    // atributo id
    $this->id += 10;
    // utilização do método Trait01
    $this->doSomething();
    // exibição local
    print "Class03->doSomethingElse\n";
  }

}

// teste01 ----------------
function test01(): void {
  $class02 = new Class02();
  $class03 = new Class03();
  $class02->doSomething();
  $class03->doSomethingElse();
}

// teste01
print "test01-----------------\n";
test01();

Comentários

  • linhas 3-10: um traço que define um atributo (linha 5) e um método (linhas 8-10).
  • O traço [Trait01] é injetado em duas classes: [Class02] (linhas 14-32) e [Class03] (linhas 34-46).
  • linhas 16-20: injeção de [Trait01] em [Class02];
  • a linha 19 destina-se a resolver um conflito: [Trait01] e [Class02] têm ambos um método denominado [doSomething]. Há dois casos a considerar:
    • o método [Class02::doSomething] é chamado a partir do exterior da classe. Neste caso, o método [Class02::doSomething] tem prioridade sobre o método [Trait01::doSomething] e é este que é chamado;
    • o método [Class02::doSomething] é chamado a partir do interior da classe. Neste caso, ocorre um conflito: o interpretador PHP não sabe qual o método a chamar;

A linha 19 permite renomear o método [Trait01::doSomething] para [doSomethingInTrait]. Assim, no interior de [Class02], utilizar-se-ão as notações:

  • [doSomethingInTrait] para chamar o método [Trait01::doSomething];
  • [doSomething] para chamar o método [Class02::doSomething];
  • linhas 25, 27: a classe [Class02] utiliza o atributo e o método de [Trait01] como se fossem próprios;
  • linhas 34-48: a classe [Class03] é idêntica à classe [Class02]. A inclusão de [Trait01] é aqui mais simples, porque não há conflito entre os métodos de [Trait01] e [Class03];

Resultados

1
2
3
4
5
test01-----------------
Trait01::doSomething… (10)
Class02->doSomething
Trait01::doSomething… (10)
Class03->doSomethingElse

Note-se que não há partilha da característica [Trait01] entre as classes [Class02] e [Class03]. Assim, oatributo [Trait01::i] torna-se, por inclusão de [Trait01] nas classes [Class02] e [Class03], dois atributos diferentes: [Class02::i] e [Class03::i]. É isso que mostram as linhas 2 e 4 dos resultados. Se o atributo [Trait01::i] tivesse sido partilhado entre as classes [Class02] e [Class03], teríamos 20 na linha 4 em vez de 10.

O script [trait-03.php] mostra que é possível chegar ao mesmo resultado utilizando uma classe em vez do traço:


<?php

// classe que substitui o trait
class Class01 {
  // atributo
  private $id = 0;

  // setter
  public function setId(int $id) {
    $this->id = $id;
  }

  // getter
  public function getId(): int {
    return $this->id;
  }

  // método
  public function doSomething(): void {
    print "Class01::doSomething… ($this->id)\n";
  }

}

class Class02 {
  // inclusão Class01
  private $class01;

  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // método específico da classe
  public function doSomething(): void {
    // alteração do atributo de Class01
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // utilização do método da Class01
    $this->class01->doSomething();
    // exibição local
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // inclusão da Class01
  private $class01;

  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // método local da classe
  public function doSomethingElse(): void {
    // alteração do atributo da Class01
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // utilização do método da Class01
    $this->class01->doSomething();
    // exibição local
    print "Class03->doSomethingElse\n";
  }

}

// teste01 ----------------
function test01(): void {
  // dois objetos
  $class02 = new Class02();
  $class03 = new Class03();
  // vão aceder a duas instâncias diferentes de [Class01]
  $class02->setClass01(new Class01());
  $class03->setClass01(new Class01());
  // verificação
  $class02->doSomething();
  $class03->doSomethingElse();
}

// teste02 ----------------
function test02(): void {
  // instância partilhada de [Class01]
  $class01 = new Class01();
  // dois objetos
  $class02 = new Class02();
  $class03 = new Class03();
  // vão aceder à mesma instância de [Class01]
  $class02->setClass01($class01);
  $class03->setClass01($class01);
  // verificação
  $class02->doSomething();
  $class03->doSomethingElse();
}

// teste01
print "test01-----------------\n";
test01();
// teste02
print "test02-----------------\n";
test02();

Comentários

  • linhas 4-23: a classe [Class01] substitui a classe [Trait01]. O código de [Trait01] estava incorporado no código das classes [Class02] e [Class03]. Neste caso, isso não acontecerá. Será uma referência ao código da classe [Class01] que será inserida nas classes [Class02] e [Class03]. O que significa que os atributos da classe [Class01] ficarão fora do código das classes [Class02] e [Class03]. Como, neste caso, o atributo [id] é privado (linha 6), é necessário definir um getter (linhas 14-16) e um setter (linhas 9-11). Esta é a primeira diferença em relação ao trait: é necessário criar o código de acesso aos atributos privados da classe. Poderíamos ter alterado a visibilidade do atributo [id] para [public], mas nunca é aconselhável fazê-lo. Utilizar um setter para definir o valor de um atributo permite verificar a sua validade;
  • linha 27: inclusão no código de [Class02] de uma referência à classe [Class01]. Como este atributo é privado, temos de criar um setter (linhas 30-32) para o inicializar;
  • linhas 37-41: para qualquer utilização do código de [Class01], temos de passar pelo atributo [$this→class01];
  • linhas 48-69: a classe [Class03] é um clone da classe [Classe02], com a única diferença de que o seu método tem um nome diferente;
  • linhas 72-82: o primeiro teste. Este consiste em injetar nas classes [Class02] e [Class03] duas instâncias diferentes da classe [Class01] (linhas 77 e 78);
  • linhas 85-97: o segundo teste injeta nas classes [Class02] e [Class03] a mesma instância da classe [Class01] (linhas 97, 92, 93);
  • linhas 100-101: execução do teste [test01];
  • linhas 103-104: execução do teste [test02];

Resultados

test01-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (10)
Class03->doSomethingElse
test02-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (20)
Class03->doSomethingElse

Comentários aos resultados

  • linhas 2-5: obtêm-se os mesmos resultados que com o traço [Trait01]. Conclui-se que a utilização do trait não é, neste caso, indispensável, mas que permite uma redução do código, uma vez que não são necessários métodos para aceder aos atributos do trait: estes fazem parte integrante do código no qual o trait foi incorporado;
  • linhas 7-10: como se injetou a mesma referência de [Class01] nas classes [Class02] e [Class03], o atributo [Class01::id] foi partilhado entre as duas classes. É por isso que a linha 9 dos resultados apresenta 20 em vez de 10 com o trait. Conclui-se, portanto, que se os atributos do trait tiverem de ser partilhados entre classes, então o trait não é utilizável e é necessário recorrer a uma classe;

9.4. Agrupar métodos num trait

No exemplo anterior, o trait incluía atributos e métodos. Aqui, vamos considerar o caso em que contém apenas métodos. Neste caso, o trait assemelha-se a uma fatoração de métodos que podem, então, ser utilizados em diferentes classes. Como o trait não tem, neste caso, nenhum atributo, vamos considerar o caso em que os métodos que reúne operam exclusivamente sobre os parâmetros que lhes são passados. Na verdade, isso não é obrigatório: um trait pode operar sobre um atributo [$this→attribut1] sem possuir esse atributo. Cabe então às classes que utilizam esse trait fornecer o atributo [$this→attribut1].

No caso de o trait ter apenas métodos que operam exclusivamente sobre os parâmetros que lhes são passados, demonstraremos que o trait pode então ser substituído por uma classe com os mesmos métodos que o trait, declarados como estáticos.

A utilização do trait é ilustrada pelo script [trait-04.php], que retoma o trait do exemplo anterior, removendo-lhe todos os atributos:


<?php

trait Trait01 {

  // método a partilhar
  public function doSomething() {
    print "Trait01::doSomething ….\n";
  }

}

class Class02 {
  // inclusão Trait01
  use Trait01 {
    // o método [Trait01::doSomething] está acessível
    // na classe com o nome [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }

  public function doSomething(): void {
    // chamada ao método Trait01
    $this->doSomethingInTrait();
    // exibição local
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // inclusão de Trait01
  use Trait01;

  // método local da classe
  public function doSomethingElse(): void {
    // chamada ao método de Trait01
    $this->doSomething();
    // exibição local
    print "Class03->doSomethingElse\n";
  }

}

// teste ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

Comentários

  • linhas 3-10: o trait [Trait01] já não tem atributos;
  • linhas 14-18: inclusão de [Trait01] em [Class02]. O método de [Trait01] é utilizado na linha 22;
  • linha 31: inclusão de [Trait01] em [Class03]. O método de [Trait01] é utilizado na linha 36;

Resultados

1
2
3
4
Trait01::doSomething ….
Class02->doSomething
Trait01::doSomething ….
Class03->doSomethingElse

Neste caso de utilização, o traço [Trait01] pode ser facilmente substituído por uma classe. Isto é demonstrado pelo seguinte script [trait-05.php]:


<?php

abstract class Class01 {

  // método estático a partilhar
  public static function doSomething() {
    print "Class01::doSomething ….\n";
  }

}

class Class02 {

  public function doSomething(): void {
    // chamada ao método da Class01
    Class01::doSomething();
    // exibição local
    print "Class02->doSomething\n";
  }

}

class Class03 {

  // método local da classe
  public function doSomethingElse(): void {
    // chamada ao método da Class01
    Class01::doSomething();
    // exibição local
    print "Class03->doSomethingElse\n";
  }

}

// teste ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

Comentários

  • linhas 3-10: o traço [Trait01] é substituído por uma classe abstrata [Class01], cujos métodos são todos declarados como estáticos. A classe é declarada como abstrata apenas para impedir a sua instanciação. Gostaríamos de ter escrito também [final] para impedir a sua derivação, mas PHP 7 não aceita o prefixo [final abstract] para uma classe. É um ou outro, mas não ambos;
  • linha 16: em vez de escrever [$this→doSomethingInTrait], escrevemos agora [Class01::doSomething], ou seja, chamamos o método estático [doSomething] da classe [Class01];
  • linha 28: repete-se o mesmo procedimento em [Class03];

Resultados

1
2
3
4
Class01::doSomething ….
Class02->doSomething
Class01::doSomething ….
Class03->doSomethingElse

Temos, de facto, o mesmo resultado que com o traço [Trait01], o que demonstra que a utilização deste pode ser evitada. Referimos que os métodos de um traço podem operar sobre um atributo [$this→attribut1] sem que o traço possua esse atributo. Cabe, então, às classes que utilizam esse traço fornecer o atributo [$this→attribut1]. Trata-se de um caso excêntrico: mais vale «transmitir» o atributo [$this→attribut1], que as classes que utilizam o traço devem possuir, para o próprio traço. Desta forma, ele fará necessariamente parte dos atributos da classe que utiliza o traço.

9.5. Herança múltipla com um traço

É frequente ler na literatura PHP que o traço permitiria a herança múltipla: a possibilidade de uma classe herdar de várias classes. A linguagem C++ possui essa possibilidade, mas não as linguagens Java ou C#, que apenas conhecem a herança simples. Vamos demonstrar que, embora a utilização de um traço numa classe derivada permita implementar algo semelhante à herança múltipla, este caso de utilização pode, mais uma vez, ser implementado com classes simples.

O script [trait-06.php] implementa uma classe derivada e um traço:


<?php

trait Trait01 {
  // atributo
  private $i;

  // método a partilhar
  public function doSomethingInTrait01() {
    // alteração de Trait01::$i
    $this->i++;
    // exibição
    print "Trait01::doSomethingInTrait01… i=$this->i\n";
  }

}

class Class02 {
  // atributo
  protected $j = 0;

  // método
  public function doSomethingInClass02(): void {
    // alteração Class02::j
    $this->j += 10;
    // exibição
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }

}

// classe derivada
class Class03 extends Class02 {
  // herda de Class02:j e Trait01::i
  // inclusão de Trait01
  use Trait01;

  // método
  public function doSomethingInClass03(): void {
    // utilização do método de Trait01
    $this->doSomethingInTrait01();
    // alteração de Trait01::i
    $this->i += 100;
    // alteração da Class03::j (==Class02::j)
    $this->j += 1000;
    // visualização
    print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
  }

}

// teste ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();

Comentários

  • linhas 3-15: voltamos a um traço [Trait01] com um atributo e um método que o manipula;
  • linhas 17-29: uma classe [Class02] que não tem nada a ver com o traço [Trait01]. Ela não o utiliza;
  • linha 19: foi declarado o único atributo de [Class02] com visibilidade [protected] para que este seja acessível nas classes derivadas;
  • linha 32: a classe [Class03] estende a classe [Class02]. Além disso, incorpora o traço [Trait01] (linha 35). Por fim, herda os atributos e métodos de [Class02] e incorpora os atributos e métodos de [Trait01]. Temos, portanto, algo análogo à herança múltipla;

Resultados

1
2
3
Class02->doSomethingInClass02… j=10
Trait01::doSomethingInTrait01… i=1
Class03->doSomethingInClass03… i=101, j=1000

Da mesma forma que foi feito num exemplo anterior, vamos demonstrar que:

  • o traço pode ser substituído por uma classe;
  • em vez de incorporar o traço na classe derivada, incorpora-se a referência a uma instância da classe;

O script [trait-07.php] é o seguinte:


<?php

class Class01 {
  // atributo
  protected $i;

  // getter e setter
  public function getI(): int {
    return $this->i;
  }

  public function setI(int $i): void {
    $this->i = $i;
  }

  // método
  public function doSomethingInClass01(): void {
    // alteração Class01::$i
    $this->i++;
    // visualização
    print "Class01::doSomething in Class01… i=$this->i\n";
  }

}

class Class02 {
  // atributo
  protected $j = 0;

  // método específico da classe
  public function doSomethingInClass02(): void {
    // alteração Class02::j
    $this->j += 10;
    // exibição
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }

}

class Class03 extends Class02 {
  // inclusão da Classe01
  private $class01;

  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // método local da classe
  public function doSomethingInClass03(): void {
    // utilização do método da Class01
    $this->class01->doSomethingInClass01();
    // alteração de Class01::i
    $i = $this->class01->getI();
    $i += 100;
    $this->class01->setI($i);
    // alteração de Class03::j
    $this->j += 1000;
    // visualização
    print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
  }

}

// teste ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();

Comentários

  • linhas 3-24: a classe [Class01] substitui o traço [Trait01]. Como a classe [Class01] vai ser incorporada nas classes através de uma referência, foram previstos métodos get/set para o atributo [$i];
  • linhas 26-38: a classe [Classe02] não sofre alterações;
  • linha 40: a classe [Class03] estende a classe [Classe02];
  • linha 42: a classe [Classe01] é incorporada na [Classe03] através de uma referência;
  • linhas 45-47: prevê-se uma classe setter para inicializar a referência à classe [Classe01];
  • linhas 50-61: o método [doSomethingInClass03] faz o mesmo que anteriormente, mas com um código mais complexo;

Resultados

1
2
3
Class02->doSomethingInClass02… j=10
Class01::doSomething in Class01… i=1
Class03->doSomethingInClass03… i=101, j=1000

A partir deste exemplo, pode-se concluir que, mais uma vez, a traço não é indispensável, mas é preciso reconhecer que permite escrever um código mais curto na classe derivada.

9.6. Utilizar um trait em vez de uma classe abstrata

É frequente depararmo-nos com o seguinte caso de utilização: cria-se uma interface I bastante geral que pode dar origem a várias implementações. Estas partilham um código comum, mas diferem noutros métodos. Este caso de utilização pode ser implementado de duas formas:

  1. cria-se uma classe abstrata C que agrupa o código comum às classes derivadas. A classe C implementa a interface I, mas certos métodos que devem ser declarados nas classes derivadas são, na classe C, declarados como abstratos e, por isso, a própria classe C é abstrata. Em seguida, criam-se as classes C1 e C2, derivadas de C, que implementam, cada uma à sua maneira, os métodos não definidos (abstratos) da sua classe pai C;
  2. cria-se um traço T quase idêntico à classe abstrata C da solução anterior. Este traço não implementa a interface I, uma vez que, do ponto de vista sintático, não o pode fazer. Em seguida, criam-se as classes C1 e C2, que implementam a interface I e utilizam o traço T. A estas classes resta apenas implementar os métodos da interface I que não foram implementados pelo traço T que importam;

Eis um exemplo que ilustra a grande semelhança entre estas duas soluções.

A aplicação 1 implementa a solução 1 descrita anteriormente, [trait-08.php]:


<?php

interface Interface1 {

  public function doSomething(): void;

  public function doSomethingElse(): void;
}

abstract class AbstractClass implements Interface1 {
  // atributos
  protected $attr1 = 11;
  protected $attr2 = 12;

  // getters e setters
  public function getAttr1() {
    return $this->attr1;
  }

  public function getAttr2() {
    return $this->attr2;
  }

  public function setAttr1($attr1) {
    $this->attr1 = $attr1;
    return $this;
  }

  public function setAttr2($attr2) {
    $this->attr2 = $attr2;
    return $this;
  }

  // método implementado
  public function doSomething(): void {
    print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
  }

  // método não implementado
  abstract public function doSomethingElse(): void;
}

// classe derivada 1
class Class1 extends AbstractClass {
  // atributo
  private $attr3 = 13;

  // getters e setters
  public function getAttr3() {
    return $this->attr3;
  }

  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }

  // implementação doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }

}

// classe derivada 2
class Class2 extends AbstractClass {
  // atributo
  private $attr4 = 14;

  public function getAttr4() {
    return $this->attr4;
  }

  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // implementação doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }

}

// função externa
function useInterfaceWith(Interface1 $interface):void{
  $interface->doSomething();
  $interface->doSomethingElse();
}

// testes
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

Comentários

  • linhas 3-8: a interface [Interface1] tem dois métodos;
  • linhas 10-41: a classe abstrata [AbstractClass] implementa a interface [Interface1] (linha 10). Possui dois atributos com os respetivos getters e setters (linhas 12-32), implementa o método [doSomething] da interface [Interface1] (linhas 35-37) mas não sabe implementar o método [doSomethingElse]. Este é, portanto, declarado como abstrato (linha 40). A classe abstrata [AbstractClass] não pode ser instanciada e, para ter utilidade, tem de ser obrigatoriamente derivada;
  • linhas 44-63: a classe Class1 estende a classe abstrata [AbstractClass] e, por isso, implementa a interface [Interface1] (linha 14). Ela fornece um corpo ao método [doSomethingElse] que a sua classe pai não tinha definido (linhas 59-61). Adiciona também um atributo aos da sua classe pai (linhas 46-56);
  • linhas 66-82: a classe Class2 estende a classe abstrata [AbstractClass] e, por conseguinte, implementa a interface [Interface1] (linha 66). Ela define o corpo do método [doSomethingElse], que a sua classe pai não tinha definido (linhas 80-82). Adiciona também um atributo aos da sua classe pai (linhas 68-77);
  • linhas 87-90: a função [useInterfaceWith] recebe como parâmetro um tipo [Interface1] e chama os dois métodos desta interface;
  • linhas 93-94: a função [useInterfaceWith] é chamada pela primeira vez com um tipo [Class1] e, pela segunda vez, com um tipo [Class2]. Isto está correto, uma vez que estes dois tipos implementam a interface [Interface1];

Resultados


AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]

Agora, implementamos a solução 2 com o script [trait-09.php]. Isto consiste em substituir a classe abstrata por um trait:


<?php

interface Interface1 {

  public function doSomething(): void;

  public function doSomethingElse(): void;
}

trait Trait1 {
  // atributos
  private $attr1 = 11;
  private $attr2 = 12;

  // getters e setters
  public function getAttr1() {
    return $this->attr1;
  }

  public function getAttr2() {
    return $this->attr2;
  }

  public function setAttr1($attr1) {
    $this->attr1 = $attr1;
    return $this;
  }

  public function setAttr2($attr2) {
    $this->attr2 = $attr2;
    return $this;
  }

  // método implementado
  public function doSomething(): void {
    print "Trait::doSomething [$this->attr1,$this->attr2]\n";
  }
}

// classe derivada 1
class Class1 implements Interface1 {
  // utilização do trait
  use Trait1;
  // atributo
  private $attr3 = 13;

  // getters e setters
  public function getAttr3() {
    return $this->attr3;
  }

  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }

  // implementação doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }

}

// classe derivada 2
class Class2 implements Interface1 {
  // utilização da característica
  use Trait1;
  // atributo
  private $attr4 = 14;

  public function getAttr4() {
    return $this->attr4;
  }

  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // implementação doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }

}

// função externa que utiliza a interface
function useInterfaceWith(Interface1 $interface): void {
  $interface->doSomething();
  $interface->doSomethingElse();
}

// testes
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

Comentários

  • linhas 3-8: a interface [Interface1] não sofreu alterações;
  • linhas 10-41: o trait [Trait1] substitui a classe abstrata [AbstractClass] da solução 1. O código é o mesmo, com as seguintes diferenças:
    • linha 10: o traço [Trait1] não implementa a interface [Interface1]. Isso é sintaticamente impossível;
    • linhas 12-13: o atributo de visibilidade [protected] dos atributos da classe abstrata [AbstractClass] passa aqui a ser [private]. Estes dois atributos têm como objetivo proporcionar às classes derivadas acesso direto aos atributos da classe pai (protected) ou do traço (private) sem ter de passar pelos getters e setters;
    • o traço [Trait1] não declara o método abstrato [doSomethingElse];
  • linhas 41-62: a classe [Class1] da solução 2 é idêntica à classe [Class1] da solução 1, com as seguintes diferenças:
    • linha 41: a classe [Class1] implementa a interface [Interface1], enquanto que na solução 1 estendia a classe abstrata [AbstractClass];
    • linha 43: utiliza o traço [Trait1] para implementar uma parte da interface;
  • linhas 65-85: podem fazer-se os mesmos comentários que para [Class1];
  • linhas 87-95: o resto do código não sofre alterações;

Resultados


Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]

Obtêm-se, de facto, os mesmos resultados.

9.7. Conclusion

Dos exemplos anteriores, depreende-se que os casos de utilização em que o uso do traço traria uma vantagem líquida não são claros. Nos nossos exemplos, é sempre possível prescindir dele, substituindo-o por uma classe. No entanto, parece que a sua utilização é prática para fatorizar código entre diferentes classes derivadas, como se esse código pertencesse a uma classe pai. É isso que faremos num exemplo a seguir.