Traits can be seen almost as an automation of Ctrl+C
and Ctrl+V
. This definition may seem coarse, but in fact traits can be quite useful if used carefully in specific situations (more on this below).
It is recommended that you continue using interfaces to define contracts (such as project documentation) and abstract classes to implement the base tag of these contracts, leaving some more specific for their daughter classes to implement. Anyway, if the class hierarchy for your project is important, keep doing it that way.
What about traits?
They definitely do not serve to define contracts. Traits completely ignore the class hierarchy. Use them to define very specific behaviors that can be reused by different types of objects. Among other things, they are very useful for classes that implement contracts that are repeated several times.
For example, imagine that you have the following base classes:
- Player
- Vehicle
- Weapon
- Enemy
- Scenario
And the following child classes:
- Rafael
extends
Player
- Car% with% Vehicle
- Motor% of% Vehicle
- Magnum% with% Weapon
- AR-15% with% Weapon
- Knife% with% Weapon
- Soldier
extends
Enemy
- Monster
extends
Enemy
- Water
extends
Scenario
- Fire
extends
Scenario
- ...
- Water Tank% with% Weapon or Vehicle ?? What if he can be controlled? Are you a Player too? Look! It can also take damage!
In such a case, traits would be useful for defining very specific methods and properties that can be used by several objects of different types. For example:
//Pode ser type-hinted
Interface RecebeDano {
protected $vida;
public function foiAcertado($dano);
}
Interface CausaDano {
protected $dano;
public function acertou(RecebeDano $alvo);
}
//Pode ser usado pelas classes Jogador (com as mãos),
//Arma (todas), TanqueDeGuerra e Fogo (item do cenário)
Trait ImplementsCausaDano {
protected $dano = 10; //Pode ser sobrescrito pela classe
public function acertou(RecebeDano $alvo) {
$alvo->foiAcertado($this->dano);
}
}
"Ah, dude, but this I can do with abstract class!"
Well, let's take it easy. You can only extend a single abstract class. That is, it clearly defines a parent-child relationship. If you want to force class hierarchy, beauty. However, if you need a more flexible structure (like the tank example above), traits are the best way.
Important:
The methods of traits are evaluated as if they had been defined in the class that uses them, that is: it has priority over a method of a parent class with the same name.
The only entity that can override a method of a trait is the class itself that uses it, or another trait that has the same method. If the class that uses traits does not explicitly define which method prevails, this will cause a conflict.
In this case, PHP offers the following syntax for solving them:
class Classe {
use TraitA, TraitB {
TraitA::metodoDuplicado insteadof TraitB;
}
}
Finally, if you want to remember what each structure is meant to be, you can look at the semantics of the commands you write:
Interface you implements:
It is a contract. She can not implement anything, just define, but what she
define you have to follow.
Abstract class you extends: (inherits)
It's a father. It will help you in whatever you need to constitute your base, however some things you will have to do yourself.
Trait you uses:
It is a tool. You can use it for better or for worse. "With big powers come big responsabilities." (Ben, Uncle)