Skip to content

9. Traits

Os Traits são estruturas análogas às classes. No entanto, não podem ser instanciados. Destinam-se a ser incluídos em classes. Incluir um Trait numa classe tem o mesmo efeito que se o código do Trait tivesse sido copiado para a classe. Colocamos num Trait código que é provável que seja reutilizado em várias classes.

9.1. A árvore de scripts

Image

9.2. Incluir um traço numa classe

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


<?php
 
class Class04 {
  // attribute
  private $name;
 
  // manufacturer
  public function __construct(string $name) {
    $this->name = $name;
  }
 
  // getters and setters
  public function getName(): string {
    return $this->name;
  }
 
  public function setName(string $name): void {
    $this->name = $name;
  }
 
}
 
trait Trait04 {
  // attribute
  private $name;
 
  // getters and setters
  public function getName(): string {
    return $this->name;
  }
 
  public function setName(string $name): void {
    $this->name = $name;
  }
 
}
 
class Class05 {
  // trait inclusion
  use Trait04;
 
  // manufacturer
  public function __construct(string $name) {
    $this->name = $name;
  }
 
}
 
// test --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// display of both classes
print_r($class04);
print_r($class05);

Comentários de 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: retiramos o código de [Class04] sem o seu construtor e transferimo-lo para o trait [Trait04] tal como está. Não incluímos o construtor, uma vez que um trait não pode ser instanciado;
  • linha 23: a palavra-chave [trait] torna [Trait04] um trait em vez de uma classe;
  • linhas 38–45: definimos uma classe [Class05] que retira o código da característica [Trait04] (linha 40) e adiciona um construtor (linhas 43–45), idêntico ao da classe [Class04], para tornar a classe instanciável;
  • linha 40: a palavra-chave [use] permite que uma característica seja incluída numa classe;
  • linhas 50–56: os testes mostram 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 nas linhas 3–10 mostram que as classes [Class04] e [Class05] têm o mesmo conteúdo;

Conclusão

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

9.3. Utilizar o mesmo Trait em diferentes classes

Uma das principais vantagens de um trait parece ser a reutilização do mesmo código (atributos + métodos) em diferentes classes. Veremos, no entanto, que podemos alcançar o mesmo objetivo utilizando classes simples.

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


<?php
 
trait Trait01 {
  // attribute
  private $id = 0;
 
  // method
  public function doSomething() {
    print "Trait01::doSomething… ($this->id)\n";
  }
 
}
 
class Class02 {
  // inclusion Trait01
  use Trait01 {
    // method [Trait01::doSomething] is accessible
    // in the class under the name [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }
 
  // class-specific method
  public function doSomething(): void {
    // id attribute
    $this->id += 10;
    // using the Trait01 method
    $this->doSomethingInTrait();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // inclusion Trait01
  use Trait01;
 
  // method local to the class
  public function doSomethingElse(): void {
    // id attribute
    $this->id += 10;
    // using the Trait01 method
    $this->doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test01 ----------------
function test01(): void {
  $class02 = new Class02();
  $class03 = new Class03();
  $class02->doSomething();
  $class03->doSomethingElse();
}
 
// test01
print "test01-----------------\n";
test01();

Comentários

  • Linhas 3–10: uma característica que define um atributo (linha 5) e um método (linhas 8–10).
  • A característica [Trait01] é injetada em duas classes: [Class02] (linhas 14–32) e [Class03] (linhas 34–46).
  • Linhas 16–20: Injeção de [Trait01] em [Class02];
  • A linha 19 resolve um conflito: [Trait01] e [Class02] têm ambos um método chamado [doSomething]. Há dois casos a considerar:
    • o método [Class02::doSomething] é chamado de fora da classe. Neste caso, o método [Class02::doSomething] tem precedência sobre o método [Trait01::doSomething] e é o que é chamado;
    • o método [Class02::doSomething] é chamado de dentro da classe. Neste caso, há um conflito: o interpretador PHP não sabe qual método chamar;

A linha 19 renomeia o método [Trait01::doSomething] para [doSomethingInTrait]. Assim, dentro de [Class02], utilizaremos a seguinte notação:

  • [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 seus;
  • linhas 34–48: a classe [Class03] é idêntica à classe [Class02]. A inclusão de [Trait01] é mais simples aqui 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 a característica [Trait01] não é partilhada entre as classes [Class02] e [Class03]. Assim, ao incluir [Trait01] nas classes [Class02] e [Class03], o atributo [Trait01::i] torna-se dois atributos distintos: [Class02::i] e [Class03::i]. Isto é mostrado nas 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 podemos obter o mesmo resultado utilizando uma classe em vez de um trait:


<?php
 
// class that replaces the
class Class01 {
  // attribute
  private $id = 0;
 
  // setter
  public function setId(int $id) {
    $this->id = $id;
  }
 
  // getter
  public function getId(): int {
    return $this->id;
  }
 
  // method
  public function doSomething(): void {
    print "Class01::doSomething… ($this->id)\n";
  }
 
}
 
class Class02 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // class-specific method
  public function doSomething(): void {
    // chgt Class01 attribute
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // using the Class01 method
    $this->class01->doSomething();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // method local to the class
  public function doSomethingElse(): void {
    // chgt Class01 attribute
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // using the Class01 method
    $this->class01->doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test01 ----------------
function test01(): void {
  // two objects
  $class02 = new Class02();
  $class03 = new Class03();
  // will access two different instances of [Class01]
  $class02->setClass01(new Class01());
  $class03->setClass01(new Class01());
  // check
  $class02->doSomething();
  $class03->doSomethingElse();
}
 
// test02 ----------------
function test02(): void {
  // shared instance of [Class01]
  $class01 = new Class01();
  // two objects
  $class02 = new Class02();
  $class03 = new Class03();
  // will access the same instance of [Class01]
  $class02->setClass01($class01);
  $class03->setClass01($class01);
  // check
  $class02->doSomething();
  $class03->doSomethingElse();
}
 
// test01
print "test01-----------------\n";
test01();
// test02
print "test02-----------------\n";
test02();

Comentários

  • linhas 4-23: a classe [Class01] substitui o traço [Trait01]. O código para [Trait01] estava incorporado no código das classes [Class02] e [Class03]. Aqui, isso não acontecerá. Em vez disso, será injetada uma referência ao código da classe [Class01] nas classes [Class02] e [Class03]. Isto significa que os atributos da classe [Class01] serão externos ao código das classes [Class02] e [Class03]. Uma vez que o atributo [id] é privado aqui (linha 6), devem ser fornecidos um getter (linhas 14–16) e um setter (linhas 9–11). Esta é a primeira diferença em relação a um trait: é necessário criar o código para aceder aos atributos privados da classe. Poderíamos ter alterado a visibilidade do atributo [id] para [public], mas isso nunca é recomendado. Usar um setter para definir o valor de um atributo permite-nos validá-lo;
  • linha 27: inclusão no código [Class02] de uma referência à classe [Class01]. Como este atributo é privado, precisamos de criar um setter (linhas 30–32) para o inicializar;
  • linhas 37–41: para qualquer utilização do código da [Class01], devemos utilizar o atributo [$this→class01];
  • linhas 48–69: a classe [Class03] é um clone da classe [Class02], exceto que o seu método tem um nome diferente;
  • linhas 72–82: o primeiro teste. Este teste envolve a injeção de duas instâncias diferentes da classe [Class01] nas classes [Class02] e [Class03] (linhas 77 e 78);
  • linhas 85–97: o segundo teste injeta a mesma instância da classe [Class01] nas classes [Class02] e [Class03] (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 sobre os resultados

  • linhas 2–5: obtemos os mesmos resultados que com o trait [Trait01]. Podemos concluir que a utilização do trait não é essencial aqui, mas reduz o código, pois não há necessidade de métodos para aceder aos atributos do trait: estes são parte integrante do código no qual o trait foi incorporado;
  • linhas 7–10: Uma vez que a mesma referência a [Class01] foi injetada 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 quando se utiliza o trait. Podemos concluir que, se os atributos das características precisarem de ser partilhados entre classes, então a característica não é utilizável e deve ser utilizada uma classe em vez disso;

9.4. Agrupamento de métodos num trait

No exemplo anterior, o trait continha atributos e métodos. Aqui, consideramos o caso em que ele 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. Uma vez que o trait não tem atributos aqui, consideraremos o caso em que os métodos que agrupa funcionam exclusivamente com os parâmetros que lhes são passados. Na verdade, isto não é obrigatório: uma característica pode operar sobre um atributo [$this→attribute1] sem possuir esse atributo. Cabe então às classes que utilizam esta característica fornecer o atributo [$this→attribute1].

No caso em que o trait tem apenas métodos que operam exclusivamente sobre os parâmetros que lhes são passados, vamos mostrar que o trait pode então ser substituído por uma classe que tenha os mesmos métodos que o trait e que seja declarada como estática.

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


<?php
 
trait Trait01 {
 
  // method to share
  public function doSomething() {
    print "Trait01::doSomething ….\n";
  }
 
}
 
class Class02 {
  // inclusion Trait01
  use Trait01 {
    // method [Trait01::doSomething] is accessible
    // in the class under the name [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }
 
  public function doSomething(): void {
    // trait01 method call
    $this->doSomethingInTrait();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // inclusion Trait01
  use Trait01;
 
  // method local to the class
  public function doSomethingElse(): void {
    // trait01 method call
    $this->doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

Comentários

  • linhas 3-10: o trait [Trait01] já não tem quaisquer 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 uso, o trait [Trait01] pode ser facilmente substituído por uma classe. Isto é demonstrado pelo seguinte script [trait-05.php]:


<?php
 
abstract class Class01 {
 
  // static method to share
  public static function doSomething() {
    print "Class01::doSomething ….\n";
  }
 
}
 
class Class02 {

  public function doSomething(): void {
    // calling the Class01 method
    Class01::doSomething();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
 
  // method local to the class
  public function doSomethingElse(): void {
    // calling the Class01 method
    Class01::doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

Comentários

  • linhas 3-10: o trait [Trait01] é substituído por uma classe abstrata [Class01], na qual todos os métodos são declarados como estáticos. A classe é declarada como abstrata exclusivamente para impedir a sua instanciação. Teríamos gostado de incluir também [final] para impedir a sua derivação, mas o PHP 7 não aceita o prefixo [final abstract] para uma classe. É uma coisa ou outra, mas não ambas;
  • linha 16: em vez de escrever [$this→doSomethingInTrait], agora escrevemos [Class01::doSomething], ou seja, chamamos o método estático [doSomething] da classe [Class01];
  • linha 28: repetimos o mesmo processo em [Class03];

Resultados

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

Obtemos o mesmo resultado que com o trait [Trait01], o que mostra que a sua utilização pode ser evitada. Observámos que os métodos de um trait podem operar sobre um atributo [$this→attribut1] mesmo que o próprio trait não possua esse atributo. Cabe, portanto, às classes que utilizam este trait fornecer o atributo [$this→attribut1]. Este é um caso exótico: poderíamos muito bem “passar” o atributo [$this→attribut1] — que as classes que utilizam o trait devem ter — para o próprio trait. Dessa forma, ele fará necessariamente parte dos atributos da classe que utiliza o trait.

9.5. Herança múltipla com um trait

É comum ler na literatura sobre PHP que os traits permitem a herança múltipla: a capacidade de uma classe herdar de várias classes. A linguagem C++ suporta esta funcionalidade, mas Java e C# não, uma vez que apenas suportam herança única. Iremos mostrar que, embora a utilização de um trait numa classe derivada permita a implementação de algo semelhante à herança múltipla, este caso de utilização também pode ser implementado utilizando classes simples.

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


<?php
 
trait Trait01 {
  // attribute
  private $i;
 
  // method to share
  public function doSomethingInTrait01() {
    // modification Trait01::$i
    $this->i++;
    // display
    print "Trait01::doSomethingInTrait01… i=$this->i\n";
  }
 
}
 
class Class02 {
  // attribute
  protected $j = 0;
 
  // method
  public function doSomethingInClass02(): void {
    // modification Class02::j
    $this->j += 10;
    // display
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }
 
}
 
// derived class
class Class03 extends Class02 {
  // inherits Class02:j and Trait01::i
  // inclusion Trait01
  use Trait01;
 
  // method
  public function doSomethingInClass03(): void {
    // using the Trait01 method
    $this->doSomethingInTrait01();
    // modification Trait01::i
    $this->i += 100;
    // modification Class03::j (==Class02::j)
    $this->j += 1000;
    // display
    print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
  }
 
}
 
// test ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();

Comentários

  • linhas 3-15: voltamos a um trait [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 a característica [Trait01]. Ela não a utiliza;
  • linha 19: declarámos o único atributo de [Class02] com visibilidade [protected] para que 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

Tal como fizemos no exemplo anterior, vamos mostrar que:

  • o trait pode ser substituído por uma classe;
  • em vez de incorporar a característica na classe derivada, incorporamos uma referência a uma instância da classe;

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


<?php
 
class Class01 {
  // attribute
  protected $i;
 
  // getter and setter
  public function getI(): int {
    return $this->i;
  }
 
  public function setI(int $i): void {
    $this->i = $i;
  }
 
  // method
  public function doSomethingInClass01(): void {
    // modification Class01::$i
    $this->i++;
    // display
    print "Class01::doSomething in Class01… i=$this->i\n";
  }
 
}
 
class Class02 {
  // attribute
  protected $j = 0;
 
  // class-specific method
  public function doSomethingInClass02(): void {
    // modification Class02::j
    $this->j += 10;
    // display
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }
 
}
 
class Class03 extends Class02 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // method local to the class
  public function doSomethingInClass03(): void {
    // using the Class01 method
    $this->class01->doSomethingInClass01();
    // modification Class01::i
    $i = $this->class01->getI();
    $i += 100;
    $this->class01->setI($i);
    // modification Class03::j
    $this->j += 1000;
    // display
    print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
  }
 
}
 
// test ----------------
$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]. Uma vez que a classe [Class01] será incorporada nas classes através de uma referência, foram fornecidos métodos get/set para o atributo [$i];
  • linhas 26–38: a classe [Class02] permanece inalterada;
  • linha 40: a classe [Class03] estende a classe [Class02];
  • linha 42: a classe [Class01] é incorporada na [Class03] através de uma referência;
  • linhas 45–47: é fornecido um setter para inicializar a referência à classe [Class01];
  • linhas 50–61: o método [doSomethingInClass03] faz o mesmo que antes, mas com 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, podemos concluir que, mais uma vez, o trait não é essencial, mas é preciso reconhecer que permite um código mais curto na classe derivada.

9.6. Usar um trait em vez de uma classe abstrata

Encontramos frequentemente o seguinte caso de uso: criamos uma interface I bastante geral que pode dar origem a várias implementações. Estas partilham código comum, mas diferem noutros métodos. Podemos implementar este caso de uso de duas maneiras:

  1. Criamos uma classe abstrata C que contém 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 declarados como abstratos na classe C e, portanto, a própria classe C é abstrata. Criamos então as classes C1 e C2 derivadas de C, cada uma das quais implementa os métodos não definidos (abstratos) da sua classe pai C à sua maneira;
  2. criamos um trait T que é quase idêntico à classe abstrata C da solução anterior. Este trait não implementa a interface I porque, sintaticamente, não o pode fazer. Criamos então as classes C1 e C2 que implementam a interface I e utilizam o trait T. Tudo o que resta a estas classes é implementar os métodos da interface I que não são implementados pelo trait T que importam;

Aqui está um exemplo que ilustra o quão semelhantes estas duas soluções são.

A Aplicação 1 implementa a Solução 1 descrita acima [trait-08.php]:


<?php

interface Interface1 {
 
  public function doSomething(): void;
 
  public function doSomethingElse(): void;
}
 
abstract class AbstractClass implements Interface1 {
  // attributes
  protected $attr1 = 11;
  protected $attr2 = 12;
 
  // getters and 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;
  }
 
  // implemented method
  public function doSomething(): void {
    print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
  }
 
  // method not implemented
  abstract public function doSomethingElse(): void;
}
 
// derived class 1
class Class1 extends AbstractClass {
  // attribute
  private $attr3 = 13;
 
  // getter and setter
  public function getAttr3() {
    return $this->attr3;
  }
 
  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }
 
}
 
// derived class 2
class Class2 extends AbstractClass {
  // attribute
  private $attr4 = 14;
 
  public function getAttr4() {
    return $this->attr4;
  }
 
  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }
 
}
 
// external function
function useInterfaceWith(Interface1 $interface):void{
  $interface->doSomething();
  $interface->doSomethingElse();
}
 
// tests
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 seus getters e setters (linhas 12–32), implementa o método [doSomething] da interface [Interface1] (linhas 35–37), mas não implementa o método [doSomethingElse]. Este método é, portanto, declarado como abstrato (linha 40). A classe abstrata [AbstractClass] não pode ser instanciada e, para ser útil, deve ser derivada de;
  • linhas 44–63: a classe Class1 estende a classe abstrata [AbstractClass] e, assim, implementa a interface [Interface1] (linha 14). Ela fornece um corpo para o método [doSomethingElse] que a sua classe pai [ ] não tinha definido (linhas 59–61). Além disso, acrescenta um atributo aos da sua classe pai (linhas 46–56);
  • linhas 66–82: A classe Class2 estende a classe abstrata [AbstractClass] e, portanto, implementa a interface [Interface1] (linha 66). Ela fornece um corpo para o método [doSomethingElse], que a sua classe pai não tinha definido (linhas 80–82). Ela também adiciona um atributo aos da sua classe pai (linhas 68–77);
  • linhas 87–90: A função [useInterfaceWith] recebe um tipo [Interface1] como parâmetro e chama os dois métodos dessa 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 ambos os 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 utilizando o script [trait-09.php]. Isto envolve substituir a classe abstrata por um trait:


<?php
 
interface Interface1 {
 
  public function doSomething(): void;
 
  public function doSomethingElse(): void;
}
 
trait Trait1 {
  // attributes
  private $attr1 = 11;
  private $attr2 = 12;
 
  // getters and 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;
  }
 
  // implemented method
  public function doSomething(): void {
    print "Trait::doSomething [$this->attr1,$this->attr2]\n";
  }
}
 
// derived class 1
class Class1 implements Interface1 {
  // line usage
  use Trait1;
  // attribute
  private $attr3 = 13;
 
  // getter and setter
  public function getAttr3() {
    return $this->attr3;
  }
 
  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }
 
}
 
// derived class 2
class Class2 implements Interface1 {
  // line usage
  use Trait1;
  // attribute
  private $attr4 = 14;
 
  public function getAttr4() {
    return $this->attr4;
  }
 
  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }
 
}
 
// external function using the
function useInterfaceWith(Interface1 $interface): void {
  $interface->doSomething();
  $interface->doSomethingElse();
}
 
// tests
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, exceto pelos seguintes detalhes:
    • linha 10: o trait [Trait1] não implementa a interface [Interface1]. Isto é sintaticamente impossível;
    • linhas 12–13: o atributo de visibilidade [protected] dos atributos da classe abstrata [AbstractClass] passa a ser [private] aqui. Estes dois atributos destinam-se a dar às classes derivadas acesso direto aos atributos da classe pai (protected) ou do trait (private) sem ter de passar por getters e setters;
    • o trait [Trait1] não declara o método abstrato [doSomethingElse];
  • linhas 41-62: a classe [Class1] na Solução 2 é idêntica à classe [Class1] na Solução 1, com as seguintes pequenas diferenças:
    • linha 41: a classe [Class1] implementa a interface [Interface1], enquanto na Solução 1 ela estendia a classe abstrata [AbstractClass];
    • linha 43: utiliza o trait [Trait1] para implementar parte da interface;
  • linhas 65–85: aplicam-se os mesmos comentários que para [Class1];
  • linhas 87–95: o resto do código permanece inalterado;

Resultados


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

De facto, obtemos os mesmos resultados.

9.7. Conclusão

A partir dos exemplos anteriores, fica claro que os casos de utilização em que o uso de um trait proporcionaria um benefício líquido não são evidentes. Nos nossos exemplos, podemos sempre passar sem ele, substituindo-o por uma classe. No entanto, parece que o uso de um trait é prático para separar código entre diferentes classes derivadas, como se esse código pertencesse a uma classe pai. É isso que faremos no exemplo seguinte.