Eliminating repetition and encouraging code reuse are central tenets of software development.
PHP’s lack of multiple inheritance means sometimes there are tough design choices to be made when similar functionality is required in separate branches of the class heirarchy.
As proposed in the Horizontal Reuse for PHP RFC, traits offer a means of duplicating functionality in independent classes.
In this post, I’ll be detailing an approach that offers trait-like functionality to your objects in an intuitive and straightforward way.
Why Traits?
So why do we need traits, anyway?
PHP is a single inheritance language, meaning that each class can only extend one other. This allows us to build logical class hierarchies which extend functionality in a “vertical” way.
For example, the Lion class might extend a Mammal class, which in turn extends Animal. Or Eagle could extend Bird which also extends Animal. Or maybe Locust -> Insect -> Animal. Pretty logical stuff.
The problem arises when an overlap in functionality occurs in different branches of the class hierarchy. Say, for instance, we are interested how the different species of animals get around. Both our locust and our eagle are flyers, but they’re in separate branches of the class hierarchy. So where do we put our getAverageFlyingSpeed() and getMaxAltitude() methods?
At this point we have two choices. The first option is to move the methods into a class higher up the tree, like the Animal class. The drawback here is that we end up with a superclass containing scores of methods that are redundant for all but a handful of concrete classes. Not good.
The second option is to simply duplicate the code in the classes where it is required. This, however, flies in the face of the DRY principle and can make the code harder to maintain.
What we need is a means of re-using code other than simple inheritance. That’s where traits come in.
Traits are classes containing a collection of methods, usually relating to a particular behaviour. Since they can be applied at will, they are independent of the inheritance hierarchy. Conceptually, traits are very much like interfaces with actual methods.
Using our example, we could create a Flight trait which could be applied to both the eagle and locust classes, giving the desired functionality without the drawbacks associated with single class inheritance.
I Want Them Now!
Whoa there! One step at a time
Hopefully the benefits of traits for a language like PHP should be pretty clear.
Sadly, they’re unlikely to included in any PHP release in the near future (although you can download Stefan Marr’s patched PHP versions).
In the meantime, I’ve put together an approach that offers trait-like functionality and is reasonably easy to implement. There’s too much code to include it all in this post, so you can download the source code here.
The Code
In this setup, traits are classes that extend My_Trait_Abstract. To illustrate this, we’ll create a trait, My_Trait_Json, that adds a toJson method to any class it is added to.
require_once 'My/Trait/Abstract.php';
class My_Trait_Json extends My_Trait_Abstract
{
/**
* @param My_Object $object
* @return string
*/
public function toJson( My_Object $object )
{
$json = json_encode( get_object_vars( $object ) );
return $json;
}
}
Every public method in a trait class will be available to the classes to which they’re added. Also notice that the first parameter of each method must be an instance of the object which it is working on… that’ll make more sense later.
The abstract trait class uses reflection to examine each trait to find public methods – only the constructor and getMethods are excluded. _retrieveMethods is lazy-loaded and only carried out on a call to getMethods.
/**
* @return array
*/
public function getMethods()
{
if ( NULL === $this->_methods ) {
$this->_methods = array();
$this->_retrieveMethods();
}
return $this->_methods;
}
/**
* @return My_Trait_Abstract
*/
protected function _retrieveMethods()
{
// Exclude the constructor and public getMethods method
$exclude = array(
'__construct',
'getMethods'
);
$refObject = new ReflectionObject( $this );
foreach ( $refObject->getMethods() as $method ) {
if ( $method->isPublic() ) {
$name = $method->getName();
if ( !in_array( $name, $exclude ) ) {
$this->_addMethod( $name );
}
}
}
return $this;
}
/**
* @param string $method Name of method
* @return My_Trait_Abstract
*/
protected function _addMethod( $method )
{
if ( !method_exists( $this, $method ) ) {
throw new Exception( "Method '$method' does not exist" );
}
if ( array_search( $method, $this->_methods ) ) {
throw new Exception( "Duplicate method '$method'" );
}
$this->_methods[] = $method;
return $this;
}
The Glue
With our trait classes in order, we now need a way to access their functionality.
To do this, we’ll use a __call magic method in our object superclass and a “trait broker” – a class which maintains trait instances and their methods. (For Zend Framework users, the TraitBroker works in a very similar way to ZF’s PluginBroker.)
Here’s our object superclass:
abstract class My_Object
{
/**
* @var My_TraitBroker
*/
static private $_traitBroker;
/**
* @param string $method
* @param array $args
* @return mixed
*/
public function __call( $method, $args )
{
// Check trait broker for extended functionality
$class = get_class( $this );
$traitBroker = $this->getTraitBroker();
// Check whether class traits have been initialised
if ( !$traitBroker->isClassRegistered( $class ) ) {
$this->_initTraits();
$traitBroker->registerClass( $class );
}
if ( $traitBroker->hasMethod( $method, $class ) ) {
return $traitBroker->callMethod( $method, $this, $args );
}
throw new Exception(
"Invalid method ". get_class( $this ) ."::". $method ."(". print_r( $args, 1 ) .")"
);
}
//////////////////////////
// Trait Functionality //
//////////////////////////
/**
* @return My_TraitBroker
*/
public function getTraitBroker()
{
if ( !isset( self::$_traitBroker ) ) {
require_once 'My/TraitBroker.php';
$broker = new My_TraitBroker();
self::$_traitBroker = $broker;
}
return self::$_traitBroker;
}
/**
* @param My_TraitBroker
* @return void
*/
protected function _initTraits()
{
// Do nothing - child classes add traits here
}
/**
* @param My_Trait_Abstract
* @return My_Object
*/
protected function _registerTrait( My_Trait_Abstract $trait )
{
$this->getTraitBroker()->registerTrait( $trait, get_class( $this ) );
return $this;
}
}
The __call method will trap calls to non-existent methods, at which point the TraitBroker is instantiated and any traits for the class are initialised. All that our child classes need to do is extend _initTraits and specify which traits it uses.
Internally, the TraitBroker takes care of getting the right trait instance and calling the requested method.
/**
* Call the trait method
*
* @param string $method
* @param string $objectClass
* @param My_Object $object
* @param array $args
* @return mixed
*/
public function callMethod( $method, My_Object $object, array $args = array() )
{
$objectClass = get_class( $object );
if ( !$this->hasMethod( $method, $objectClass ) ) {
throw new My_Exception( "Cannot call method '$method' - not registered." );
}
$trait = $this->_methods[ $objectClass ][ $method ];
$args = array_merge( array( $object ), $args );
$result = call_user_func_array(
array( $trait, $method ),
$args
);
return $result;
}
As you can see, the trait class is passed the calling object and any arguments trapped by the __call method. This does mean that traits can only manipulate an object’s public methods and properties – how much of a limitation this is will depend on your exact implementation. In practice, I’ve found that this is usually easy to work around.
Now all we need is a class extending My_Object that registers the Json trait.
require_once 'My/Object.php';
require_once 'My/Trait/Json.php';
class My_Model extends My_Object
{
/**
* @var string
*/
public $foo = 'bar';
/**
* @var string
*/
public $bat = 'baz';
/**
* @return void
*/
protected function _initTraits()
{
// Register traits
$this->_registerTrait( new My_Trait_Json() );
}
}
The protected _registerTrait method takes care of registering the the trait with the broker.
The client code and output is exactly the same as if the toJson method were part of our model.
require_once 'My/Model.php';
$object = new My_Model();
var_dump( $object->toJson() );
// outputs
// string(25) "{"foo":"bar","bat":"baz"}"
A few notes:
- Since the _initTraits method is inherited by child classes like any other, traits will also be inherited. You can override this by putting a custom (or empty) _initTraits method in the child class.
- My_Trait_Abstract allows for an associative array of options to be passed to its constructor. For these to have any effect, you’ll need to add a protected _setOptionName method in your trait class.
- Although I haven’t benchmarked it, there is sure to be a small performance hit using traits against hard coded methods due to the use of __call and reflection. Chances are it’ll be pretty insignificant in a real world application though.
Although the JSON trait is a very simple example, I hope that you can see the potential value in this kind of setup. I’ve only been tinkering with it for a couple of weeks, but I’ve already used traits for things like addresses and HTML metadata (for a content management system). In fact, I’ve started putting functionality into traits that I’d normally hard code into classes, purely because the potential for re-use is so much greater.
Final Thoughts
PHP is at an exciting stage in its development and it will be interesting to see how the language changes over the next year or so, especially towards the release of PHP 6. But until new features like traits and grafts are available within the language itself, it’s up to us to come up with other ways around the limitations we face.
This approach to implementing traits is one way in which you can help to reduce repetition and promote code reuse within your applications.
Don’t forget that you download the complete source code.
Please let me know what you think by leaving a comment




April 7, 2010 at 2:09 am
Why not extend Locus and Eagle from FlyingAnimal? Despite the constraints, I feel whenever there is a will there is a way – PHP is already flexible enough to allow the developer to come up with their own solutions per project.
Looked at the RFC and I’m confused why they don’t just use:
class ezcReflectionMethod extends ReflectionMethod, ezcReflectionReturnInfo
rather than the use syntax which seems another way to do the same thing…
However it seems rather what has been invented in this post is a plugin system, rather than anything to do with inheritance – but perhaps that is point/solution? PHP could well include a plugin system on my behalf, would certainly cut down on development time having to code that up for each project (or have each framework do it differently).
April 7, 2010 at 2:26 pm
Posted on Steve’s behalf:
In response to balupton
>> Why not extend Locust and Eagle from FlyingAnimal?
Because then Locust would no longer extend Insect and Eagle would not extend Bird. That’s logically and semantically incorrect and you’d lose any common functionality in those parent classes.
It’s a trivial example, but I think it makes the point.
Terminology is a funny thing – I was using something like this in my current project and calling them "plugins" before I stumbled across the RFC. But I think "trait" is a better name for something this general – plugin, to me, suggests something that is more specific to a particular use.
April 8, 2010 at 9:33 pm
Favor Composition over Inheritance.
April 9, 2010 at 6:55 am
This _is_ a form of composition, surely? Certainly the author of the RFC thinks so…
"It is an addition to traditional inheritance and enables horizontal composition of behavior"
In fact, I think that traits fill the gap where composition is overkill or you need to add arbitrary functionality to individual classes. The toJSON (or toXML or toYAML etc.) trait is perhaps a good example of that.
April 9, 2010 at 5:23 pm
what you do with protected members?
September 23, 2010 at 5:37 am
Why not extend Locus and Eagle from FlyingAnimal?
October 19, 2010 at 6:11 pm
Have you considered packaging this and putting in on PEAR? I’m a Perl programmer doing PHP and sorely miss traits (we call them roles). It would be easier for the PHP programmers I work with to swallow if its a PEAR package rather than "something I downloaded from a blog".
January 12, 2011 at 6:11 am
This is a nice feature.
The problem is, you can’t use reflection on your model class to get the methods provided through your trait. On your example only __call and getTraitBroker are listed from outside.
Such a feature would be very helpful, if you like to use your model within a webservice for example. Zend_XmlRpc_Server and Zend_Json_Server use reflection for every service call and throwing exceptions, if methods are not listed, instead of trying to call them and throw exceptions afterwards if the call fails. So all the methods provided through your traits wouldn’t be reachable.