Inversion of Control

Inversion of Control is a pattern I did not know yesterday, I know it a little better today and love it.
Leaving the theory behind I’d like to show an application of its principles I’m using while developing my theme framework.

The situation before applying the IoC pattern

The Main class uses an instance of the Bot class to “find itself” both in filesystem and namespacing terms.
In the MainTest class file I’m trying to test the expectation of the show method being called on the FirstController object.
TestMain extends Main and FirstController extends Controller; this is due to the fact that both Main and Controller are abstract classes and cannot be tested on their own.

public function testShowWillCallShowOnController() { // get an instance of the subject under test $sut = TestMain::getInstance(); // get a mock instance of the FirstController class to inject it in the TestMain but... $mockController = $this->getMockBuilder('\TAD\Test\controllers\FirstController')->setMethods(array( 'show' ))->disableOriginalConstructor()->getMock(); // ... it's the Bot that will provide the FirstController instance to the TestMain and I have to mock it... $mockBot = $this->getMockBuilder('\TAD\MVC\helpers\Bot')->setMethods(array( 'getControllerInstance' ))->disableOriginalConstructor()->getMock(); // ... to return the mock FirstController instance when requested via the `getControllerInstance` method $mockBot->expects($this->once())->method('getControllerInstance')->will($this->returnValue($mockController)); // finally I set an expectation on the FirstController mock $mockController->expects($this->once())->method('show'); // inject the mock Bot $sut->bot = $mockBot; // <<<<<<<<<<<<<<< DEPENDENCY INJECTION via a purposedly made set method $sut->show('First'); } 

There is nothing wrong with this implementation of the test but for the fact that I’ve added a __set method to the Main class to allow, see the code above, for dependency injection.
The problem with this is that my code exposes now one of its properties, bot, to allow testing. Having implemented the __set magic method actually exposes all the Main class properties but the same stands if I had implemented the setBot method alone.
I do not like implementing a method for the sake of testing, sure it’s better than non-tested code, but still I know I will not use the setBot method in production.

The good

My IoC implementation is, right now, trivial

<?php namespace TAD\helpers; class IoC{ protected static $resolvers = array(); public static function register($key, \Closure $resolver){ static::$resolvers[$key] = $resolver; } public static function make($key, $params = array()){ if (!isset(static::$resolvers[$key])) { throw new \RuntimeException("Resolver for $key is not set in IoC", 1); } $resolver = static::$resolvers[$key]; return call_user_func_array($resolver, $params); } public static function deregister($key = null){ if (isset($key)) { static::$resolvers[$key] = null; } else static::$resolvers = array(); } } 

and very common place among the internet. In my Main class the __construct method reads like

protected function __construct() { // set the called class $this->calledClassName = get_called_class(); // init the root namespace and root folder path $this->setRoots(); // get controllers information $this->bot = IoC::make('Bot', array($this->mainFilePath, $this->mainClassNamespace)); // $this->bot = new Bot($this->mainFilePath, $this-> // namespace); $this->controllers = array(); } 

and I get an instance of the Bot class from the IoC and no more from the Bot class directly.
Somewhere else I’ve registered the default IoC resolver for the Bot class like

 // set the Bot class default resolver IoC::register('\TAD\MVC\helpers\Bot', function ($path, $fullNamespace) { return new \TAD\MVC\helpers\Bot($path, $fullNamespace); }); 

and thus using IoC::make('Bot', ...) will actually return an instance of the Bot class. I can now refactor my test code to make its dependency injection in the Main::__construct method removing the way-too-much-stuff-exposing __set method from the Main class.

public function testShowWillCallShowOnController() { // register the mock bot in the IoC IoC::register('Bot', function () { $mockBot = $this->getMockBuilder('\TAD\MVC\helpers\Bot')->setMethods(array( 'getControllerInstance' ))->disableOriginalConstructor()->getMock(); $mockController = $this->getMockBuilder('\TAD\Test\controllers\FirstController')->setMethods(array( 'show' ))->disableOriginalConstructor()->getMock(); $mockBot = $mockBot->expects($this->once())->method('getControllerInstance')->will($this->returnValue($mockController)); $mockController->expects($this->once())->method('show'); return $mockBot; }); // dependency injection happens in the constructor $sut = TestMain::getInstance(); $sut->show('First'); } 

The bad

While setting IoC resolvers for dependency injection in tests is a breeze some code is added to default IoC resolution for production code either in some fallback way or via an explicit setting (the way I did it).

The ugly

There is none beside me staring at the screen after 10 hours straight of coding but I really could not use that header…

See the code using Inversion of Control

I’ve setup a different git branch to try my hand at IoC, here it is.