facebook youtube pinterest twitter reddit whatsapp instagram

Building Dependency Injection and The Container from Scratch in PHP

In this guide, you'll learn about dependency injection and a way to build a simple DIC (Dependency Injection Container) in PHP using PSR-11 from scratch.

First,

What is an Ordinary Dependency?

This is when an object depends on another object to function, it is as simple as that. Look at the following =>

<?php

class Data {

    private $post;
    
    public function __construct() {
        $this->post = new Post();
    }
    
    public function getPostData(){
        return $this->post->getPost();
    }

}

Here, we have a class Data that obviously depends on Post, we initialize the post dependency in the constructor, we then get the post with the getPostData() method.

To use this, we can do:

(new Data())->getPostData();

What you need to understand is that, the Data object is depending on an object to function, without the post object it obviously won't work. This is an example of Ordinary Dependency.

The problem with this approach is that the Data object is constructing the dependency itself, and we are likely to face issues if we want to edit the Data, for example, if we wanna use a Page object, we would do something like =>

<?php

class Data {

    private $post;
    private $page;
    
    public function __construct() {
        $this->post = new Post();
        $this->page= new Page();
    }
    
    public function getPostData(){
        return $this->post->getPost();
    }

    public function getPageData(){
        return $this->page->getPage();
    }

}

If we wanna add Menu, we would repeat the same steps, you can see where still can get messy. The Data object would end up knowing too much, and here is where...

Dependency Injection Comes to Our Rescue

So, what is dependency injection, and how can it help us solve the aforementioned issue:

First, dependency injection is when object (e.g. our Data object) receive instances of objects from other codes, instead of the Data Object having to construct its dependencies itself, the dependency is simply passed to it from an outside code, here is an example =>

<?php

class Data {

    private $post;
    
    public function __construct($post) {
        $this->post = $post;
    }
    
    public function getPostData(){
       return $this->post->getPost();
    }

}

To use this, we can do =>

$post = new Post();
(new Data($post))->getPostData();

You see, this is just a way of passing the dependency to the Data object without the Data object doing it itself, the good thing about this is that, you can easily change the dependency. We can make our code better by type hinting the dependency we want to receive =>

<?php

class Data {

    private $post;
    
    public function __construct(Post $post) {
        $this->post = $post;
    }
    
    public function getPostData(){
        return $this->post->getPost();
    }

}

This is saying the $post instance should be of type Post. You don't have to do this, but this helps to catch error in the occasion you erroneously add a wrong type plus it also gives you a bit of documentation. Adding more injection is as simple as doing:

.......
    public function __construct(Post $post, Page $page) {
        $this->post = $post;
        $this->page= $page;
    }
......

Well, hold on! If you've been looking closely, you'll notice that the above isn't really difference from Ordinary Dependency except this time we are passing it to Data object constructor, to add more dependency would still prove a bit cumbersome.

The solution to this is by using an interface, so, you'll create a DataInterface and describe the common method (e.g get()) the classes implementing it would have, by doing this, you can freely change the Data object dependency with ease.

Now, let's move to...

Dependency Injection Container

Now imagine you have a tool that can automatically build your object dependencies for you, and that is what DIC is all about, it is really useful when you need to manage a lot of different objects with a lot of dependencies.

In the above example, you would have noticed you have to know all the dependencies you need before you can create the actual object, it would even be worse if you have nested dependencies (object that depends on another object that also depend on another and so on).

This is the usefulness of Dependency Injection Container, it is an object that knows how to instantiate and configure object dependencies, the way it does this is, it needs to know the relationship between the objects.

For cases like this, we would write a DIC to manage the dependencies for us, let's get started.

The best way to understand this is to open your IDE and type along, you'll thank me later...

Installing and Using The PSR-11 Interface

We would be using the PSR-11 container interface, this interface provides us the method with no implementation, so, we would implement the actual functionality ourselves, the advantage of using a PSR is it provides interoperability of components, this way any developer creating a container library can follow the same standards, and that would yield better code readability and maintainability since incorporating the standard means standardizing the way it is done across projects.

Install psr-11 using:

composer require psr/container

Creating Container Project Directory

Now, you can create a Container directory with the file Container that has the following:

<?php

namespace App\Container;

use Psr\Container\ContainerInterface;

class Container implements ContainerInterface
{

    /**
     * This holds all of the id of the instance passed to the container
     * @var array
     */
    private array $instances = [];

    /**
     * @inheritDoc
     */
    public function get($id, $parameters = [])
    {
    }

    /**
     * @inheritDoc
     */
    public function has(string $id): bool
    {
    }
}

Abstract Overview of The get and has method

The get() and has() method was from the PSR container interface. The get($id, $parameters = []) is how to get the container by its identifier which is then returned, and the has(string $id) method is for returning Boolean (TRUE/FALSE), so, if the container can return an entry for the given identifier, we return true, and false otherwise.

The private property ($instances) is an array that store the classes that needs its dependency taken care of.

The set and has method

Now, everything is meaningless without setting the id, so let's add a set() method:

    /**
     * @param $id
     * @param Closure|null $concrete
     */
    public function set($id, Closure $concrete = NULL)
    {
        if (is_null($concrete)){
            $concrete = $id;
        }

        $this->instances[$id] = $concrete;
    }

    /**
     * @inheritDoc
     */
    public function has(string $id): bool
    {
        // simple return a bool if we have an $id in the instance array or not
        return array_key_exists($id, $this->instances);
    }

The set method has two parameters, one is the id, this is used in identifying the class that wants its dependency to be taken care of, and the second parameter takes a closure. The closure parameter in most cases is the parameter the container needs to load the dependency of the class, this would be done by the container itself, so, we only need to worry about the id.

The has is self-descriptive, it simply checks if we have an $id of the instance, and it returns a Boolean. I won't be using it in this guide, but you can if you wanna take things a bit further.

So, before going further, add a dump after the $this->instances[$id] = $concrete; like so:

................        
$this->instances[$id] = $concrete;
dump($this->instances);

Let's try to set something to the container =>

$container = new Container();
$container->set('Post');


// Output is:
array:1 [▼
 "Post" => "Post"
]

I am trying to break it down little by little, so, what is happening is, when we set an identifier, the set method checks if the parameter that is taking a closure is null, if it is, it overrides the closure parameter with the identifier, having done that, it then set the value of the identifier to concrete, here: $this->instances[$id] = $concrete; which is why we have the above array result.

But if the concrete isn't null, it set the value of the identifier to whatever is passed, so, most times, the container would be the one passing the parameters. Now, remove the dump function.

Now, before we go further, let's revisit the Data and Post class we were working with, here is the Data class:

<?php

namespace App\Container;

class Data {

    private $post;

    public function __construct(Post $post) {
        $this->post = $post;
    }

    public function getPostData(){
        return $this->post->getPost();
    }

}

and here is the Post Class:

<?php

class Post {

    /**
     * @return object
     */
    public function getPost(): object
    {
        return [
            'Title' => 'Post One',
            'Body' => 'I am the post',
        ];
    }

}

So, without Dependency Injection Container, we can handle the dependency ourselves like so:

$post = new Post();
$data = new Data(post: $post);
$data = $data->getPostData(); dump($data); // Output is: array:2 [▼ "Title" => "Post One" "Body" => "I am the post" ]

The above is without Dependency Injection Container, if we were to use DIC, all we might have needed to pass is just the Data::class and it would resolve the dependency for us. The good thing about this is that if Post also depends on something (maybe Database), DIC would recursively resolve the dependency. So, let's get back to our DIC creation, here is what we have so far in our Container.php =>

<?php


class Container implements \Psr\Container\ContainerInterface
{
    /**
     * This holds all of the id of the instance passed to the container
     * @var array
     */
    private array $instances = [];

    /**
     * @param $id
     * @param Closure|null $concrete
     */
    public function set($id, Closure $concrete = NULL)
    {
        if (is_null($concrete)){
            $concrete = $id;
        }

        $this->instances[$id] = $concrete;
    }

    /**
     * @inheritDoc
     */
    public function get(string $id)
    {
        // TODO: Implement get() method.
    }

    /**
     * @inheritDoc
     */
    public function has(string $id): bool
    {
        // TODO: Implement has() method.
    }

}

Implementing the get method

The next line of action is a way to get the dependency resolved, now in your get method add the following =>

    /**
     * @inheritDoc
     */
    public function get($id, $parameters = [])
    {
        // if we don't have it, just set it anyway
        if (!isset($this->instances[$id])) {
            $this->set($id);
        }

        return $this->resolve($this->instances[$id], $parameters);
    }

Whenever we call the get method to resolve a dependency of an object, the first thing it does is, it checks if the object isn't set in the instances array, if it isn't, we set it anyway, we can choose to return null, but setting it would mean, perhaps we forgot to set it, so, we set it implicitly.

The resolve method

The next step is where the magic happens, we resolve the dependency, and here is the rest of the container implementation:

    /**
     * The resolve method would iterate over the instances that has been...
     * passed to the container, and resolve it
     * @param $id
     * @param $parameters
     * @return object
     * @throws ReflectionException
     * @throws ExceptionDependencyNotInstantiable
     */
    public function resolve($id, $parameters)
    {

        $reflector = new ReflectionClass($id);

        // get class constructor
        $__construct = $reflector->getConstructor();

        // Checks if the function is an anonymous function
        // if so, we return it return $id($this, $parameters);
        // $this is the current class instance, so, it contains info about this container or whatever
        // properties are in this container
        // $parameters are the object parameter if any is set
        if ($reflector->isAnonymous()) return $id($this, $parameters);


        // if class is not instantiatable we throw exception
        if (!$reflector->isInstantiable()) throw new ExceptionDependencyNotInstantiable("Class $id not instantiable.");

        // If there is no constructor in class, just return the object
        // by returning the object, you can think of it has (new Class())
        if(!$__construct) return $reflector->newInstance();

        $dependencies = $this->resolveDependencies($__construct->getParameters());

        // This create a new class instance from $dependencies retrieved
        return $reflector->newInstanceArgs($dependencies);
    }

    public function resolveDependencies($parameters){

       $dependencies = [];

        foreach ($parameters as $param) {
            // this get the type hinted class in the constructor
            // so, if the class constructor has __construct(Post $post)...
            // then the type hinted class is Post
            if($paramType = $param->getType()->getName()) {
                $dependencies[] = $this->get($paramType);
              //  dd($dependencies);
            }
        }

        return $dependencies;
    }

To better understand, let's assume we wanna resolve the following dependencies:

$data = new Data(
    new Post(new Test()),
    new Page()
);

An Instance of Class Data -> Depends on an Instance of Post and Page Class:

// CLASS DATA

class Data {

    private Post $post;
    private Page $page;

    public function __construct(Post $post, Page $page) {
        $this->post = $post;
        $this->page = $page;
    }

    public function getPostData(){
        return $this->post->getPost();
    }

    public function getPageData(){
        return $this->page->getPage();
    }
}

// CLASS POST

class Post {

    public Test $test;

    public function __construct(Test $test){
        $this->test = $test;
    }

    /**
     * @return array
     */
    public function getPost(): array
    {
        return [
            'Title' => 'Post One',
            'Body' => 'I am the post',
            'Test' => $this->test->getTest()
        ];
    }

}

// CLASS PAGE

class Page
{

    public function getPage(): array
    {
        return [
            'Title' => 'Page One',
            'Body' => 'I am the page',
        ];
    }

}

The Post also has a Test instance dependency, so, here is the Class Test:

// CLASS TEST

class Test
{
    public function getTest()
    {
        return 'This is a test';
    }
}

So, whenever you call:

 $data = (new Container())->get(Data::class)

It turns it into this behind the scene:

$data = new Data(
    new Post(new Test()),
    new Page()
);

So, how does it do it? It is simple =>

STEP 1: PASSING THE CLASS =>

The moment you passed the Data::class to the get method, the get method checks if what you passed hasn't already been passed before, and if that is true, we keep it in an array, which is $this->instances.

STEP 2: SEND THE CLASS FOR DEPENDENCY RESOLVING => 

If step 1 is done, we then call the resolve method to resolve it dependencies

return $this->resolve($this->instances[$id], $parameters);

STEP 3: THE RESOLVE METHOD =>

Inside the resolve method, we examine the class you send in step 2 using ReflectionClass, here:

$reflector = new ReflectionClass($id);

See ReflectionClass has a way to know the details of the class, e.g, its constructor, methods, properties, and about everything you have in the class.

STEP 4: GETTING THE CLASS CONSTRUCTOR =>

Now, we can go ahead and store its constructor in a variable, recall that in step 3, the reflector has already given us everything about the Class, so, getting the constructor is as simple as doing:

 $__construct = $reflector->getConstructor()

If for example, the class has: __construct(Post $post, Page $page), then by getting the constructor, you are simply getting the following:

ReflectionMethod {#7 ▼
  +name: "__construct"
  +class: "App\Container\Data"
  parameters: {▼
    $post: ReflectionParameter {#21 ▼
      +name: "post"
      position: 0
      typeHint: "App\Container\Post"
    }
    $page: ReflectionParameter {#20 ▼
      +name: "page"
      position: 1
      typeHint: "App\Container\Page"
    }
  }
}

You can see how it packed the constructor for you,

STEP 5: CHECK IF CLASS IS ANONYMOUS =>

so, moving on, we check if the class is of anonymous, if so, we return it, here:

if ($reflector->isAnonymous()) return $id($this, $parameters);

STEP 6: CHECK IF CLASS IS INSTANTIABLE =>

We also check if the class is instantiable (instantiable meaning can you instantiate it i.e new Data()), if it is not instantiable, we throw an exception, here:

if (!$reflector->isInstantiable()) throw new ExceptionDependencyNotInstantiable("Class $id not instantiable.");

For the exception, create a new folder called Exception and you should also create a class called ExceptionDependencyNotInstantiable (give it any name appropriate), having done that, add the following in the class you just created:

<?php

use Exception;
use Psr\Container\ContainerExceptionInterface;

class ExceptionDependencyNotInstantiable extends Exception implements ContainerExceptionInterface{}

STEP 7: RETURN CLASS INSTANCE IF THERE IS NO CONSTRUCTOR =>

In the event that the class doesn't have a constructor, we instantiate, here:

if(!$__construct) return $reflector->newInstance();

STEP 8: RESOLVE DEPENDENCIES IF CLASS HAS CONSTRUCTOR =>

A class that doesn't have a construtor, won't get to step 8, it would just return an instance back to wherever it is called, but if a class does have a constructor we get its dependencies, here:

$dependencies = $this->resolveDependencies($__construct->getParameters());

Here is the implementation of the resolveDependencies() method:

    public function resolveDependencies($parameters){
        
       $dependencies = [];
        foreach ($parameters as $param) {
            // this get the type hinted class in the constructor
            // so, if the class constructor has __construct(Post $post)...
            // then the type hinted class is Post
            if($paramType = $param->getType()->getName()) {
                $dependencies[] = $this->get($paramType);
            }
        }
        return $dependencies;
    }

We initialized a dependencies array, we then beging loop through the parameters passed to the resolveDependencies() method, the paarameters of the Data class would look something like so:

 array:2 [▼
  0 => ReflectionParameter {#8 ▼
    +name: "post"
    position: 0
    typeHint: "App\Container\Post"
  }
  1 => ReflectionParameter {#9 ▼
    +name: "page"
    position: 1
    typeHint: "App\Container\Page"
  }
]

So, here:

if($paramType = $param->getType()->getName()) {
     $dependencies[] = $this->get($paramType);
}

we are saying if there is a ParameterType, if there is one, we call $this->get($paramType); in a recursive mode, this means, it goes back to step 1 with the new class name until there is no dependency to resolve.  If this souds confusing, dump the dependencies, so, you can see how everything wires. add it after storing it, here:

if($paramType = $param->getType()->getName()) {
    $dependencies[] = $this->get($paramType);
    dump($dependencies); //<- here
}

You should get the following:

^ array:1 [▼
  0 => App\Container\Test {#14}
]
^ array:1 [▼
  0 => App\Container\Post {#12 ▼
    +test: App\Container\Test {#14}
  }
]
^ array:2 [▼
  0 => App\Container\Post {#12 ▼
    +test: App\Container\Test {#14}
  }
  1 => App\Container\Page {#22}
]

The above is how tjhe execution played out, and lastly we create the new instance from the dependencies retrieved, here:

return $reflector->newInstanceArgs($dependencies);

and that is it.

So, resolving our dependency is as easy as doing:

$data = (new Container())->get(Data::class);

I know you'll have some question, send them away!

Related Post(s)

  • Laravel - Edit and Delete Data (+ User Authentication)

    In this guide, you'll learn how to edit and delete post data in Laravel, before proceeding you should read the previous guides as this guide would be the continuation, here are the previous guides:

  • Guide To Laravel - Model and Database Migrations

    I don't know if you have read my guide on Creating a Tiny PHP MVC Framework From Scratch where we create a tiny MVC framework in the hope of understanding the concepts of how major frameworks imple

  • A Practical Examples of Building An Advance Flexbox Layout

    Flexbox needs no introduction, and this tutorial isn't an intro to FlexBox either, it is a practical guide on using flexbox. Well, if you want a basic intro to flexbox, then... The Flexible Box Layou

  • Creating a Tiny PHP MVC Framework From Scratch

    In this guide, we would go over creating a tiny PHP MVC Framework, this would sharpen your knowledge on how major frameworks (e.g Codeigniter or Laravel) works in general. I believe if you can unders

  • PHP Pluggable and Modular System – Part 2 (Implementation) [Event Dispatcher]

    In the first series of this guide, we discussed the theoretical aspect of building a pluggable system in PHP, I wrote a bit of code in that guide plus a couple of stuff you should avoid, you can lear

  • Best Way To Implement a Non-Breaking Friendly URL In PHP or Laravel or Any Language

    I was working on the link structure of my new Laravel app, and out of the blue I said: "What would happen if a user changes the slug of a post?" First Attempt - 301 Redirection The first solution I t