Creating your own dependency injection container in PHP

Published by at 2nd February 2019 8:45 pm

Dependency injection can be a difficult concept to understand in the early stages. Even when you're using it all the time, it can often seem like magic. However, it's really not all that complicated once you actually get into the nuts and bolts of it, and building your own container is a good way to learn more about how it works and how to use it.

In this tutorial, I'll walk you through creating a simple, minimal dependency injection container, using PHPSpec as part of a TDD workflow. While the end result isn't necessarily something I'd be happy using in a production environment, it's sufficient to understand the basic concept and make it feel less like a black box. Our container will be called Ernie (if you want to know why, it's a reference to a 90's era video game that had a character based on Eric Cantona called Ernie Container).

The first thing we need to do is set up our dependencies. Our container will implement PSR-11, so we need to include the interface that defines that. We'll also use PHP CodeSniffer to ensure code quality, and PHPSpec for testing. Your composer.json should look something like this:

1{
2 "name": "matthewbdaly/ernie",
3 "description": "Simple DI container",
4 "type": "library",
5 "require-dev": {
6 "squizlabs/php_codesniffer": "^3.3",
7 "phpspec/phpspec": "^5.0",
8 "psr/container": "^1.0"
9 },
10 "license": "MIT",
11 "authors": [
12 {
13 "name": "Matthew Daly",
14 "email": "450801+matthewbdaly@users.noreply.github.com"
15 }
16 ],
17 "require": {},
18 "autoload": {
19 "psr-4": {
20 "Matthewbdaly\\Ernie\\": "src/"
21 }
22 }
23}

We also need to put this in our phpspec.yml file:

1suites:
2 test_suite:
3 namespace: Matthewbdaly\Ernie
4 psr4_prefix: Matthewbdaly\Ernie

With that done, we can start working on our implementation.

Creating the exceptions

The PSR-11 specification defines two interfaces for exceptions, which we will implement before actually moving on to the container itself. The first of these is Psr\Container\ContainerExceptionInterface. Run the following command to create a basic spec for the exception:

$ vendor/bin/phpspec desc Matthewbdaly/Ernie/Exceptions/ContainerException

The generated specification for it at spec/Exceptions/ContainerExceptionSpec.php will look something like this:

1<?php
2
3namespace spec\Matthewbdaly\Ernie;
4
5use Matthewbdaly\Ernie\ContainerException;
6use PhpSpec\ObjectBehavior;
7use Prophecy\Argument;
8
9class ContainerExceptionSpec extends ObjectBehavior
10{
11 function it_is_initializable()
12 {
13 $this->shouldHaveType(ContainerException::class);
14 }
15}

This is not sufficient for our needs. Our exception must also implement two interfaces:

  • Throwable
  • Psr\Container\ContainerExceptionInterface

The former can be resolved by inheriting from Exception, while the latter doesn't require any additional methods. Let's expand our spec to check for these:

1<?php
2
3namespace spec\Matthewbdaly\Ernie\Exceptions;
4
5use Matthewbdaly\Ernie\Exceptions\ContainerException;
6use PhpSpec\ObjectBehavior;
7use Prophecy\Argument;
8
9class ContainerExceptionSpec extends ObjectBehavior
10{
11 function it_is_initializable()
12 {
13 $this->shouldHaveType(ContainerException::class);
14 }
15
16 function it_implements_interface()
17 {
18 $this->shouldImplement('Psr\Container\ContainerExceptionInterface');
19 }
20
21 function it_implements_throwable()
22 {
23 $this->shouldImplement('Throwable');
24 }
25}

Now run the spec and PHPSpec will generate the boilerplate exception for you:

1$ vendor/bin/phpspec run
2Matthewbdaly/Ernie/Exceptions/ContainerException
3 11 - it is initializable
4 class Matthewbdaly\Ernie\Exceptions\ContainerException does not exist.
5
6Matthewbdaly/Ernie/Exceptions/ContainerException
7 16 - it implements interface
8 class Matthewbdaly\Ernie\Exceptions\ContainerException does not exist.
9
10Matthewbdaly/Ernie/Exceptions/ContainerException
11 21 - it implements throwable
12 class Matthewbdaly\Ernie\Exceptions\ContainerException does not exist.
13
14 100% 3
151 specs
163 examples (3 broken)
1723ms
18
19
20 Do you want me to create `Matthewbdaly\Ernie\Exceptions\ContainerException`
21 for you?
22 [Y/n]
23y
24Class Matthewbdaly\Ernie\Exceptions\ContainerException created in /home/matthew/Projects/ernie-clone/src/Exceptions/ContainerException.php.
25
26Matthewbdaly/Ernie/Exceptions/ContainerException
27 16 - it implements interface
28 expected an instance of Psr\Container\ContainerExceptionInterface, but got
29 [obj:Matthewbdaly\Ernie\Exceptions\ContainerException].
30
31Matthewbdaly/Ernie/Exceptions/ContainerException
32 21 - it implements throwable
33 expected an instance of Throwable, but got
34 [obj:Matthewbdaly\Ernie\Exceptions\ContainerException].
35
36 33% 66% 3
371 specs
383 examples (1 passed, 2 failed)
3936ms

It's failing, but we expect that. We need to update our exception to extend the base PHP exception, and implement Psr\Container\ContainerExceptionInterface. Let's do that now:

1<?php
2
3namespace Matthewbdaly\Ernie\Exceptions;
4
5use Psr\Container\ContainerExceptionInterface;
6use Exception;
7
8class ContainerException extends Exception implements ContainerExceptionInterface
9{
10}

Let's re-run the spec:

1$ vendor/bin/phpspec run
2 100% 3
31 specs
43 examples (3 passed)
524ms

The second exception we need to implement is Psr\Container\NotFoundExceptionInterface and it's a similar story. Run the following command to create the spec:

$ vendor/bin/phpspec desc Matthewbdaly/Ernie/Exceptions/NotFoundException

Again, the spec needs to be amended to verify that it's a throwable and implements the required interface:

1<?php
2
3namespace spec\Matthewbdaly\Ernie\Exceptions;
4
5use Matthewbdaly\Ernie\Exceptions\NotFoundException;
6use PhpSpec\ObjectBehavior;
7use Prophecy\Argument;
8
9class NotFoundExceptionSpec extends ObjectBehavior
10{
11 function it_is_initializable()
12 {
13 $this->shouldHaveType(NotFoundException::class);
14 }
15
16 function it_implements_interface()
17 {
18 $this->shouldImplement('Psr\Container\NotFoundExceptionInterface');
19 }
20
21 function it_implements_throwable()
22 {
23 $this->shouldImplement('Throwable');
24 }
25}

For the sake of brevity I've left out the output, but if you run vendor/bin/phpspec run you'll see it fail due to the fact that the generated class doesn't implement the required interfaces. Amend src/Exceptions/NotFoundException as follows:

1<?php
2
3namespace Matthewbdaly\Ernie\Exceptions;
4
5use Psr\Container\NotFoundExceptionInterface;
6use Exception;
7
8class NotFoundException extends Exception implements NotFoundExceptionInterface
9{
10}

Running vendor/bin/phpspec run should now see it pass. Now let's move on to the container class...

Building the container

Run the following command to create the container spec:

$ vendor/bin/phpspec desc Matthewbdaly/Ernie/Container

However, the default generated spec isn't sufficient. We need to check it implements the required interface:

1<?php
2
3namespace spec\Matthewbdaly\Ernie;
4
5use Matthewbdaly\Ernie\Container;
6use PhpSpec\ObjectBehavior;
7use Prophecy\Argument;
8
9class ContainerSpec extends ObjectBehavior
10{
11 function it_is_initializable()
12 {
13 $this->shouldHaveType(Container::class);
14 }
15
16 function it_implements_interface()
17 {
18 $this->shouldImplement('Psr\Container\ContainerInterface');
19 }
20}

Now, if we run PHPSpec, we'll generate our class:

1$ vendor/bin/phpspec run
2Matthewbdaly/Ernie/Container
3 11 - it is initializable
4 class Matthewbdaly\Ernie\Container does not exist.
5
6Matthewbdaly/Ernie/Container
7 16 - it implements interface
8 class Matthewbdaly\Ernie\Container does not exist.
9
10 75% 25% 8
113 specs
128 examples (6 passed, 2 broken)
13404ms
14
15
16 Do you want me to create `Matthewbdaly\Ernie\Container` for you?
17 [Y/n]
18y
19Class Matthewbdaly\Ernie\Container created in /home/matthew/Projects/ernie-clone/src/Container.php.
20
21Matthewbdaly/Ernie/Container
22 16 - it implements interface
23 expected an instance of Psr\Container\ContainerInterface, but got
24 [obj:Matthewbdaly\Ernie\Container].
25
26 87% 12% 8
273 specs
288 examples (7 passed, 1 failed)
2940ms

Now, as we can see, this class doesn't implement the interface. Let's remedy that:

1<?php
2
3namespace Matthewbdaly\Ernie;
4
5use Psr\Container\ContainerInterface;
6
7class Container implements ContainerInterface
8{
9}

Now, if we run the tests, they should fail because the class needs to add the required methods:

1$ vendor/bin/phpspec run
2✘ Fatal error happened while executing the following
3 it is initializable
4 Class Matthewbdaly\Ernie\Container contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (Psr\Container\ContainerInterface::get, Psr\Container\ContainerInterface::has) in /home/matthew/Projects/ernie-clone/src/Container.php on line 7

If you use an editor or IDE that allows you to implement an interface automatically, you can run it to add the required methods. I use PHPActor with Neovim, and used the option in the Transform menu to implement the contract:

1<?php
2
3namespace Matthewbdaly\Ernie;
4
5use Psr\Container\ContainerInterface;
6
7class Container implements ContainerInterface
8{
9 /**
10 * {@inheritDoc}
11 */
12 public function get($id)
13 {
14 }
15
16 /**
17 * {@inheritDoc}
18 */
19 public function has($id)
20 {
21 }
22}

Running vendor/bin/phpspec run should now make the spec pass, but the methods don't actually do anything yet. If you read the spec for PSR-11, you'll see that has() returns a boolean to indicate whether a class can be instantiated or not, while get() will either return an instance of the specified class, or throw an exception. We will add specs that check that built-in classes can be returned by both, and unknown classes display the expected behaviour. We'll do both at once, because in both cases, the functionality to actually resolve the required class will be deferred to a single resolver method, and these methods will not do all that much as a result:

1 function it_has_simple_classes()
2 {
3 $this->has('DateTime')->shouldReturn(true);
4 }
5
6 function it_does_not_have_unknown_classes()
7 {
8 $this->has('UnknownClass')->shouldReturn(false);
9 }
10
11 function it_can_get_simple_classes()
12 {
13 $this->get('DateTime')->shouldReturnAnInstanceOf('DateTime');
14 }
15
16 function it_returns_not_found_exception_if_class_cannot_be_found()
17 {
18 $this->shouldThrow('Matthewbdaly\Ernie\Exceptions\NotFoundException')
19 ->duringGet('UnknownClass');
20 }

These tests verify that:

  • has() returns true when called with the always-present DateTime class
  • has() returns false for the undefined UnknownClass
  • get() successfully instantiates an instance of DateTime
  • get() throws an exception if you try to instantiate the undefined UnknownClass

Running the specs will raise errors:

1$ vendor/bin/phpspec run
2Matthewbdaly/Ernie/Container
3 21 - it has simple classes
4 expected true, but got null.
5
6Matthewbdaly/Ernie/Container
7 26 - it does not have unknown classes
8 expected false, but got null.
9
10Matthewbdaly/Ernie/Container
11 31 - it can get simple classes
12 expected an instance of DateTime, but got null.
13
14Matthewbdaly/Ernie/Container
15 36 - it returns not found exception if class cannot be found
16 expected to get exception / throwable, none got.
17
18 66% 33% 12
193 specs
2012 examples (8 passed, 4 failed)
2198ms

Let's populate these empty methods:

1<?php
2
3namespace Matthewbdaly\Ernie;
4
5use Psr\Container\ContainerInterface;
6use Matthewbdaly\Ernie\Exceptions\NotFoundException;
7use ReflectionClass;
8use ReflectionException;
9
10class Container implements ContainerInterface
11{
12 /**
13 * {@inheritDoc}
14 */
15 public function get($id)
16 {
17 $item = $this->resolve($id);
18 return $this->getInstance($item);
19 }
20
21 /**
22 * {@inheritDoc}
23 */
24 public function has($id)
25 {
26 try {
27 $item = $this->resolve($id);
28 } catch (NotFoundException $e) {
29 return false;
30 }
31 return $item->isInstantiable();
32 }
33
34 private function resolve($id)
35 {
36 try {
37 return (new ReflectionClass($id));
38 } catch (ReflectionException $e) {
39 throw new NotFoundException($e->getMessage(), $e->getCode(), $e);
40 }
41 }
42
43 private function getInstance(ReflectionClass $item)
44 {
45 return $item->newInstance();
46 }
47}

As you can see, both the has() and get() methods need to resolve a string ID to an actual class, so that common functionality is stored in a private method called resolve(). This uses the PHP Reflection API to resolve the class name to an actual class. We pass the string ID into a constructor of ReflectionClass, and the resolve() method will either return the created instance of ReflectionClass, or throw an exception.

For the uninitiated, ReflectionClass allows you to reflect on the object whose fully qualified class name is passed to the constructor, in order to interact with that class programmatically. The methods we will use include:

  • isInstantiable - confirms whether or not the class can be instantiated (for instance, traits and abstract classes can't)
  • newInstance - creates a new instance of the item in question, as long as it has no dependencies in the constructor
  • newInstanceArgs - creates a new instance, using the arguments passed in
  • getConstructor - allows you to get information about the constructor

The Reflection API is pretty comprehensive, and I would recommend reading the documentation linked to above if you want to know more.

For the has() method, we check that the resolved class is instantiable, and return the result of that. For the get() method, we use getInstance() to instantiate the item and return that, throwing an exception if that fails.

Registering objects

In its current state, the container doesn't allow you to set an item. To be useful, we need to be able to specify that an interface or string should be resolved to a given class, or for cases where we need to pass in scalar parameters, such as a database object, to specify how a concrete instance of that class should be instantiated. To that end, we'll create a new set() public method that will allow a dependency to be set. Here are the revised specs including this:

1<?php
2
3namespace spec\Matthewbdaly\Ernie;
4
5use Matthewbdaly\Ernie\Container;
6use PhpSpec\ObjectBehavior;
7use Prophecy\Argument;
8use DateTime;
9
10class ContainerSpec extends ObjectBehavior
11{
12 function it_is_initializable()
13 {
14 $this->shouldHaveType(Container::class);
15 }
16
17 function it_implements_interface()
18 {
19 $this->shouldImplement('Psr\Container\ContainerInterface');
20 }
21
22 function it_has_simple_classes()
23 {
24 $this->has('DateTime')->shouldReturn(true);
25 }
26
27 function it_does_not_have_unknown_classes()
28 {
29 $this->has('UnknownClass')->shouldReturn(false);
30 }
31
32 function it_can_get_simple_classes()
33 {
34 $this->get('DateTime')->shouldReturnAnInstanceOf('DateTime');
35 }
36
37 function it_returns_not_found_exception_if_class_cannot_be_found()
38 {
39 $this->shouldThrow('Matthewbdaly\Ernie\Exceptions\NotFoundException')
40 ->duringGet('UnknownClass');
41 }
42
43 function it_can_register_dependencies()
44 {
45 $toResolve = new class {
46 };
47 $this->set('Foo\Bar', $toResolve)->shouldReturn($this);
48 }
49
50 function it_can_resolve_registered_dependencies()
51 {
52 $toResolve = new class {
53 };
54 $this->set('Foo\Bar', $toResolve);
55 $this->get('Foo\Bar')->shouldReturnAnInstanceOf($toResolve);
56 }
57
58 function it_can_resolve_registered_invokable()
59 {
60 $toResolve = new class {
61 public function __invoke() {
62 return new DateTime;
63 }
64 };
65 $this->set('Foo\Bar', $toResolve);
66 $this->get('Foo\Bar')->shouldReturnAnInstanceOf('DateTime');
67 }
68
69 function it_can_resolve_registered_callable()
70 {
71 $toResolve = function () {
72 return new DateTime;
73 };
74 $this->set('Foo\Bar', $toResolve);
75 $this->get('Foo\Bar')->shouldReturnAnInstanceOf('DateTime');
76 }
77
78 function it_can_resolve_if_registered_dependencies_instantiable()
79 {
80 $toResolve = new class {
81 };
82 $this->set('Foo\Bar', $toResolve);
83 $this->has('Foo\Bar')->shouldReturn(true);
84 }
85}

This needs to handle quite a few scenarios, so there are several tests we have in place. These verify that:

  • The set() method returns an instance of the container class, to allow for method chaining
  • When a dependency is set, calling get() returns an instance of that class
  • When a concrete class that has the __invoke() magic method set is passed to set(), it is invoked and the response returned.
  • When the value passed through is a callback, the callback is resolved and the response returned
  • When a dependency is set, calling has() for it returns the right value

Note that we use anonymous classes for testing - I've written about these before and they're very useful in this context because they allow us to create a simple class inline for testing purposes.

Running the specs should result in us being prompted to generate the set() method, and failing afterwards:

1$ vendor/bin/phpspec run
2Matthewbdaly/Ernie/Container
3 42 - it can register dependencies
4 method Matthewbdaly\Ernie\Container::set not found.
5
6Matthewbdaly/Ernie/Container
7 49 - it can resolve registered dependencies
8 method Matthewbdaly\Ernie\Container::set not found.
9
10Matthewbdaly/Ernie/Container
11 57 - it can resolve registered invokable
12 method Matthewbdaly\Ernie\Container::set not found.
13
14Matthewbdaly/Ernie/Container
15 68 - it can resolve registered callable
16 method Matthewbdaly\Ernie\Container::set not found.
17
18Matthewbdaly/Ernie/Container
19 77 - it can resolve if registered dependencies instantiable
20 method Matthewbdaly\Ernie\Container::set not found.
21
22 70% 29% 17
233 specs
2417 examples (12 passed, 5 broken)
25316ms
26
27 Do you want me to create `Matthewbdaly\Ernie\Container::set()` for you?
28 [Y/n]
29y
30 Method Matthewbdaly\Ernie\Container::set() has been created.
31
32Matthewbdaly/Ernie/Container
33 42 - it can register dependencies
34 expected [obj:Matthewbdaly\Ernie\Container], but got null.
35
36Matthewbdaly/Ernie/Container
37 49 - it can resolve registered dependencies
38 exception [exc:Matthewbdaly\Ernie\Exceptions\NotFoundException("Class Foo\Bar does not exist")] has been thrown.
39
40Matthewbdaly/Ernie/Container
41 57 - it can resolve registered invokable
42 exception [exc:Matthewbdaly\Ernie\Exceptions\NotFoundException("Class Foo\Bar does not exist")] has been thrown.
43
44Matthewbdaly/Ernie/Container
45 68 - it can resolve registered callable
46 exception [exc:Matthewbdaly\Ernie\Exceptions\NotFoundException("Class Foo\Bar does not exist")] has been thrown.
47
48Matthewbdaly/Ernie/Container
49 77 - it can resolve if registered dependencies instantiable
50 expected true, but got false.
51
52 70% 11% 17% 17
533 specs
5417 examples (12 passed, 2 failed, 3 broken)
5590ms

First, we need to set up the set() method properly, and define a property to contain the stored services:

1 private $services = [];
2
3 public function set(string $key, $value)
4 {
5 $this->services[$key] = $value;
6 return $this;
7 }

This fixes the first spec, but the resolver needs to be amended to handle cases where the ID is set manually:

1 private function resolve($id)
2 {
3 try {
4 $name = $id;
5 if (isset($this->services[$id])) {
6 $name = $this->services[$id];
7 if (is_callable($name)) {
8 return $name();
9 }
10 }
11 return (new ReflectionClass($name));
12 } catch (ReflectionException $e) {
13 throw new NotFoundException($e->getMessage(), $e->getCode(), $e);
14 }
15 }

This will allow us to resolve classes set with set(). However, we also want to resolve any callables, such as callbacks or classes that implement the __invoke() magic method, which means that sometimes resolve() will return the result of the callable instead of an instance of ReflectionClass. Under those circumstances we should return the item directly:

1 public function get($id)
2 {
3 $item = $this->resolve($id);
4 if (!($item instanceof ReflectionClass)) {
5 return $item;
6 }
7 return $this->getInstance($item);
8 }

Note that because the __invoke() method is automatically called in any concrete class specified in the second argument to set(), it's only possible to resolve classes that define an __invoke() method if they are passed in as string representations. The following PsySh session should make it clear what this means:

1>>> use Matthewbdaly\Ernie\Container;
2>>> $c = new Container;
3=> Matthewbdaly\Ernie\Container {#2307}
4>>> class TestClass { public function __invoke() { return "Called"; }}
5>>> $c->get('TestClass');
6=> TestClass {#2319}
7>>> $c->set('Foo\Bar', 'TestClass');
8=> Matthewbdaly\Ernie\Container {#2307}
9>>> $c->get('Foo\Bar');
10=> TestClass {#2309}
11>>> $c->set('Foo\Bar', new TestClass);
12=> Matthewbdaly\Ernie\Container {#2307}
13>>> $c->get('Foo\Bar');
14=> "Called"

As you can see, if we pass in the fully qualified class name of a class that defines an __invoke() method, it can be resolved as expected. However, if we pass a concrete instance of it to set(), it will be called and will return the response from that. This may not be the behaviour you want for your own container.

According to this issue on the PHP League's Container implementation, it was also an issue for them, so seeing as this is just a toy example I'm not going to lose any sleep over it. Just something to be aware of if you use this post as the basis for writing your own container.

Resolving dependencies

One thing is missing from our container. Right now it should be able to instantiate pretty much any class that has no dependencies, but these are quite firmly in the minority. To be useful, a container should be able to resolve all of the dependencies for a class automatically.

Let's add a spec for that:

1 function it_can_resolve_dependencies()
2 {
3 $toResolve = get_class(new class(new DateTime) {
4 public $datetime;
5 public function __construct(DateTime $datetime)
6 {
7 $this->datetime = $datetime;
8 }
9 });
10 $this->set('Foo\Bar', $toResolve);
11 $this->get('Foo\Bar')->shouldReturnAnInstanceOf($toResolve);
12 }

Here we have to be a bit crafty. Anonymous classes are defined and instantiated at the same time, so we can't pass it in as an anonymous class in the test. Instead, we call the anonymous class and get its name, then set that as the second argument to set(). Then we can verify that the returned object is an instance of the same class.

Running this throws an error:

1$ vendor/bin/phpspec run
2Matthewbdaly/Ernie/Container
3 86 - it can resolve dependencies
4 exception [err:ArgumentCountError("Too few arguments to function class@anonymous::__construct(), 0 passed and exactly 1 expected")] has been thrown.
5
6 94% 18
73 specs
818 examples (17 passed, 1 broken)
960ms

This is expected. Our test class accepts an instance of DateTime in the constructor as a mandatory dependency, so instantiating it fails. We need to update the getInstance() method so that it can handle pulling in any dependencies:

1 private function getInstance(ReflectionClass $item)
2 {
3 $constructor = $item->getConstructor();
4 if (is_null($constructor) || $constructor->getNumberOfRequiredParameters() == 0) {
5 return $item->newInstance();
6 }
7 $params = [];
8 foreach ($constructor->getParameters() as $param) {
9 if ($type = $param->getType()) {
10 $params[] = $this->get($type->getName());
11 }
12 }
13 return $item->newInstanceArgs($params);
14 }

Here, we use the Reflection API to get the constructor. If there's no constructor, or it has no required parameters, we just return a new instance of the reflected class as before.

Otherwise, we loop through the required parameters. For each parameter, we get the string representation of the type specified for that parameter, and retrieve an instance of it from the container. Afterwards, we use those parameters to instantiate the object.

Let's run the specs again:

1$ vendor/bin/phpspec run
2 100% 18
33 specs
418 examples (18 passed)
551ms

Our container is now complete. We can:

  • Resolve simple classes out of the box
  • Set arbitrary keys to resolve to particular classes, or the result of callables, so as to enable mapping interfaces to concrete implementations, or resolve classes that require specific non-object parameters, such as PDO
  • Resolve complex classes with multiple dependencies

Not too bad for just over 100 lines of PHP...

Final thoughts

As I've said, this is a pretty minimal example of a dependency injection container, and I wouldn't advise using this in production when there are so many existing, mature solutions available. I have no idea how the performance would stack up against existing solutions, or whether there are any issues with it, and quite frankly that's besides the point - this is intended as a learning exercise to understand how dependency injection containers in general work, not as an actual useful piece of code for production. If you want an off-the-shelf container, I'd point you in the direction of league/container, which has served me well.

You can find the code for this tutorial on GitHub, so if you have any problems, you should take a look there to see where the problem lies. Of course, if you go on to create your own kick-ass container based on this, do let me know!