It’s another quick primer this time. Today we’re going to talk about a PHP 5.4+ feature called traits. While Laravel doesn’t make heavy use of traits they are sprinkled around the core codebase, so you’ll want to get familiar with them.
The Problem
Sometimes when you’re working in a class based object oriented system, you’ll find yourself in a situation where you want functionality from another class in your own class. If your class doesn’t have a parent class, you can accomplish this by extend
ing the class whose behavior your want. Pretend you’re creating a messenger class.
class Messenger
{
}
While you’re working on the messenger class, other members of your team have written a Hello
class.
class Hello
{
public function hello()
{
echo "Hello","\n"
}
}
If you want Hello
‘s functionality, you can extend
it.
class Messenger extends Hello
{
}
$o = new Messenger;
$o->hello();
Pretty basic object oriented stuff. However, lets say someone on your team has also written a class Goodbye
.
class Goodbye
{
public function goodbye()
{
echo "Goodbye","\n";
}
}
As it stands right now, there’s no way for you to incorporate Goodbye
‘s functionality into your Messenger
class. That is, in PHP, a class can only have one parent class. Some languages have experimented with allowing a class to have many parents (with a feature called multiple inheritance), but multiple inheritance creates a slew of ambiguities and has a bad reputation as it complicates simpler single class inheritance.
When PHP’s core team considered this programming problem for PHP 5.4, instead of multiple inheritance they developed a language feature called “traits”.
What’s a Trait
Traits are PHP’s take on the multiple inheritance problem. Think of a trait as something in-between a class
and an interface
. You can define a trait in PHP with the following
trait Hello
{
}
Similar to interfaces, you can define abstract methods on traits
trait Hello
{
abstract public function sendMessage();
}
However, different from interfaces, you can also define concrete methods on traits
trait Hello
{
abstract public function sendMessage();
public function hello()
{
echo "Hello World","\n";
}
}
You may not directly instantiate traits. The following program
trait Hello
{
abstract public function sendMessage();
public function hello()
{
echo "Hello World","\n";
}
}
$o = new Hello;
will halt with the error.
Fatal error: Cannot instantiate trait Hello
So far, traits look a lot like abstract classes. However, a class cannot extend a trait. The following program
trait Hello
{
abstract public function sendMessage();
public function hello()
{
echo "Hello World","\n";
}
}
class Messenger extends Hello
{
}
$o = new Messenger
will halt with the error
Fatal error: Class Messenger cannot extend from trait
Instead, a class uses traits. Consider the following program.
trait Hello
{
abstract public function sendMessage();
public function hello()
{
echo "Hello World","\n";
}
}
class Messenger
{
use Hello;
public function sendMessage()
{
echo 'I am going to say hello',"\n";
echo $this->hello(),"\n";
}
}
$o = new Messenger;
$o->sendMessage();
This program will run with the following output
I am going to say hello
Hello World
Notice that our class definition contains a
use Hello;
This tells PHP we want our class Messenger
to use the trait Hello
. The use
keyword here has nothing to do with PHP namespaces. When use
is inside a class
block, PHP knows it means use
a trait — when use
is outside a class block, PHP knows it means alias or import a namespace.
When a classes uses a trait, it has access to all the trait’s methods, just as though it had extended from it. Notice we’re calling the hello
method
echo $this->hello(),"\n";
There’s no hello
defined on Messenger
. Instead, hello
is defined on the trait.
Multiple Magic
So far the functionality we’ve described can all be accomplished without traits. That is, other than the use
operator, this all looks like plain old class inheritance. That’s about to change. Consider the following program
trait Goodbye
{
public function goodbye()
{
echo "Goodbye","\n";
}
}
trait Hello
{
public function hello()
{
echo "Hello World","\n";
}
}
class Messenger
{
use Hello, Goodbye;
public function sendMessage()
{
echo 'You say goodbye',"\n";
echo $this->goodbye();
echo 'and I say hello',"\n";
echo $this->hello(),"\n";
}
}
$o = new Messenger;
$o->sendMessage();
Here we’ve defined two traits. One named Goodbye
, the other named Hello
. Then, in our Messenger
class, we’ve used both
use Hello, Goodbye;
Run the above program, and you’ll get the following output
You say goodbye
Goodbye
and I say hello
Hello World
This is the point of traits. A client programmer can pull in any trait they want into their class to adopt the trait’s functionality.
While not as powerful as multiple inheritance (i.e. you need to write your code as traits in the first place), traits cordon off the ambiguities that arise from multiple inheritance into a separate system. This allows that separate system to develop advanced features for dealing with those ambiguities without complicating existing class syntax. If you’re interested in how these ambiguities are resolved, the PHP manual has a more in depth discussion of all the features of PHP’s traits.
With PHP 5.3 finally deprecated, you’ll start seeing traits used more often. In particular, there are parts of the Laravel framework that rely on traits for their functionality. Next time we’ll have a quick primer on Laravel’s MacroableTrait
.