Advertisement
PHP

Dependency Injection: Huh?

by

Chances are, at some point in your learning, you've come across the term, "dependency injection." If you're still relatively early in your learning, you likely formed a confused expression and skipped over that part. Still, this is an important aspect of writing maintainable (and testable) code. In this article, I'll explain it in as simple a way as I'm capable of.


An Example

Let's jump into a fairly generic piece of code, and discuss its short-comings.

class Photo {
	/**
	 * @var PDO The connection to the database
	 */
	protected $db;

	/**
	 * Construct.
	 */
	public function __construct()
	{
		$this->db = DB::getInstance();
	}
}

At first glance, this code might seem fairly harmless. But consider the fact that, already, we've hardcoded a dependency: the database connection. What if we want to introduce a different persistence layer? Or, think about it this way: why should the Photo object be communicating with outside sources? Doesn't that violate the concept of separation of concerns? It certainly does. This object shouldn't be concerned with anything that isn't directly related to a Photo.

The basic idea is that your classes should be responsible for one thing only. With that in mind, it shouldn't be responsible for connecting to databases and other things of that nature.

Think of objects in the same way that you think of your pet. The dog doesn't decide when he goes outside for a walk or to play at the park. You do! It's not his place to make these decisions.

Let's regain control of the class, and, instead, pass in the database connection. There are two ways to accomplish this: constructor and setter injection, respectively. Here's examples of both:

Constructor Injection

class Photo {
	/**
	 * @var PDO The connection to the database
	 */
	protected $db;

	/**
	 * Construct.
	 * @param PDO $db_conn The database connection
	 */
	public function __construct($dbConn)
	{
		$this->db = $dbConn;
	}
}

$photo = new Photo($dbConn);

Above, we're injecting all required dependencies, when the class's constructor method runs, rather than hardcoding them directly into the class.

Setter Injection

class Photo {
	/**
	 * @var PDO The connection to the database
	 */
	protected $db;

	public function __construct() {}

	/**
	 * Sets the database connection
	 * @param PDO $dbConn The connection to the database.
	 */
	public function setDB($dbConn)
	{
		$this->db = $dbConn;
	}
}

$photo = new Photo;
$photo->setDB($dbConn);

With this simple change, the class is no longer dependent upon any specific connection. The outside system retains complete control, as should be the case. While it may not be immediately visible, this technique also makes the class considerably easier to test, as we can now mock the database, when calling the setDB method.

Even better, if we later decide to use a different form of persistence, thanks to dependency injection, it's a cinch.

"Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields."


The Rub

There's one problem with using setter injection in this way: it makes the class considerably more difficult to work with. The user now must be fully aware of the class's dependencies, and must remember to set them, accordingly. Consider, down the line, when our fictional class requires a couple more dependencies. Well, following the rules of the dependency injection pattern, we'd have to do:

$photo = new Photo;
$photo->setDB($dbConn);
$photo->setConfig($config);
$photo->setResponse($response);

Yikes; the class may be more modular, but we've also piled on confusion and complexity. Before, the user could simply create a new instance of Photo, but, now, he has to remember to set all of these dependencies. What a pain!


The Solution

The solution to this dilemma is to create a container class that will handle the brunt of the work for us. If you've ever come across the term, "Inversion of Control (IoC)," now you know what they're referring to.

Definition: In software engineering, Inversion of Control (IoC) is an object-oriented programming practice where the object coupling is bound at run time by an assembler object and is typically not known at compile time using static analysis.

This class will store a registry of all the dependencies for our project. Each key will have an associated lambda function that instantiates the associated class.

There are a couple ways to tackle this. We could be explicit, and store methods, such as newPhoto:

// Also frequently called "Container"
class IoC {
	/**
	 * @var PDO The connection to the database
	 */
	protected $db;

	/**
	 * Create a new instance of Photo and set dependencies.
	 */
	public static newPhoto()
	{
		$photo = new Photo;
		$photo->setDB(static::$db);
		// $photo->setConfig();
		// $photo->setResponse();

		return $photo;
	}
}

$photo = IoC::newPhoto();

Now, $photo will be equal to a new instance of the Photo class, with all of the required dependencies set. This way, the user doesn't have to remember to set these dependencies manually; he simply calls the newPhoto method.

The second option, rather than creating a new method for each class instantiation, is to write a generic registry container, like so:

class IoC {
	/**
	 * @var PDO The connection to the database
	 */
	protected static $registry = array();

	/**
	 * Add a new resolver to the registry array.
	 * @param  string $name The id
	 * @param  object $resolve Closure that creates instance
	 * @return void
	 */
	public static function register($name, Closure $resolve)
	{
		static::$registry[$name] = $resolve;
	}

	/**
	 * Create the instance
	 * @param  string $name The id
	 * @return mixed
	 */
	public static function resolve($name)
	{
		if ( static::registered($name) )
		{
			$name = static::$registry[$name];
			return $name();
		}

		throw new Exception('Nothing registered with that name, fool.');
	}

	/**
	 * Determine whether the id is registered
	 * @param  string $name The id
	 * @return bool Whether to id exists or not
	 */
	public static function registered($name)
	{
		return array_key_exists($name, static::$registry);
	}
}

Don't let this code scare you; it's really very simple. When the user calls the IoC::register method, they're adding an id, such as photo, and its associated resolver, which is just a lambda that creates the instance and sets any necessary dependencies on the class.

Now, we can register and resolve dependencies through the IoC class, like this:

// Add `photo` to the registry array, along with a resolver
IoC::register('photo', function() {
	$photo = new Photo;
	$photo->setDB('...');
	$photo->setConfig('...');

	return $photo;
});

// Fetch new photo instance with dependencies set
$photo = IoC::resolve('photo');

So, we can observe that, with this pattern, we're not manually instantiating the class. Instead, we register it with the IoC container, and then request a specific instance. This reduces the complexity that we introduced, when we stripped the hardcoded dependencies out of the class.

// Before
$photo = new Photo;

// After
$photo = IoC::resolve('photo');

Virtually the same number of characters, but, now, the class is significantly more flexible and testable. In real-world usage, you'd likely want to extend this class to allow for the creation of singletons as well.


Embracing Magic Methods

If we want to reduce the length of the IoC class even further, we can take advantage of magic methods - namely __set() and __get(), which will be triggered if the user calls a method that does not exist in the class.

class IoC {
	protected $registry = array();

	public function __set($name, $resolver)
	{
		$this->registry[$name] = $resolver;
	}

	public function __get($name)
	{
		return $this->registry[$name]();
	}
}

Popularized by Fabien Potencier, this is a super-minimal implementation - but it'll work. Whether or not __get() or set() runs will be dependent upon whether the user is setting a value or not.

Basic usage would be:

$c = new IoC;
$c->mailer = function() {
  $m = new Mailer;
  // create new instance of mailer
  // set creds, etc.
  
  return $m;
};

// Fetch, boy
$mailer = $c->mailer; // mailer instance

Thanks for reading!

Related Posts
  • Code
    Web Development
    Laravel Unwrapped: Session, Auth and CacheLaravel wide retina preview
    Join me as we learn how to use Laravel's component-based system, Illuminate. Additionally, we'll see how to utilize service providers, Laravel's manager system, the Session, Auth, and Cache components, and the Store, Guard, and Repository libraries.Read More…
  • Code
    PHP
    Building a Customer Management App Using AngularJS and LaravelLaravel 4 auth retina preview
    When creating a single-page app we should use some kind of framework to do some of the job for us, so we can focus on the actual functionality. AngularJS fits here perfectly, because features like dynamic dependency injection and bi-directional data binding are just great. Sometimes we also require some kind of server. If you've chosen PHP then Laravel may be your best option, as it's easy to work with and pretty powerful.Read More…
  • Code
    PHP
    25 Laravel Tips and TricksCode
    There was a period of time, not too long ago, when PHP and its community were, for lack of better words, hated. Seemingly, the headline joke of every day was one that related to how terrible PHP was. Let's see, what new PHP-slamming blog article will be posted today?Read More…
  • Code
    Tools & Tips
    Tips to Avoid Brittle UI TestsUi test retina preview
    In the last article I talked about a few ideas and patterns, like the Page Object pattern, that help write maintainable UI tests. In this article we are going to discuss a few advanced topics that could help you write more robust tests, and troubleshoot them when they fail:Read More…
  • Code
    JavaScript & AJAX
    Combining Laravel 4 and BackboneLaravel plus backbone 400
    For this tutorial, we're going to be building a single page app using Laravel 4 and Backbone.js. Both frameworks make it very easy to use a different templating engine other than their respective default, so we're going to use Mustache, which is an engine that is common to both. By using the same templating language on both sides of our application, we'll be able to share our views betweem them, saving us from having to repeat our work multiple times.Read More…
  • Code
    PHP
    How to Write Testable and Maintainable Code in PHPTestable and maintainable code in php 400
    Frameworks provide a tool for rapid application development, but often accrue technical debt as rapidly as they allow you to create functionality. Technical debt is created when maintainability isn't a purposeful focus of the developer. Future changes and debugging become costly, due to a lack of unit testing and structure. Here's how to begin structuring your code to achieve testability and maintainability - and save you time.Read More…