Coding methods and principles (part 3)


Inversion of Control (IoC)

In the previous article (part 2), we talked about using Dependency Injection and Programming to an Interface to make your code more modular.

However, we realised that we still had an issue with confusion and complexity whenever we needed to instantiate a class that requires a lot of arguments to be passed to it.

Also if any of the classes arguments needed changing, then we would have to go through all our code and modify every instance.

IoC (Inversion of Control) can solve that issue by creating a container class that will handle the brunt of the work for us.

Put simply, this container class will register all our dependencies for us.

It does this by building up all the class dependency injections in a container class at run time and we then just use this container to call all future instances of our classes. This will stop us having to remember every argument that a  class needs each time we create a new instance of it.

Below shows an example of a container class that we could use for our book and CD examples:

[php]

//also often called "Container"
class IoC
{
    
    //pdo ORM
    protected $db_orm = new pdo();
    
    //easily switch database ORMs
    //eloquent ORM
    //protected $db = new eloquent();

    public static newBook(){
        
        //late static binding
        $book = new book(IoC::db_orm);
        
        return $book;
        
    }
    
    public static newCD(){
    
        $cd = new cd(IoC::db_orm);
        
        return $cd;
    
    }

}

[/php]

As you can see in the comments, all we have to do to switch between the PDO ORM and Eloquent ORM is change the database (db) variable in this one container rather than having to change it everywhere we instantiate the Book or CD class.

Then all we have to do is, instead of calling the actual Book or CD class to instantiate a new object, we simply call the container class and newBook/newCD method, then all dependencies will already have been taken care of.

Below shows how we now call out Book and CD classes using our new IoC container:

[php]

//below database ORM now is instantiated in the IoC (container)
//$pdo_database_orm = new pdo();

//no need to inject the dependancy now as the IoC (container) is doing that for us
//$book = new book($pdo_database_orm);

$book = IoC::newBook();

//no need to inject the dependancy now as the IoC (container) is doing that for us
//$cd = new cd($pdo_database_orm);

$cd = IoC::newCD();

$bookOne = $book->getByID(1);

$cdsFrom2012 = $cd->getByYearReleased(‘2012’);

[/php]

Doing this will still have the same outcome as instantiating the actual Book or CD class, however the required dependencies would have already been set and we don’t have to set them manually anymore.

The only issue with this is that we have to create a new method for each class instantiation.

This is not a huge issue but if you didn’t want to do this you could always write a generic registry container.

This generic registry container will store a registry of all the dependencies for our projects and it will do this by using key value pairs.

The key will state the name of the class we want to instantiate and the value will be a lambda (anonymous) function which deals with all the dependencies required for that specific class.

Below shows our new IoC class using our generic registry technique:

[php]

class IoC
{
    
    //pdo ORM
    protected $db_orm = new pdo();
    
    protected static $registry = array();
    
    //easily switch database ORMs
    //eloquent ORM
    //protected $db_orm = new eloquent();
    
    public static function register($name,Closure $resolve){
        IoC::$registry[$name] = $resolve();
    }
    
    public static function resolve($name){
        
        if(IoC::registered($name)){
        
            $name = IoC::$registry();
            
            return $name;
        
        }
        
        throw new Exception(‘Nothing registered with that name’);
        
    }
    
    public static function registered($name){
    
        return array_key_exists($name, IoC::$registry);
    
    }   

}

[/php]

The below is now no longer needed in our IoC class:

[php]

/*
    Below is now no longer needed
    
    public static newBook(){

        $book = new book(IoC::db_orm);
        
        return $book;
        
    }
    
    public static newCD(){
    
        $cd = new cd(IoC::db_orm);
        
        return $cd;
    
    }
    */

[/php]

Now in our main code we simply do the following instead:

[php]

//register it once using below
IoC::register(‘book’, function(){   

    $book = new book(IoC::db_orm);
    
    return $book
    
});

//then can just use the resolve method any time after we have registered it
$book = IoC::resolve(‘book’);

IoC::register(‘cd’, function(){   

    $cd = new cd(IoC::db_orm);
    //if had setters we could also have the following in here
    //$cd->setDB(‘…’);
    //$cd->setConfig(‘…’);
    
    return $cd
    
});

$cd = IoC::resolve(‘cd’);

$bookOne = $book->getByID(1);

$cdsFrom2012 = $cd->getByYearReleased(‘2012’);

[/php]

To make it even better, we can create a new method in the IoC to register all dependency classes at once. To do this we will add a registerAll method to our IoC class.

[php]

class IoC
{
    
    //pdo ORM
    //protected $db_orm = new pdo();
    
    protected static $registry = array();
    
    //easily switch database ORMs
    //eloquent ORM
    //protected $db_orm = new eloquent();
    
    public static::registerAll(){
    
        IoC::register(‘book’, function(){   
        
            $book = new book(IoC::db_orm);
            
            return $book
            
        });
        
        IoC::register(‘cd’, function(){   
        
            $cd = new cd(IoC::db_orm);
            
            return $cd
            
        });        
    
    }
    
//below methods stay the same
    public static function register($name,Closure $resolve){
        IoC::$registry[$name] = $resolve();
    }
    
    public static function resolve($name){
        
        if(IoC::registered($name)){
        
            $name = IoC::$registry[$name];
            
            return $name;
        
        }
        
        throw new Exception(‘Nothing registered with that name’);
        
    }
    
    public static function registered($name){
    
        return array_key_exists($name, IoC::$registry);
    
    }    

}

[/php]

We can then run this registerAll method at run time, maybe in a headscript or config file that gets included in every page.

[php]

//config file or headscript

//register all classes with dependances
IoC::registerAll();

//end config file or head script

//then we can just use the resolve method any time after
$book = IoC::resolve(‘book’);

$cd = IoC::resolve(‘cd’);

$bookOne = $book->getByID(1);

$cdsFrom2012 = $cd->getByYearReleased(‘2012’);

[/php]

Using the above techniques in your project will make it more agile, and therefore easier to maintain.