Injectors: Dependency Injection with Traits

by

With the release of PHP 5.4, Traits were added to our toolbox.  And, if I might be so bold, it's one of the most exciting features I've seen come out of PHP in a long time.

Traits is 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. The semantics of the combination of Traits and classes is defined in a way which reduces complexity, and avoids the typical problems associated with multiple inheritance and Mixins.

A Trait is similar to a class, but only intended to group functionality in a fine-grained and consistent way. It is not possible to instantiate a Trait on its own. It is an addition to traditional inheritance and enables horizontal composition of behavior; that is, the application of class members without requiring inheritance.

With this in mind, I knew when I first saw traits that it was the way to solve dependency injection.  Granted, when I say solve dependency injection, I'm mostly referring to the way dependency injection (or DI) is being solved in the current incarnations of PHP frameworks.  Basically, through the use of dependency injection containers (DIC).  Essentially, a dependency injection container stores objects, or the ability to create objects, in some "master" object or container.  I'm not going to spend too much time on what DI is, or what a DIC is.  Fabien Potencier has done an excellent job on the subject with is "What is Dependency Injection" series.

Anyways, let's be clear about two things here:

  1. Dependency injection is good.
  2. Dependency injection containers solve a real problem.

However, the current incarnations of DICs have problems of their own.  First, let's cover a problem with basic DI, and what DIC solves.

<?phpclass User{  protected $record;  public function __construct( $userId )  {    $db = new DB( );    $this->record = $db->get( 'users', "userid = $userid" )->limit( 1 )->execute( );  }  // ....}$user = new User( 1 );

So, you might recognize this style of programming.  A User is created by passing a $userId to the constructor, whereby it calls the DB class to fetch a record.  What we've done here is couple the User class to the DB class.  This means we can't ever create a User unless we have a DB available.  If we want to create a User from some other source, we can't without a rewrite.  It also makes testing User difficult.  To test User, we also need to use DB, and make sure that is setup.

The first step to a solution is to inject the DB into the User class like so.

<?phpclass User{  protected $record;  public function __construct( $userId, DB $db )  {    $this->record = $db->get( 'users', "userid = $userid" )->limit( 1 )->execute( );  }  // ....}$user = new User( 1, new DB( ) );

Now, when we create the User class, we need to pass in the DB container.  That way, we can "inject" into the User class any data store we want.  In fact, we'd probably want to take this a a step further and instead of requiring a DB class, we wants to require an Interface of the DataStore type.

<?phpinterface DataStore{  public function get( $source, $condition );  public function limit( $limit );  public function execute( );}class DB implements DataStore{  // implementation goes here}class User{  protected $record;  public function __construct( $userId, DataStore $db )  {    $this->record = $db->get( 'users', "userid = $userid" )->limit( 1 )->execute( );  }  // ....}$user = new User( '1', new DB( ) );

Okay, so here we see the DataStore hsa been quickly drafted (it's actually a fairly bad implementation, but serves for the examples).

This means if at some point, we want to create a User from some call to an API, can can inject that instead.

<?phpinterface DataStore{public function get( $source, $condition );public function limit( $limit );public function execute( );}class DB implements DataStore{// implementation goes here}class API implements DataStore{// implementation goes here}class User{protected $record;public function __construct( $userId, DataStore $db ){$this->record = $db->get( 'users', "userid = $userid" )->limit( 1 )->execute( );}// ....}$user = new User( 1, new API( ) );

And that is dependency injection.  However, a problem occurs a your class starts needing more and more.

<?phpclass User{  protected $record;  public function __construct( $userId, DataStore $db, ISession $session, IConfig $config, ITracker $tracker, IMailer $mailer)  {    $this->record = $db->get( 'users', "userid = $userid" )->limit( 1 )->execute( );    $session->start( $this->record->userid );    $session->lifetime( $config->yes );    // ...  }  // ....}

Suddenly, you keep adding more and more to your constructor, and it takes on a whole new meaning.  Even worse off is that you might not need the Mailer unless you want to send the user a message.  This means unless you are sending a message, you are creating an object that won't ever get used.  This also places a lot of work on the programmer using the User class.  Every time they want want to create a User object, they need to create all these classes.  Sure, you could only require the Mailer if the user is going to message someone, and require they use a setMailer( ) function, but that creates even more confusing.

Yeah, gets fairly confusing fast.

So, what do we do about that?

Dependency Injection Container

The idea with the DIC is that you can store your objects, or the generation of those objects in one central registry (generally an array) that can be called from anywhere.  In our example, it might look like this.

<?phpclass User{  protected $record;  public function __construct( $userId )  {    $db = Container::get('DB');    $session = Container::get('Session', array('userId' => $userId));    // ...  }}new User( $id );

In this case, Container is simply that, a container of other objects.  We map the string 'DB' to a method, or some system that will return to us the DB class.  Actually, that's not quite right.  Instead, it returns, in our example, a class that implements the DataStore interface.

In our Container, we can make what DB returns actually be anything we want.

This helps tremendously in testing, because we can setup DB to be something other than an actual DB class.  It can simply be a mock DataStore object setup just for testing.

This is where we currently stand with dependency injection containers in PHP.  They are, for better or worse, global arrays we pass around.  That isn't so much a problem.  Rather, the problem is one of discovery.  Remember how I said containers like this map a string to an object?  That's because that's pretty much all it does.

I pass in DB, and I hope I get back an object like DB.  But where do I go to find out what exactly is happening?  Container::get isn't actually generating the object.  That's most likely abstracted out someplace else.  Another problem is knowing which values get will accept.  If I call Container::get('Cookie'), will that work as expected?  What about Container::get('Mailer')?  What do these actually return?  Nothing enforces this, nothing is easily discoverable through straight PHP code.  And, frankly, I don't know how you'd document this.

Container::get, at best, can tell you to pass it a string, and it will return, maybe, something.

It basically comes down to this:

$user = new GoogleTracker(  );

// versus

$user = Container::get('Tracker');

In one case, you know exactly which object you are going to use.  In the other, you can only assume.  In the former, you can get to the code quickly.  In the latter, you cannot.

Don't get me wrong, DI and a DIC solves a problem.  It's just missing a crucial piece to complete it.

Traits to the Rescue

So now we come to traits.  Let's start with a simple example.  In most applications, their exists a concept of configuration.

namespace Lib;class Config{    protected $loadedConfigFile = null;    protected $configName;    /**     * @param string $configFile Full path to config file     */    public function __construct( $configFile )    {        $this->configName = $configName;    }    /**     * @return object     */    public function getConfig( )    {        if ( ! isset($this->loadedConfigFile) )        {            $this->loadFile();        }        return $this->loadedConfigFile;    }    /**     * Loads the config file     *     * @return void     */    protected function loadFile( )    {        $this->loadedConfigFile = json_decode(file_get_contents($this->configName));    }}

Ignore the lack of error checking (please!), and let's explore how we could use a trait to load this.  What we want is to enforce that the same config file is only loaded once.  That way, multiple calls to create Config will only ever have to load the file once.  So, our trait would look like this.

namespace LibInjector;trait Config{   /**    * @return LibConfig    */   public function getConfig( $configName )   {        $keyName = __TRAIT__ . $configName;        if ( ! LibContainer::exists( $keyName ) )        {            LibContainer::set( $keyName, new LibConfig($configName) );        }        return LibContainer::get( $keyName );    }}

Here, the Config trait is created.  We've contained the loading code for a Config object in a single place, and that place is easily discovered.  Now, when we want to use the Config class, we can add it to a class like so.

You'll note I placed the Config trait into the Injector namespace. I like to think of traits used in this manner as an injector - the trait allows you the program to easily inject other needed code into your code.

namespace Lib;class User{    use InjectorConfig;    function __construct()    {        $this->config = $this->getConfig( 'UserConfig.json' );    }}

But does this really abstract out the calling of the code?  Sure!

In this case, the trait Config's getConfig method is fairly short, but that doesn't mean it can't expand to handle other cases.  If you want to replace LibConfig with AnotherLibConfig, it's simply a matter of switching it out.  You can also have getConfig use both Config libraries, determining which one to use based not the $configName passed (let's say, to handle an XML config file, and a JSON config file, and using two different libraries to handle that).

Injectors are also testable. Granted, you'll have to Mock out the test Injectors, but I found that to be easy as well.  Again, you can do anything you like in the injector, so handling different test cases is trivial.

Using injectors also give you the benefit of an easy code path to follow.  After all, the class is explicitly using InjectorConfig, which is a specific trait, and easily discovered.  Most IDE's will make this as easy as clicking on the name Config and it will take you right to the appropriate trait.  And there you see the creation code.

Also, you'll note getConfig has been given the tag @return LibConfig.  This again provides for clear documentation, and IDE's should return the proper type, allowing for better auto completion.

I've yet to find a case where using an injector has made things difficult or challenging.  And it's wonderful to work with them.  If I'm in an object and need to include  a Config or Cache object, it's as simple as adding a use statement at the top of the class and it's available.

Injectors

I hope you find the use of Injectors interesting.  If you have any questions, let me know.  I'll try to answer them.  This article has been sitting in my drafts file for far too long, and I need to get it out. So, I'm sure there are errors riddled throughout.  My apologies.  However, I hope the message gets across.  Try using Injectors, and let me know how they work out for you.  I'd love to hear it.