PHP - Using Traits
Introduction
Traits, which were introduced in PHP 5.4, are defined as:
Traits are a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.
Up until now I have never really felt the need for multiple inheritance and have always managed to get by with using factories/composition/interfaces, but whilst building an API, I decided to look into using traits because:
- AWS uses them in their PHP SDK codebase.
- Lots of my API resources have the same groups of methods, but different resources have a combination of different method groups, so I can't just use inheritance.
It is worth noting that Netbeans fully supports auto-completion of classes that use traits, as well as "go to source" (ctrl-B).
Examples
Below is a very simple example of using a trait to have a class print "hello world" when its hello
method is called.
trait MyTrait
{
public function hello()
{
print "hello world" . PHP_EOL;
}
}
class MyClass
{
use MyTrait;
}
$myObj = new MyClass();
$myObj->hello();
Multiple Traits
Your classes can use multiple traits as shown below:
trait Trait1
{
public function hello()
{
print "hello ";
}
}
trait Trait2
{
public function world()
{
print "world";
}
}
class MyClass
{
use Trait1;
use Trait2;
}
$myObj = new MyClass();
$myObj->hello();
$myObj->world();
Traits Within Traits
Traits can be comprised of other traits as well. With this example, there is never a need to use Trait1 if you have Trait2.
trait Trait1
{
public function hello()
{
print "hello ";
}
}
trait Trait2
{
use Trait1;
public function world()
{
print "world";
}
}
class MyClass
{
use Trait2;
}
$myObj = new MyClass();
$myObj->hello();
$myObj->world();
Inheritance With Traits
Traits will work with inheritance as shown in this script which will print "hello world":
trait MyTrait
{
public function hello()
{
print "hello ";
parent::hello();
}
}
class ParentClass
{
public function hello()
{
print "world";
}
}
class MyClass extends ParentClass
{
use MyTrait;
}
$myObj = new MyClass();
$myObj->hello();
A trait's method will automatically try to use the child's methods before resorting to the parents. To demonstrate this, run the code below once and then see what it does when you remove world
from MyClass
:
trait MyTrait
{
public function hello()
{
print "hello ";
$this->world();
}
}
class ParentClass
{
public function world()
{
print "there";
}
}
class MyClass extends ParentClass
{
use MyTrait {
hello as private hey;
}
public function world()
{
print "world";
}
}
$myObj = new MyClass();
$myObj->hello();
Overriding And Order of Precedence
Traits are overridden if the method with the same name is defined in the class that uses the traits. The precedence order is that members from the current class override Trait methods, which in turn override inherited methods. The script below will just print "hi there":
trait MyTrait
{
public function hello()
{
print "hello ";
parent::hello();
}
}
class ParentClass
{
public function hello()
{
print "world";
}
}
class MyClass extends ParentClass
{
use MyTrait;
public function hello()
{
print "hi there";
}
}
$myObj = new MyClass();
$myObj->hello();
Remapping
If you want to work around a trait's method being overridden, or you need to resolve a conflict of the same method name being used in two traits of a class, you can "remap" a trait's methods by changing their names and even whether the methods are private, public, protected. The example below extends the previous one an prints "Using hey method: hello world":
trait MyTrait
{
public function hello()
{
print "hello ";
parent::hello();
}
}
class ParentClass
{
public function hello()
{
print "world";
}
}
class MyClass extends ParentClass
{
use MyTrait {
hello as private hey;
}
public function hello()
{
print "Using hey method: ";
$this->hey();
}
}
$myObj = new MyClass();
$myObj->hello();
Abstract Methods
Traits can specify abstract methods, forcing the class that uses the trait to implement methods that the trait's other methods need as shown below:
trait MyTrait
{
public function runSomething()
{
return $this->something();
}
public abstract function something();
}
class MyClass
{
use MyTrait;
public function something()
{
// do something here.
}
}
$myObj = new MyClass();
$myObj->runSomething();
Disadvantages
Interfaces Not Supported
For some reason, traits do not support interfaces (yet).
Hidden Issues and Complicated Codebases
If you are writing traits that use methods that aren't defined in the trait itself, it can be easy to get into a situation whereby updates to your classes that implement such traits, or act as the parent to a class that does, will break your code without warning (and will even run perfectly fine until you try to execute that non-existant method route). I would recommend that any methods that are called from the trait itself on $this implement define that method as abstract to try and prevent such issues.
Combining traits with inheritance could make for an incredibly complicated codebase that is hard to understand and maintain. You can tell you are getting into a rat's nest as soon as you need to perform remapping on the traits.
References
- PHP Manual - Traits
- Stack Overflow - How to override trait function and call it from the overridden function?
- RFC - Traits with Interfaces
- Stack Overflow - Why PHP Trait can't implement interfaces?
First published: 16th August 2018