What is a Trait?
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
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
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?
The first thing you should do, when considering a Trait, is to ask yourself this important question
Can I avoid using a Trait by restructuring my code?
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
Running this example:
So be aware that in the last example in
MyClass2 the original un-aliased method from
trait HelloWorld stays accessible as-is.
Trying to use several traits into one class could result in issues involving conflicting methods. You need to resolve such conflicts manually.
For example, let's create this hierarchy:
Now, let's try to create the following class:
The php interpreter will return a fatal error:
Fatal error: Trait method say has not been applied, because there are collisions with other trait methods on TalkingParrot
To resolve this conflict, we could do this:
- use keyword
insteadofto use the method from one trait instead of method from another trait
- create an alias for the method with a construct like
WoofTrait::say as sayAsDog;
This code will produce the following output:
Implementing a Singleton using Traits
Disclaimer: In no way does this example advocate the use of singletons. Singletons are to be used with a lot of care.
In PHP there is quite a standard way of implementing a singleton:
To prevent code duplication, it is a good idea to extract this behaviour into a trait.
Now any class that wants to function as a singleton can simply use the trait:
Even though it is now impossible to serialize a singleton, it is still useful to also disallow the deserialize method.
Multiple Traits Usage
The above example will output:
Traits to facilitate horizontal code reuse
Let's say we have an interface for logging:
Now say we have two concrete implementations of the
Logger interface: the
FileLogger and the
Now if you define some other class
Foo which you also want to be able to perform logging tasks, you could do something like this:
Foo is now also a
Logger, but its functionality depends on the
Logger implementation passed to it via
setLogger(). If we now want class
Bar to also have this logging mechanism, we would have to duplicate this piece of logic in the
Instead of duplicating the code, a trait can be defined:
Now that we have defined the logic in a trait, we can use the trait to add the logic to the
And, for example, we can use the
Foo class like this:
Traits to keep classes clean
Over time, our classes may implement more and more interfaces. When these interfaces have many methods, the total number of methods in our class will become very large.
For example, let's suppose that we have two interfaces and a class implementing them:
Instead of implementing all the interface methods inside the
Article class, we could use separate Traits to implement these interfaces, keeping the class smaller and separating the code of the interface implementation from the class.
From example, to implement the
Printable interface we could create this trait:
and make the class use the trait:
The primary benefits would be that our interface-implementation methods will be separated from the rest of the class, and stored in a trait who has the sole responsibility to implement the interface for that particular type of object.