Writing a Simple Dependency Injection Container in PHP: Lessons Learned

Patrick Assoa Adou
5 min readMar 21, 2019
Photo by Ricky Kharawala on Unsplash

Why?

A few weeks ago, I decided to revisit Dependency Injection (DI) in the PHP world and quickly build a simple one. I looked at various article including the one by Mustafa Magdi and that by Andrew Carter, among others. With all the powerful and well know PHP DI containers out there, why go through the trouble? I figured that there must be some lessons to learn.

Before going any further, DI can be defined as:

[A] technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.

Example usage of DI in PHP and in other languages are plenty. Hence, in this article, I will share some lessons learned while building a simple DI container. You can find the container code at https://github.com/kanian/containerx.

Implementation of the DI Container and PSR-11

Initial reaserch led me to read the PSR-11. The PHP community, through the PHP Framework Interop Group (PHP-FIG) has produced the PSR-11 that describes the recommended interface to a PHP DI container. I found out that it is a very simple interface that describes:

  • How to fetch entries from a container
  • The exceptions that are thrown by a container, and
  • Its recommended usage.

All I had to do while implementing my PSR-11 container was to require the psr/container package:

composer require psr/container

and implement its Psr\Container\ContainerInterface , Psr\Container\ContainerExceptionInterface and Psr\Container\NotFoundExceptionInterface .

Here is an example of how I implemented Psr\Container\ContainerInterface:

This is an example implementation of Psr\Container\ContainerExceptionInterface:

And finally one example implementation of Psr\Container\NotFoundExceptionInterface:

The PSR-11 purposefully leaves out details of how to set dependencies on the container. Here is the implementation of my set method to register dependencies on a container taken from Mustafa Magdi:

The Container as an Array

Once adhering to the PSR-11 interface, and thus having ensured interoperability, I realized that I could make my life easier by having the container behave as an array. What I had to do was to implement the PHP ArrayAccess interface:

I could now do things such as:

Auto-wiring, Closures, and Factory methods

As the previous example shows, the container offered auto-wiring. A sample of the implementation:

While many DI containers offer an automatic resolution of dependencies through reflection-based auto-wiring, I found out that allowing the container to use closure factory methods for my user-defined classes sped up the dependency resolution process.

To reinforce that intuition, I implemented a small number of performance tests based on an article from Tom Butler’s where he compares the performances of a number of PHP DI containers, including his own Dice container. Here you can see code I wrote to run a deep object graph resolution performance test when using the container exclusively with closure-based factory methods:

From his article, the fastest container was Tom Bulter’s Dice at 0.914 s. Refraining from using auto-wiring and using only closures instead, gave me results consistently near 0.48 s.

When using closures, I had to trade code terseness for speed. However, the closures allowed me to explicitly specify how my dependencies were to be resolved. Not to mention that there is a school of thought that views auto-wiring as a code smell.

To enable closure factories, I found it simpler to apply the closure to the container itself, letting subsequent dependencies resolve themselves through other closures in a process of factory composition. Please note that auto-wiring is still available when the dependency entry is not a closure:

The Container, Testing, and Singletons

Dependency Injection lets us replace external objects used by a consuming object with different implementations of those external objects, if we are careful enough to define interfaces to which both the external objects and the replacing objects adhere. I personally prefer constructor injection to achieve that result.

This was particularly beneficial when using the container during unit testing. For example, suppose we have the following domain service class that we wanted to unit test:

We could test it in a manner similar to the following code sample, using phpunit and Prophecy:

Now every time my customer service needed a customer repository, it was instead given a mock by Prophecy, that had prediction capabilities, and could tell me what I wanted to know about how my CustomerService class behaved. However, something went wrong here.

I noted that each time the container resolved my customer repository interface, it would create a new mock object that was not the same one nor of the same class as the one I initially registered with the container. I needed a way to specify which ones of my dependencies were supposed to be Singletons.

It is in solving this issue that I first thought of singletonize as generic singleton mechanism. However, type hinting was lost with singletonize. Hence I thought of a more robust solution, that I describe in a more recent article, and that uses ASTs and PHP-Parser. Briefly, the solution is embodied by the makeProphecy method below:

A factory method for a the Prophecy mock is registered as a singleton with our container. It is the same mock object that would always be returned by the container now:

Finally, my mock could properly record method calls on it. The code for the generic Singleton can be found at https://github.com/kanian/patterns.

Conclusion

Besides learning a bit more about PHP reflection that I would have expected, working on this container opened my eyes on how the Singleton pattern can be linked to unit testing and mocking. Moreover, it helped me find a quantitative answer as to why I should favor factory based dependency resolution as opposed to auto-wiring.

--

--