PHP (Database) Dependency Injection

PHP (Database) Dependency Injection

Dependency injection is the answer to more maintainable, testable, modular code.

Every project has dependencies and the more complex the project is the more dependencies it will most likely have.  The most common dependency in today’s web application is the database and chances are if it goes down the application will all together stop working.  That is because the code is dependent on the database server, which is perfectly fine.  Not using a database server because it could one day crash is a bit ridiculous.  Even though the dependency has its flaws, it still makes life for the code, and thus the developer, a lot easier.

The problem with most dependencies is the way that code handles and interacts with them. What I really mean is the problem is in the code and not the dependency.  If you are not using dependency injection, chances are your code looks something like this:

class Book {

	public function __construct() {

		$registry  = RegistrySingleton::getInstance();
		$this->_database = $registry->database;

		// or

		global $databaseConnection;
		$this->_database = $database;
	}

}

The book object now is given full access to the database once it is constructed.  That is good, the book needs to be able to talk to the database and pull data.  The problem lies in the way the book gained its access.  In order for the book to be able to talk to the database the code must have an outside variable named $database, or worse, it must have a singleton pattern class (registry) object containing a record for a database connection.  If neither of these exist the book fails, making the code far from modular.

This raises the question, how exactly does the book get access to the database?  This is where inversion of control comes in.

In Hollywood a struggling actor does not call up a director and ask for a role in his next film.  No, the opposite happens.  The director calls up the actor and asks him to play the main character in his next movie.  Objects are struggling actors, they do not get to pick the roles they play, the director needs to tell them what to do.  Objects do not get to pick the outside systems they interact with, instead, the outside systems are given to the objects.  Remember this as Inversion of Control.

This is how a developer tells his objects how to interact with outside dependencies:

class Book {

	private $_databaseConnection;

	public function __construct() { }

	public function setDatabaseConnection($databaseConnection) {
		$this->_databaseConnection = $databaseConnection;
	}

}
$book = new Book();
$book->setDatabase($databaseConnection);

This code allows for the book class to be used in any web application.  The Book is no longer dependent on anything other than the developer supplying a database shortly after object creation.

This is, at its finest, dependency injection.  There are two common ways of injecting dependencies.  The first being constructor injection and the second being setter injection.  Constructor injection involves passing all of the dependencies as arguments when creating a new object.  The code would look something like this:

$book = new Book($databaseConnection, $configFile);

There are some issues with constructor injection. First, the more dependencies a class has the messier the constructor becomes. Passing in three or four dependencies all in a constructor is extremely hard to read. Also, the less work a constructor does the better.

This leaves us with our second method of dependency injection, setter injection. A dependency is set by using a public method inside the class.

$book = new Book();
$book->setDatabase($databaseConnection);
$book->setConfigFile($configFile);

This is easy to follow, but it leads writing more and more code for your application.  When a book object is created three lines of code are required.  If we have to inject another dependency, a 4th line of code is now needed.  This gets messy quickly.

The answer to this problem is a container, which is class that is designed to hold, create, and inject all the dependencies needed for an application or class.  Here is an example:

class Container {

	public static $_database;

	public static function makeBook() {

		$book = new Book();
		$book->setDatabase(self::$_database);
		// more injection...

		return $book;
	}

}

And then:

$book = Container::makeBook();

All dependencies should be registered into the container object during run time.  This object is now the gateway that all dependencies must pass through before they can interact with any classes.  This is the dependency container.

The reason makeBook is a public static function is for ease of use and global access.   When I started this article off I made a reference to the singleton pattern and global access being a poor choices of code.  They are, for the most part.  It is bad design when they control access, but it is perfectly ok when they control creation.  The makeBook function is only a shortcut for creation.  There is no dependency what-so-ever between the book class and the container class.  The container class exists so we can contain our dependencies in one location and automatically inject those dependencies with one line of code creation.

The container class removes all of the extra work of dependency injection.

Before injection:

$book = new Book();

And now:

$book = Container::makeBook();

Hardly any extra work, but tons of extra benefits.

When test code is run, specifically unit tests, the goal is to see if a method of a class is working correctly.  Since the book class requires database access to read the book data it adds a whole layer of complexity.  The test has to acquire a database connection, pull data, and test it.  All of a sudden the test is no longer testing a single method in the book class, it is now also testing database.  If the database is offline, the test would fail.  This is far from the goal a unit test.

A way of dealing with this is just using a different database dependency for the unit tests.  When the test suite starts up a dummy database is injected into the book.  The dummy database will always have the data the developer expects it to have.  If a live database was used in a unit test the data could potentially change causing tests to unnecessarily fail.

The code is more modular because it can dropped into any other web application.  Create the book object and inject a database connection with $book->setDatabase().  It does not matter if the database is in Registry::Database, $database, or $someRandomDatabaseVarible.  As long as there is a database connection the book will work inside any system.

The code is more maintainable because each object is given exactly what it needs.  If separate database connections are required between different instances of the same class then there no extra code needed inside the class what-so-ever.  Give book1 access to database1 and book2 access to database2.

Container::$_database = $ourDatabaseVarForDB1;

$book1 = Container::makeBook();
$book2 = Container::makeBook();
$book2->setDatabase($database2);

Dependency injection really is the answer to more maintainable, testable, modular code.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s