The simplest explanation of Dependency Injection (DI)
Many friends have asked me what the concept of Dependency Injection is all about. I have explained this over and over so I decided to have it written so I would direct the next person that ask me to this post. New frameworks have pride this feature and they make it look like something totally new. Anyway, the name might be new but the concept is not #lol.
Dependency Injection is probably one of the most simple design pattern concept. And odds are you have probably already used it. But it is also one of the most difficult thing to explain well. I think it is partly due to the nonsense examples used in most introductions to Dependency Injection. I will be using Fabien Potencier's examples that fits the PHP world better.
To overcome the statelessness of the HTTP protocol, web applications need a way to store user information between web requests. This is of course quite simple to achieve by using a cookie, or even better, by using the built-in PHP session mechanism:
$_SESSION['language'] = 'fr';
The above code stores the user language in the language session variable. So, for all subsequent requests of the same user, the language will be available in the global $_SESSION array:
$user_language = $_SESSION['language'];
As Dependency Injection only makes sense in an Object-Oriented world, let's pretend we have a SessionStorage class that wraps the PHP session mechanism:
class SessionStorage
{
function __construct($cookieName = 'PHP_SESS_ID')
{
session_name($cookieName);
session_start();
}
function set($key, $value)
{
$_SESSION[$key] = $value;
}
function get($key)
{
return $_SESSION[$key];
}
// ...
}
... and a User class that provides a nice high-level interface:
class User
{
protected $storage;
function __construct()
{
$this->storage = new SessionStorage();
}
function setLanguage($language)
{
$this->storage->set('language', $language);
}
function getLanguage()
{
return $this->storage->get('language');
}
// ...
}
Those classes are simple enough and using the User class is also rather easy:
$user = new User();
$user->setLanguage('fr');
$user_language = $user->getLanguage();
All is good and well... until you want more flexibility. What if you want to change the session cookie name for instance? Here are some random possibilities: Hardcode the name in the User class in the SessionStorage constructor:
class User
{
function __construct()
{
$this->storage = new SessionStorage('SESSION_ID');
}
// ...
}
Define a constant outside of the User class:
class User
{
function __construct()
{
$this->storage = new SessionStorage(STORAGE_SESSION_NAME);
}
// ...
}
define('STORAGE_SESSION_NAME', 'SESSION_ID');
Add the session name as a User constructor argument:
class User
{
function __construct($sessionName)
{
$this->storage = new SessionStorage($sessionName);
}
// ...
}
$user = new User('SESSION_ID');
Add an array of options for the storage class:
class User
{
function __construct($storageOptions)
{
$this->storage = new SessionStorage($storageOptions['session_name']);
}
// ...
}
$user = new User(array('session_name' => 'SESSION_ID'));
All these alternatives are quite bad. Hardcoding the session name in the User class does not really solve the problem as you cannot easily change your mind later on without changing the User class again. Using a constant is also a bad idea as the User class now depends on a constant to be set. Passing the session name as an argument or as an array of options is probably the best solution, but it still smells bad. It clutters the User constructor arguments with things that are not relevant to the object itself. But there is yet another problem that cannot be solved easily: How can I change the SessionStorage class? For instance, to replace it with a mock object to ease testing. Or perhaps because you want to store the sessions in a database table or in memory.
That's impossible with the current implementation, except if you change the User class. Enter Dependency Injection. Instead of creating the SessionStorage object inside the User class, let's inject the SessionStorage object in the User object by passing it as a constructor argument:
class User
{
function __construct($storage)
{
$this->storage = $storage;
}
// ...
}
That's Dependency Injection. Nothing more! Using the User class is now a bit more involving as you first need to create the SessionStorage object:
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
Now, configuring the session storage object is dead simple, and replacing the session storage class is also very easy. And everything is possible without changing the User class thanks to the better separation of concerns.