PHP Traits
PHP only allows single inheritance. In other words, a class can only extend one other class. But what if you need to include something that doesn't belong in the parent class? Prior to PHP 5.4 you would have to get creative, but in 5.4 Traits were introduced. Traits allow you to basically "copy and paste" a portion of a class into your main class.
trait Talk {
/** @var string */
public $phrase = 'Well Wilbur...';
public function speak() {
echo $this->phrase;
}
}
class MrEd extends Horse {
use Talk;
public function __construct() {
$this->speak();
}
public function setPhrase($phrase) {
$this->phrase = $phrase;
}
}
So here we have MrEd, which is already extending Horse. But not all horses Talk, so we have a Trait for that. Let's note what this is doing
First, we define our Trait. We can use it with autoloading and Namespaces (see also Referencing a class or function in a namespace). Then we include it into our MrEd class with the keyword use.
You'll note that MrEd takes to using the Talk functions and variables without defining them. Remember what we said about copy and paste? These functions and variables are all defined within the class now, as if this class had defined them.
Traits are most closely related to Abstract classes in that you can define variables and functions. You also cannot instantiate a Trait directly (i.e. new Trait()). Traits cannot force a class to implicitly define a function like an Abstract class or an Interface can. Traits are only for explicit definitions (since you can implement as many Interfaces as you want, see Interfaces).
When should I use a Trait?
More often than not, the answer is going to be Yes. Traits are edge cases caused by single inheritance. The temptation to misuse or overuse Traits can be high. But consider that a Trait introduces another source for your code, which means there's another layer of complexity. In the example here, we're only dealing with 3 classes. But Traits mean you can now be dealing with far more than that. For each Trait, your class becomes that much harder to deal with, since you must now go reference each Trait to find out what it defines (and potentially where a collision happened, see Conflict Resolution). Ideally, you should keep as few Traits in your code as possible.
Changing Method Visibility
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// Change visibility of sayHello
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
Running this example:
(new MyClass1())->sayHello();
// Fatal error: Uncaught Error: Call to protected method MyClass1::sayHello()
(new MyClass2())->myPrivateHello();
// Fatal error: Uncaught Error: Call to private method MyClass2::myPrivateHello()
(new MyClass2())->sayHello();
// Hello World!
So be aware that in the last example in MyClass2 the original un-aliased method from trait HelloWorld stays accessible as-is.