Managing Dependencies Using Inversion Of Control in PHP: Step By Step

In my previous article, I have explained DI (Dependency Injection). In this article, I will explain how we can use Inversion of Control in PHP to reduce complexity that comes with DI.

Every design pattern has its positive and negative aspects, DI can make it easier to automate the testing process using unit testing frameworks. But, on the other side, DI can introduce extra complexity in your code that makes it more verbose and less readable. For example:

<?php

$subdep = new SubDep();     //dependency required for Dep1 class.
$dep1 = new Dep1($subdep);  //creating Dep1 by injecting its dependency
$dep2 = new Dep2();         //creating Dep2.
$dep3 = new Dep3();         //creating Dep3
$instance = new SomeClass($dep1,$dep2,$dep3); // injecting required dependencies

As you can see, SomeClass requires three dependencies, whereas Dep1 has its own dependency (more complexity). So the point is, if you are dealing with lot of dependencies in your application, you could end up writing and repeating a lot of code throughout your application. To subdue this problem, we use another design pattern called as Inversion of Control Container.

In software engineering, inversion of control (IoC) is a programming technique, expressed here in terms of object-oriented programming, in which object coupling is bound at run time by an assembler object and is typically not known at compile time using static analysis.
wikipedia

Inversion Of Control in PHP: Dependency Management

What I have learned so far is, the more you try to explain inversion of control, the more it gets complicated. So, instead of digging in further details, its better to work through an example.

Let us create some dependencies first:

<?php
//Foo.php

class Foo {

    private $name = 'Injected Dpendency';

    public function display() 
    {
        echo $this->name;
    }
}

//Bar.php

class Bar {

    private $foo;

    public function __construct(Foo $foo) 
    {
        $this->foo = $foo;
    }

    public function display() 
    {
        $this->foo->display();
    }
}

//Baz.php

class Baz {

    private $bar;

    public function __construct(Bar $bar) 
    {
        $this->bar = $bar;
    }

    public function display() 
    {
        $this->bar->display();
    }
}

Now we will register all the dependencies in a file, which I am going to call as dependencies.php, it will look like this:

<?php
//dependencies.php
/**
 * application wide dependencies
 * @var array
 * @return array array of dependencies
 */
return [ 		//php 5.4 syntax 
    'Bar' => function() {
        $foo = new Foo;
        return new Bar($foo);
    },
    'Baz' => function() {
        $foo = new Foo;
        $bar = new Bar($foo);
        return new Baz($bar);
    },
    //register more class dependencies here.
    ];

The above code arranges dependencies in an associative array of 'key'=>Closure pairs: where ‘key’ is the name of the class for which we need to register dependencies, and the closure contains the logic to initialize the class by injecting proper dependencies. We will utilize this array in our IoC container to initialize it, and register dependencies.

Code for IoC Container:

<?php

class Container {

    /**
     * registered class dependencies.
     * @var array
     */
    private static $_depKeys = array();

    /**
     * initialize and registers dependencies from dependencies.php 
     * @return void
     */
    public static function init() 
    {
        $deps = include('dependencies.php');
        foreach ($deps as $key => $closure) 
        {  					//allowing the user to register dependencies with both upercase            
      						// and lowercase letters in class names.			
            static::$_depKeys[strtolower($key)] = $closure;
        }
    }

    /**
     * returns instance of $className
     * @param  string $className required class
     * @throws Exception If class not found
     * @return mixed returns new $className instance
     */
    public static function getInstance($className) 
    {

        $className = strtolower($className);
        if (static::containerHas($className)) 
        {
            return call_user_func(static::$_depKeys[$className]);
        } 
        else 
        {
            throw new Exception("No class found with the name:$className", 1);
        }
    }

    /**
     * registers dependencies in Container
     * @param  string  $className class name to register	
     * @param  Closure $closure   closure to initiallize the dependencies	
     * @return void             
     */
    public static function register($className, Closure $closure) 
    {
        $className = strtolower($className);
        /* if(static::containerHas($className))
          {
          throw new Exception("Class is alreay registered!", 1); //who knows 🙂

          } */
        static::$_depKeys[$className] = $closure;
    }

    /**
     * checks if a class dependency is registered
     * @param  string $className class name
     * @return bool
     */
    public static function containerHas($className) 
    {
        if (array_key_exists($className, static::$_depKeys))
            return true;
    }

}

The init static method populates $_depKeys array by reading all the values from the array returned by dependencies.php. The actual inversion of control is happening inside the static function getInstance which returns an instance of required class by calling its registered closure.

A hypothetical bootstrap.php:

<?php

function __autoload($className) 
{ 					//the simplest autoloader possible.
    include($className . '.php'); 	//assuming all classes are in same dir for simplicity
}

Container::init(); //initializing the container

$bar = Container::getInstance('bar'); //creates a Bar instance by injecting foo
$bar->display();
$baz = Container::getInstance('Baz'); //creates a Baz instance by injecting bar and foo
$baz->display();
$exception = Container::getInstance('exception'); //will throw an exception

Container::register('Bar',function() //registering a dependency on the fly
{	
    $foo = new Foo;
    return new Bar($foo);
});

Final Remarks

The solution explained above is not a complete solution, it is merely a guideline. There are already third party solutions available for IoC Containers, for example you can use pimple container, it is simple and very light weight, or otherwise, if you are looking for a more advanced container you can use laravel’s Illuminate\Container package, which is available through PHP’s composer package management system.

One thought on “Managing Dependencies Using Inversion Of Control in PHP: Step By Step”

  1. Very succinct explanation of ioc. I’m looking at Laravel and wanted info on ioc and you’ve explained it nicely, cheers.

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.