How to create a psr11 dependency injection container in php
Tags: php, dependencyinjection, psr, oop
Published: 2020-09-07 12:53:47

Dependency Injection, such a fancy name haa, I'm not going to explain what dependency injection is, the fact that you're here means you already know that. If not check out this [article about dependency injection](https://www.getopensocial.com/blog/open-source-technology/dependency-injection-php). In this article, I'm going to show you how to implement a psr11 compliant **dependency injection container**.

#### First of what is a container

According to [Dependency Injection, Principles, Practices, And Patterns](https://www.manning.com/books/dependency-injection-principles-practices-patterns?a_aid=DNJ&a_bid=aebba5c9), a dependency injection container is
> a software library that provides DI functionality and allows automating many of the tasks involved in Object Composition, Interception, and Lifetime Management. DI Containers are also known as Inversion of Control (IoC) Containers

In this article, we're going to implement [psr11](https://www.php-fig.org/psr/psr-11/) interfaces for dependency injection. The first thing you need to do is go to your project folder and install the psr11 packages
```sh
composer require psr/psr-container
```
After it's done downloading the packages open your favorite text editor, I'm using atom but you can use whatever you like.
Open the ``` composer.json ``` file and enter the following lines of code
```json
autoload:{
psr-4: {
"Flixtechs\\": "src/"
}
}
```
Create a new file and call it ``` index.php ``` and put following
```php
<?php

//add composer's autoloader
require 'vendor/autoload.php';
```
In the project, folder create a new file ``` src/Container.php ``` and add the following lines of code
```php
<?php

namespace Flixtechs;

use Psr\Container\ContainerInterface;

class Container implements ContainerInterface 
{
}
```
We need a way to keep track of registered entries in the container as key-value pairs. Add the entries property to the class
```php
/**
 * Store all entries in the container
 */
 protected $entries = [];
 
/**
 * Store a list of instantiated singleton classes
 */ 
 protected $instances = [];

/**
 * Rules used to resolve the classes
 */
protected $rules = [];
```

Now we need the implement the ```get() ``` method of psr11 interfaces add this to the body of the ```Container``` class
```php
/**
 * Finds an entry of the container by its identifier and returns it.
 *
 * @param string $id Identifier of the entry to look for.
 *
 * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
 * @throws ContainerExceptionInterface Error while retrieving the entry.
 *
 * @return mixed Entry.
 */
public function get($id)
{
 if (!$this->has($id)) {
  $this->set($id);
 }
 if ($this->entries[$id] instanceof \Closure || is_callable($this->entries[$id])) {
  return $this->entries[$id]($this);
 }
 if (isset($this->rules['shared']) && in_array($id,   $this->rules['shared'])) {
  return $this->singleton($id);
 }
 return $this->resolve($id);
}
```

The method takes an ``` $id ``` and first checks if it's in the container if not it adds it. If the value  in the entries is a closure it calls the closure that will resolve the object we want. Next, it checks if the ``` $id ``` is in the ``` $shared ``` array, that is it should be a singleton class and calls the singleton method which we'll see in a moment. Finally, if the above conditions are not met it calls its resolve method to get the class.
Let's take a look into the ``` has() ``` method 
```php
/**
 * Returns true if the container can return an entry for the given identifier.
 * Returns false otherwise.
 *
 * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
 * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
 *
 * @param string $id Identifier of the entry to look for.    
 * @return bool
 */
public function has($id)
{
	return isset($this->entries[$id]);
}
```

This method simply checks if the given id is set in  the ``` $entries ``` array.
Next is the ``` set() ``` method
```php
public function set($abstract, $concrete = null)
{
	if(is_null($concrete)) {
	$concrete = $abstract;
	}
	$this->entries[$abstract] = $concrete;
}
```

The set method takes 2 arguments abstract which is the id and the concrete. The concrete can be a closure or full class name.

Now let us move to the ``` resolve() ``` method where the "magic" happens
```php
/**
 * Resolves a class name and creates its instance with dependencies
 * @param string $class The class to resolve
 * @return object The resolved instance
 * @throws Psr\Container\ContainerExceptionInterface when the class cannot be instantiated
 */
public function resolve($alias)
{
	$reflector = $this->getReflector($alias);
	$constructor = $reflector->getConstructor();
	if ($reflector->isInterface()) {
    	return $this->resolveInterface($reflector);
	}
	if (!$reflector->isInstantiable()) {
		throw new ContainerException(
                "Cannot inject {$reflector->getName()} to {$class} because it cannot be instantiated"
          );
	}
	if (null === $constructor) {
		return $reflector->newInstance();
	}
	$args = $this->getArguments($alias, $constructor);
	return $reflector->newInstanceArgs($args);
}
```

This method takes an id as an argument and tries to instantiate the class. 
Here we are using the [Reflection API](https://www.php.net/manual/en/book.reflection.php) to help us resolve the classes. The first call to ``` getReflector() ``` returns to the reflection of the given id. Next, we get the constructor of the class by calling the ``` getConstructor() ``` method of the reflector. Then we check if the reflected class is an Interface then call the method to resolve a class from a type hinted interface which we'll see in a moment.
Next, it throws an exception if the reflected class is not instantiable. If the class does not have a constructor we simply return its instance.
Next, we get all the arguments by calling the ``` getArguments() ``` method of our container. Then finally return a new instance with the arguments by calling the ``` newInstanceArgs($args) ``` method of the reflector.

The ``` getReflector() ``` method
```php
public function getReflector($alias)
{
	$class = $this->entries[$alias];
    try {
    	return (new \ReflectionClass($class));
	} catch (\ReflectionException $e) {
    	throw new NotFoundException(
                $e->getMessage(), $e->getCode()
          );
	}
}
```
The method gets the class from the entries array. Try to return a reflection class of that and throw an exception if it fails. You can implement those exceptions on your own

The ``` getArguments() ``` method
```php
/**
 * Get the constructor arguments of a class
 * @param ReflectionMethod $constructor The constructor
 * @return array The arguments
 */
public function getArguments($alias, \ReflectionMethod $constructor)
{
	$args = [];
    $params = $constructor->getParameters();
    foreach ($params as $param) {
    	if (null !== $param->getClass()) {
        	$args[] = $this->get(
            	$param->getClass()->getName()
             );
		} elseif ($param->isDefaultValueAvailable()) {
        	$args[] = $param->getDefaultValue();
        } elseif (isset($this->rules[$alias][$param->getName()])) {
        	$args[] = $this->rules[$alias][
            	$param->getName()
             ];
		}
	}
	return $args;
}
```

The method takes the alias and the reflectionMethod of the constructor. It calls the ```getParameters() ``` to get an array of ``` ReflectionParameters ```. Next, it loops through all the parameters. First check if the parameter is a type hinted class, if so it calls the ``` get() ``` method of the container to resolve the class.
If it's not a class it checks if it has a default value. If it has a default it pushes that into the ``` $args ``` array. If the argument is not a type hinted class and it doesn't have a default value it checks if its value has been set in the ``` $rules ``` array via the ``` configure() ``` method and pushes that value to the ``` $args ``` array. Last it returns the ``` $args ``` array.

The configure method is straightforwad
```php
public function configure(array $config)
{
 $this->rules = array_merge($this->rules,$config);
 return $this;
}
```
I left out the ``` resolveInterface() ``` and ``` singleton() ``` methods to resolve a class from a type hinted interface and signleton classes. You can find all the code in this article [here](https://github.com/SlimGee/dream-php/blob/master/src/container/Container.php)

#### Lets see if our container works

Let's create three classes
```php
class Task
{
public function __conctruct(Logger $loger)
{
  	echo "task created\n";
}
}

class Logger
{
public function __construct(DB $database)
{
	echo "Logger created\n";
}
}

class DB
{
public function __construct()
{
  	echo "DB created";
}
}
```

Now say we want to instantiate the Task class. The traditional way would be like this
```php
$db = new DB();
$logger = new Logger($db);
$task = new Task($logger);
```
Using our DI container, however, we'll be doing it like this. Put this in your index.php file
```php
use Flixtechs\Container;

$container = new Container;

$container->get(Task::class);
```
The container will take of everything.
Now run 
```bash
composer dumpautoload
```
and then 

```bash
php index.php
```
It should output the following
```bash
Db created
Logger created
Task created
```
That was one way of doing it, you can't use this code in production though I wanted to clarify the magic with DI containers and show you how to build your own from scratch. But you can improve it from here.