Explaining PHP dependency injection with stories — Episode 02

Let’s use a dependency injection container

Tarek Djebali
4 min readJun 15, 2020
Photo by Muradi on Unsplash

Quick reminder

If you missed the previous episode of the story … I was fired. No big deal. I have a great idea! I’ll launch my own startup!

➡️ Go to the main part if you are not interested in the story.

The product (aka my great idea)

Do you think it’s related to the cover image? Well! Not really. But you’ll get the point later.

My secret plan is to get back all those PHP developers that switched to Javascript 😈. So I decided to create a micro-framework inspired by Express.

The requirement was to have a code that looks as close as possible to Express. Something like the following:

Phase 1: Design the product

If we analyze the code above we have:

  1. Line 5: Instantiate the application.
  2. Line 7: Define a route with the corresponding callback.
  3. Line 15: Handle the request.

Use cases:

  • Performing a GET request on “ ∕ ” will return a “Hello world” response.
  • Performing a GET request on “ ∕ hello/Bob” will return a “Hello Bob” response.
  • Performing a GET request on a route that is not defined will return a 404 response.

We can clearly see a pattern in the use cases:

The road from request to response

Now that we have a clearer view of the functional requirements, we can start thinking about technical implementation.

We need 2 components here:

  1. A routing system: it collects routes definitions and their associated actions.
  2. A kernel: it handles the incoming request and returns the proper response from the routes collected.

Phase 2: The technical choices

With that in mind, I know that would be an easy task. There are some well-known packages that can do the dirty work for me. My initial choice went to Http-kernel and routing component from Symfony. But in a startup world, I need a faster solution.

Luckily for me, FastRoute is the perfect candidate. It combines all my needs in a single package.

After a few hours of work, I ended up with an MVP. You check the code on Github.

Time for growth

Quickly after the first release, I realised that I need a system that exposes external services inside the callbacks.

The most naive approach is shown below :

A naive approach

A better solution would be:

inject the service in the callback via use()

But what if we want a cleaner and most flexible way to achieve this. This is where a dependency injection container comes into play.

What is a DI container?

A Dependency Injection Container is an object that knows how to instantiate and configure objects. And to be able to do its job, it needs to knows about the constructor arguments and the relationships between the objects.

Source: http://fabien.potencier.org/do-you-need-a-dependency-injection-container.html

In other words, a DI container is a collection of instantiated objects. The simplest implementation would be as follow:

As you can see, this simple implementation is very limited and not very useful (Also not compliant with PHP-FIG PSR-11). What happens if we have a hundred of services with dependencies to each other? Good luck!!!

Hopefully, we can find some powerful packages that provide a more complete implementation:

Adding a DI container to the micro-framework

For the sake of simplicity, I choose to use PHP-di package. You can follow the steps I did to achieve my goal.

1. Setup the project

git clone https://github.com/tarekdj/micro-framework-example.git
cd micro-framework-example
composer install
cp .env.example .env

2. Create a new branch

git checkout -b feat/di-container

3. Install php-di/php-di package

composer require php-di/php-di

4. Add the container:

First, we inject the container in the constructor of the Framework class as follow (See changes on GitHub):

Then, we update the Application class to use the container (See all changes on GitHub):

5. Expose the container

This task is easy as appending the callback arguments (See changes):

6. Extend the container

The goal here is to add services in the container and use them inside our callbacks as shown in the code snippet below:

So I updated the Framework class as follow:

  1. Add a $externalServices property to the class. This is an array that will collect the services.
/**
* @var array
*/
protected $externalServices = [];

2. Create a method that collects custom services

/**
* @param string $key
* @param $definition
* @throws \Exception
*/
public function useService(string $key, $definition)
{
// Prevent from altering default services.
if (in_array($key, ['request', 'response', 'current_request'])) {
throw new \Exception('Cannot override core service.');
}

$this->externalServices[$key] = $definition;
}

3. Build the container during the framework booting phase

public function boot()
{
// build the container.
foreach ($this->externalServices as $key => $definition) {
$this->container->set($key, $definition);
}

// Build the router.
$this->buildRoutes();
}

You can see the full changes on GitHub.

It’s a wrap!

In this article, I tried to show you how to use a dependency injection container. This is a minimalist example that showcases the bare minimum usage of a container.

In the next article, we will go deeper inside the container and explore some advanced stuff.

PS: Now, I hope that you see the point of the cover image 😉

--

--