Container Baking

Hi there folks! Thanks for tuning into today’s episode of baking code with Dayle. Today, we’ll be rusting up a fresh batch of IoC container! Mmm, mm. Not just any IoC Container, but the dependency resolution flavor! Super tasty. Here are the ingredients you’ll be needing.

  • An understanding of Dependency Injection.
  • An understanding of Inversion of Control.
  • Exposure to the PHP Reflection classes.

Oh, some of you don’t have the ingredients? No problem, you can use mine! We’ll look at these techniques and tools as we go along.

Right then, now that I’ve broken the ice, I think we can lose the baking metaphors. What we’re looking to understand and create in this article, is a dependency-resolution-enabled inversion-of-control container. Now those are some pretty complicated terms, but I promise you, they aren’t anywhere near as scary as they first may seem.

This type of container you’ll find in a number of projects. It’s the backbone of the Laravel Framework, containing all the services available to the user.

It would be a shame to have to set-up a new database connection everytime that we use it, supplying the port, host, username, and password, so instead, we bind that service into the container so that we can use it in multiple places. That’s the beauty of the container!

Laravel’s implementation of the automatic dependency resolution (where it can instantiate classes by providing their constructor dependencies automatically) has been one of the more popular implementations in the PHP world, and we’ll aim to replicate that functionality within this article.

Let’s start by putting it together, piece by piece. First, we’ll setup our environment. In the project folder, we create src and tests directories to store the container code, and the tests respectively. We’ll also setup Composer for use in this project, and add PHPUnit as a dependency. Finally, I’ve added a sample phpunit.xml to load the Composer class map, and look in our tests directory for tests.

You’ll find the complete code for this article on my GitHub account. However, I highly recommend waiting until you’ve finished the article before playing with it.

First, we’ll start with an empty container class. We’ll place it in the src folder. I’ve configured Composer to map classes in this folder to the ‘Example’ namespace.

<?php

namespace Example\Container;

class Container  
{

}

Beautiful and clean! It’s a shame we’re going to make it messy by adding more code, but alas, that’s how applications are built! You’ll notice that I haven’t started with a test. We’re not actually going to TDD the container, not for any good reason, you could if you wanted to, but to make the example shorter and less “let’s check if this fails first” we’ll simply use our tests to execute the code.

We’ll implement the container feature by feature. The first thing we need to do is make it store and retrieve instances of services. This will actually make it more of a ‘service locator’, but it’s a good start point.

To make it store and retrieve instances, we’ll simply use an internal array to hold them. Here’s the example.

<?php

namespace Example\Container;

class Container  
{
    protected $instances = [];

    public function instance($key, $value)
    {
        $this->instances[$key] = $value;

        return $this;
    }

    public function make($key)
    {
        if (array_key_exists($key, $this->instances)) {
            return $this->instances[$key];
        }

        throw new \Exception('Unable to resolve binding from container.');
    }
}

Don’t be scared! We’ve got this. First, we create a class property called $instances, it’s a protected property that’s initialized to an empty array. This is where we’re going to store all the instances that we bind within the container.

The instance() public method takes two parameters, the first is the name we’ll use to store the instance, and the second is the instance itself. We place the provided instance in our $instances class property, using the $key as our index. I’ve returned $this simply to make the method chainable. That part’s not so important.

The next function is used to retrieve the instances. First, we check to make sure that the provided $key exists within our instances array, and if it does, we’ll return the instance associated with it. If we can’t find a matching index, then it means we don’t have that instance available, so we’ll throw a meaningful exception. Normally, we’d use a more specific exception, rather than the base ‘Exception’ class, but I’m keeping it simple in this example.

Let’s write some tests to further explain this functionality.

<?php

use Example\Container;  
use PHPUnit\Framework\TestCase;

class ContainerTest extends TestCase  
{
    function test_container_can_be_created()
    {
        $container = new Container;
        $this->assertInstanceOf('Example\Container', $container);
    }

    function test_instance_can_be_bound_and_resolved_from_the_container()
    {
        $instance = new Dummy;
        $container = new Container;
        $container->instance(Dummy::class, $instance);
        $this->assertSame($instance, $container->make(Dummy::class));
    }

    /**
     * @expectedException Exception
     */
    function test_exception_is_thrown_when_instance_is_not_found()
    {
        $container = new Container;
        $container->make('FakeClass');
    }
}

class Dummy {}  

Before we begin looking at the tests, note that we have a dummy class at the bottom of the file, we’ll use instances of this to test our bindings.

In the first test, we simply create a new container instance. You might think that this is an odd one, but it’s one I like to add to any new code. It means that if I’m working on a team, no one will add a constructor parameter to the class without thinking about the consequences of that action. It’s not very important to this example, simply muscle memory.

Next, we have the important test. It’s binding an instance of Dummy inside the container, using the class name Dummy::class as the key. Then, we’re retrieving the value from the container using the make() method and the same key and ensuring that the value we receive is the same instance that we stored (not just equal, but the exact instance that we stored).

Finally, we have a test which attempts to retrieve a binding from the container, without registering anything within it. Essentially, trying to get an instance that it doesn’t have. We assert that we expect an exception.

All three tests pass successfully in this case. We could also add a number of tests around retrieving multiple services, and register different types, but this isn’t an article on testing, so we’ll move along.

What we have now is a service locator. It stores services and retrieves their references on demand. The problem with this implementation is that the instances MUST be created at the time of binding. We instantiate the instances of classes, before putting them into the container. This means that their startup routines will be running, and their initial state will be stored. This could waste CPU cycles and memory. This is extremely wasteful if we consider that not all the services will be used in every request.

It would be much better if the services could be created on demand. When they are needed. We can do this without a controller ourselves, but that means exposing more of our codebase to our configuration values to make sure that they are set up right.

Instead, we can leverage Inversion of Control to achieve what we’re looking for. This is the IoC in IoC container. Essentially, we teach the container how to create services for us. So that it will create them when they are requested, and not as they are registered.

In PHP a language feature called a ‘Closure’ that was added in version 5.3 makes this extremely convenient. (Note that it was still possible before 5.3 using a class to register services, but that had more overhead.)

A closure is a function that we can store as a variable, and hand it around our application as needed. Consider the following closure.

<?php

$dummyResolver = function() {
    return new Dummy;
}

$dummy = $dummyResolver();

In the above example, the instance of Dummy won’t be created until the last line, where the closure is executed. We can use this functionality to add IoC to our container.

Let’s add a new method called bind() to the container, instead of binding instances directly, we’ll store closures in a new class property array called $bindings.

<?php

namespace Example;

class Container  
{
    protected $instances = [];

    protected $bindings = [];

    public function instance($key, $value)
    {
        $this->instances[$key] = $value;

        return $this;
    }

    public function bind($key, $value)
    {
        $this->bindings[$key] = $value;

        return $this;
    }

    public function make($key)
    {
        // Hidden for simplicity.
    }
}

Now we’ve got two methods for storing things in the container. One for storing class instances, and another for storing closures which will be the blueprints for creating new instances. Instances will be held by key within the $instances class property, and closures will be held within $bindings in the same format.

Let’s now update the make() function so that it can retrieve these bindings.

<?php

public function make($key)  
{
    if (array_key_exists($key, $this->instances)) {
        return $this->instances[$key];
    }

    if (array_key_exists($key, $this->bindings)) {
        $resolver = $this->bindings[$key];
        return $resolver();
    }

    throw new \Exception('Unable to resolve binding from container.');
}

Now if we try to resolve a dependency that isn’t stored as an instance, it will check to see if there’s a binding for it. If the binding exists, we’ll execute the closure (creating the instance within) and return that to the user.

As it stands, if we were to call the make() method several times, it would run the closure each time and we’d receive a brand new instance each time the closure runs. This is how Laravel’s bind() method works, however, it also has singleton() which is a variant of this, and instead only runs the closure the first time that binding is requested, and then caches it to supply if the instance is requested again in the future.

We can change our code to easily reflect this ‘singleton’ nature. Let’s have a go. Note that Laravel uses two arrays to store both normal bindings and singleton ones. We’re just going to support singleton instead.

<?php

public function make($key)  
{
    if (array_key_exists($key, $this->instances)) {
        return $this->instances[$key];
    }

    if (array_key_exists($key, $this->bindings)) {
        $resolver = $this->bindings[$key];
        return $this->instances[$key] = $resolver();
    }

    throw new \Exception('Unable to resolve binding from container.');
}

It’s a tiny change, but a huge difference! The first time we look in the $bindings array for a service, we store the result in the $instances array using the same key. Because the container always looks for instances first, any subsequent requests to make() for the same service will result in exactly the same service instance being restored. We can prove this using a test.

<?php

function test_singleton_bindings_can_be_resolved()  
{
    $resolver = function() { return new Dummy; };
    $container = new Container;
    $container->bind(Dummy::class, $resolver);
    $this->assertInstanceOf('Dummy', $container->make(Dummy::class));
    $dummy = $container->make(Dummy::class);
    $this->assertSame($dummy, $container->make(Dummy::class));
}

Here we create a closure that forms a blueprint for creating instances of the Dummy class. We bind() that blueprint into the container using the Dummy::class (results in a string called ‘Dummy’) as the key. First, we assert that the item that we get back from the container is an instance of Dummy, and then we resolve once more, to ensure that both instances are exactly the same.

And with that, we now have an Inversion of Control container. Our container can use closures as blueprints to create and return instances of services for us, it also caches the service, so that it’s only ever created once.

There’s another feature remaining, and that’s the automatic dependency resolution. Let’s use the Laravel container as an example.
If we were to try and resolve the class Foo from the container, it would check for instances and bindings first, but if it doesn’t exist in those collections, it will next look to see if there’s a class available called Foo and return a new instance.

We can extend our make() method to implement this functionality using class_exists(). Let’s try that now.

<?php

public function make($key)  
{
    if (array_key_exists($key, $this->instances)) {
        return $this->instances[$key];
    }

    if (array_key_exists($key, $this->bindings)) {
        $resolver = $this->bindings[$key];
        return $this->instances[$key] = $resolver();
    }

    if (class_exists($key)) {
        return new $key;
    }

    throw new \Exception('Unable to resolve binding from container.');
}

We’ve added a third step to our make method. (Note that this method should probably be broken down using extract-method refactoring, but we’re more interested in the concept than the architecture.)

The third step checks to see if the class exists (within autoload scope) and if it does, it will return a new instance of that class. Let’s use a test to prove this functionality.

<?php

function test_resolve_class_instance_by_name_without_binding()  
{
    $container = new Container;
    $dummy = $container->make(Dummy::class);
    $this->assertInstanceOf('Dummy', $dummy);
}

We create a new container instance, don’t bind anything into it, and then try and make() an instance of Dummy by class name. We use assertInstanceOf() to prove that what we receive is a dummy instance.

Our container can see a class called ‘Dummy’ is available, so instantiates and returns it for us.

That’s dependency resolution covered, but Laravel goes a step further with recursive dependency resolution. Before we can understand this concept, we need to learn about simple and complex classes. Consider the following classes.

<?php

class Foo {}

class Bar {  
    public function __construct(int $value)
    {
        // Do something with $value...
    }
}

In the above example, I would describe ‘Foo’ as a simple class. It doesn’t require any constructor parameters to be instantiated, so it’s really easy for our container to do it for us.

The second class ‘Bar’ could be described as a complex class. It requires an integer constructor parameter to be instantiated. Sure, our container could take a guess at the integer, maybe throw a 5 in there and hope for the best, but that would be a dangerous action. This class shouldn’t be automatically resolved, and if we try to resolve it with our current container we’ll find that it will fail (missing the constructor parameter).

Next, let’s consider the following classes.

<?php

class Bob {}

class Bill {  
    public function __construct(Bob $bob)
    {
        // Do something with $bob.. probably build something...
    }
}

Bob is another simple class, bless him. Bill, however, is a little more complex, but fortunately, not too complex. Bill takes an instance of Bob within its constructor. If Bob is a simple class, then why don’t we just create an instance, and then we can create an instance of Bill, right? That’s the philosophy behind the Laravel container’s dependency resolution. In fact, if Laravel can create dependencies, it will satisfy them all the way down the dependency chain, so long as the top-most class is resolved from the container.

For example:

<?php

class Bob {}

class Bill {  
    public function __construct(Bob $bob) { /* ... */ }
}

class Barry {  
    public function __construct(Bill $bill) { /* ... */ }
}

If you try to resolve Barry from the Laravel container. It will try to create an instance of Bill, but to create Bill, it will first create an instance of Bob. That’s recursive dependency resolution for you.

Right, let’s build this! Things are going to get a little more complicated now. We’re going to start playing with PHP’s reflection classes, and there’s a good chance you might not have used them before. These are a collection of classes that are used to inspect things in PHP at runtime, specifically we’ll be using them to determine the parameters required to instantiate a class.

First, let’s refactor what we have a little because our bind() function would otherwise get pretty big.

<?php

public function make($key)  
{
    if (array_key_exists($key, $this->instances)) {
        return $this->instances[$key];
    }

    if (array_key_exists($key, $this->bindings)) {
        $resolver = $this->bindings[$key];
        return $this->instances[$key] = $resolver();
    }

    if ($instance = $this->autoResolve($key)) {
        return $instance;
    }

    throw new \Exception('Unable to resolve binding from container.');
}

public function autoResolve($key)  
{
    if (class_exists($key)) {
        return new $key;
    }

    return false;
}

We’ve split the auto-resolution stuff out into its own method. The functionality of the class hasn’t changed at all here, it just means that we can focus on the autoResolve() method and thus the code samples in this section can be a little lighter! Of course, we confirmed that the refactor didn’t effect functionality by running our test suite, didn’t we? Ahhh, the wonderful safety-net of test confidence!

Right, let’s jump straight in with the full implementation.

<?php

public function autoResolve($key)  
{
    if (!class_exists($key)) {
        return false;
    }

    $reflectionClass = new \ReflectionClass($key);

    if (!$reflectionClass->isInstantiable()) {
        return false;
    }

    if (!$constructor = $reflectionClass->getConstructor()) {
        return new $key;
    }

    $params = $constructor->getParameters();

    $args = [];

    try {
        foreach($params as $param) {
            $paramClass = $param->getClass()->getName();
            $args[] = $this->make($paramClass);
        }
    } catch (\Exception $e) {
        throw new \Exception('Unable to resolve complex dependencies.');
    }

    return $reflectionClass->newInstanceArgs($args);
}

So if you’re looking at this and thinking “Woop, now he’s lost me.” then don’t worry, we’ll go through it line by line to explain what’s happening.

<?php

if (!class_exists($key)) {  
    return false;
}

So we’ve done this part before. If the class named in the key doesn’t exist, we definitely can’t create it, so we’ll exit early by returning false.

<?php

$reflectionClass = new \ReflectionClass($key);

We’re going to use reflection to examine the class that we intend to instantiate. To do this, we’ll create a new ReflectionClass and pass the name of the class we’re trying to instantiate to its constructor. The $reflectionClass instance contains a wealth of methods that will allow us to inspect the class. You’ll find more about the reflection classes on the PHP API docs.

<?php

if (!$reflectionClass->isInstantiable()) {  
    return false;
}

First we check the isInstantiable() method on the reflection class. If our subject can’t be instantiated, then our container can’t do anything with it, so we’ll exit early.

<?php

if (!$constructor = $reflectionClass->getConstructor()) {  
    return new $key;
}

Next, we use the getConstructor() method of the $reflectionClass to determine whether our subject has a __construct() method. If the class exists, is instantiable, but has no constructor, then we can consider it a simple class. We’ll just return a new instance of that class. That’s a nice result!

Of course, if the class does have a constructor, we need to think about the possibility that it might have parameters.

<?php

$params = $constructor->getParameters();

This one is nice and simple. Using the getParameters() method, we receive an array of ReflectionParameter instances for any parameters that the constructor might have.

You’ll notice that we’re using this method on the $constructor value, that’s because the getConstructor() method of the reflection class returns an instance of ReflectionMethod. So the chain is something like this:

ReflectionClass(classname) -> getConstructor() -> ReflectionMethod -> getParameters() -> ReflectionParameter[]

We’ll make use of those parameters, but first, we need a buffer to save our resolved parameters into.

<?php

$args = [];

That will do nicely. Hopefully, there’s no explanation needed here!

<?php

try {  
    foreach($params as $param) {
        $paramClass = $param->getClass()->getName();
        $args[] = $this->make($paramClass);
    }
} catch (\Exception $e) {
    throw new \Exception('Unable to resolve complex dependencies.');
}

Here’s a slightly bigger chunk of code. We’re going to wrap it in a try, because if any of the parameters can’t be resolved, then we’re going to fail to instantiate our class. We’ll catch the exception thrown by make() and show something more meaningful.

We’ll loop through the array of ReflectionParameter instances, and deal with one at a time. First, we’ll use the method chain of getClass()->getName() to retrieve the type hinted class name of the expected parameter in string format. We’ll then use recursion to resolve that parameter from our container by calling $this->make() on it. Because this a recursive action, the dependency resolution will go as deep as needed to satisfy all dependencies down the chain.

If we resolve the parameter instance, we’ll add it to the $args buffer that we created earlier.

<?php

return $reflectionClass->newInstanceArgs($args);  

Once we’ve resolved all the parameters, assuming we experienced no exceptions, we are able to create an instance of the class by passing the array to the newInstanceArgs() method of the reflection class. This gives us a new instance of the service we were trying to resolve.

Let’s prove that it works by adding a quick test to our test suite. We’ll add a number of new stubs to the bottom of our test file.

<?php

class Foo {}  
class Bar { function __construct(Foo $foo) {} }  
class Baz { function __construct(Bar $bar) {} }  

Here we have a chain of dependencies three levels deep. We’re going to test that we can automatically resolve Baz from the container and that all other dependencies are created for us. Here we go!

<?php

function test_we_can_resolve_dependencies_of_dependencies()  
{
    $container = new Container;
    $baz = $container->make(Baz::class);
    $this->assertInstanceOf('Baz', $baz);
}

By asserting the instance of the resulting value as Baz, we know that the container must have created Bar and Foo as part of the instantiation process.

And there we have it ladies and gents, a fully functional dependency-resolution IoC container. Sure, it’s not as pretty as the one Laravel uses, but that’s got a wealth of increased error handling and quality of life features, however, I think our example serves to show that the automatic-resolution stuff is in fact not ‘magic’, but a clever implementation of the reflection classes.

If you’d like to learn more about dependency injection, or would like to learn about some other topics, just ask a question in the comments! Thanks for reading!