Advertisement

Dependency Injection in PHP

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

Dependency injection has been a frequent subject of discussion among many corporate developers in the past few years. Many feared that they might sacrifice too much time building their application architecture without doing any real work. In this article, I'll explain why PHP developers should consider taking advantage of dependency injection, when building large, scalable projects.


What is Dependency Injection?

Before digging into the subject, let's precisely define what dependency injection is. Let's imagine that you currently work on a "Question and Answers" website, similar to Stack Overflow. You would more than likely create a class, called Question, which would contain a member of type Author. In 'ye olden days, programmers would have created the Author object directly in the Question constructor, like this:

class Author {
    private $firstName;
    private $lastName;

    public function __construct($firstName, $lastName) {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    public function getFirstName() {
        return $this->firstName;
    }

    public function getLastName() {
        return $this->lastName;
    }
}

class Question {
    private $author;
    private $question;
    public function __construct($question, $authorFirstName, $authorLastName) {
        $this->author = new Author($authorFirstName, $authorLastName);
        $this->question = $question;
    }

    public function getAuthor() {
        return $this->author;
    }

    public function getQuestion() {
        return $this->question;
    }
}

While many programmers might call this good code, there are in fact many problems with it:

  • The author's information passed to the Question constructor has nothing to do inside Question's scope. The name of an author should be inside the Author class because it has nothing to do with the question, itself.
  • The Author class is tightly coupled with the Question class. If we add a new parameter to Author's constructor, we then have to modify every class where we create an Author object - a tedious and long process, especially in large applications.
  • Unit testing the Question class creates the unwanted behavior of having to test the Author class as well.

Dependency injection alleviates these issues by inserting the dependencies through the dependent class' constructor ("Constructor Injection"). The result is highly maintainable code, which might look like this:

class Author {
    private $firstName;
    private $lastName;
    
    public function __construct($firstName, $lastName) {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    public function getFirstName() {
        return $this->firstName;
    }

    public function getLastName() {
        return $this->lastName;
    }
}

class Question {
    private $author;
    private $question;

    public function __construct($question, Author $author) {
        $this->author = $author;
        $this->question = $question;
    }

    public function getAuthor() {
        return $this->author;
    }

    public function getQuestion() {
        return $this->question;
    }
}

Advantages of Dependency Injection

You need to use Dependency Injection for long-term projects.

I have worked on many commercial projects in my career. Some of them are very well written; however, I must say that most of them have considerably poor code quality. I was recently assigned to work on such a code base...

A company requested the application in 2009. The project's budget was properly planned, but the project manager wanted the application built very quickly to impress his boss. At least, this is what my co-worker told me. Writing an application quickly might sound great to reduce costs, but it also means that the developers jumped into the code without proper planning.

The developers wrote code quickly, creating classes necessary to perform the necessary functionality. The features changed as the project evolved, and because the project was poorly planned (and every class was tightly coupled), the project became difficult to develop.

Currently, it's difficult to work on this project. Modifying a simple class results in a cascade of other modifications in other classes, because everything is coupled together - resulting in many bugs. Each time something is modified, the developer needs to chase down and fix bugs.

The application is also impossible to unit test, degrading the code's quality.

Every feature must be manually tested, losing valuable time that could be spent elsewhere. In fact, to reduce the cost of manual testing, we created software to test the infrastructure of the application. Yes, we had to create new software in order to test the actual product.

The company learned that trying to quickly write software resulted in unexpected costs. At first, the company made a lot of money because they charged a lot for it, but after three years, they lost money on this application. The amount of money they pulled in doesn't cover the continued, high expense of maintenance. They also lost many good, senior developers, who lost any desire to work on the project.

If the application used dependency injection, the developers would have been able to properly unit test the full application, leading to reduced maintenance costs and debugging. The company would have focused on creating new features that last, instead of losing software quality every time that a new feature was introduced. They would also have kept their senior team members and avoided losing money by finding and hiring new employees (which is difficult to begin with in my city).


Getting Started on a Limited Budget

While dependency injection helps assists you in writing better code, it can also require extra time and effort to do it correctly. This can prove to be an issue, if you need working code for a demo.

If all you need is a proof of concept, then I suggest that you don't waste time with dependency injection and proper architecture.

Jump right in and start coding. You can do things correctly after the project is approved and you have the necessary funding. In fact, once you do have proper funding, throw away your demo and begin from scratch. Otherwise, your application will end up as a spaghetti-code-trash-can.


Messy Start-up Code

So, you've begun passing your dependencies in your class constructors, but as the project grows, you end up with many levels of objects that must be created when your application starts. Depending on your application's size, creating all objects to launch your application can be a very long process and hurt your application's performance (as well as result in messy code). Here's an example:

/*
    Please note that the nature of the application is not important here. I only wanted to show how hard-to-maintain/awful such code can be:
*/
$filePath = "/path/to/file";

$fileBuilderFactory = new ConcreteFileBuilderFactory();

$filesXmlBuilderFactory = new ConcreteFilesXmlBuilderFactory($fileBuilderFactory);

$softwaresRetrieverCriteriaBuilderFactory = new ConcreteSoftwaresRetrieverCriteriaBuilderFactory($filesXmlBuilderFactory);

$softwareRetrieverCriteriaBuilderFactory = new ConcreteSoftwaresSoftwareRetrieverCriteriaBuilderFactory($filesXmlBuilderFactory);

$filesJsonBuilderFactory = new ConcreteFilesJsonBuilderFactory($fileBuilderFactory);

$objectBuildderFactory = new ConcreteSoftwaresSoftwareObjectBuilderFactory();

$softwaresSoftwareBuilderFactory = new ConcreteSoftwaresSoftwareBuilderFactory($objectBuildderFactory);

$xmlSoftwareRepository = new XmlSoftwareRepository($softwaresSoftwareBuilderFactory);

$softwaresBuilderFactory = new ConcreteSoftwaresBuilderFactory($xmlSoftwareRepository, $softwareRetrieverCriteriaBuilderFactory, $filesJsonBuilderFactory);

$xmlSoftwaresRepository = new XmlSoftwaresRepository($softwaresBuilderFactory);

$softwareToHashMap = new ConcreteSoftwareToHashMap();

$softwaresToHashMap = new ConcreteSoftwaresToHashMap($softwareToHashMap);

$jsonSoftwaresService = new JsonSoftwaresService($softwaresToHashMap);

$di = new DependencyInjection($softwaresRetrieverCriteriaBuilderFactory, $xmlSoftwaresRepository, $jsonSoftwaresService);

This start-up code is rather small. If you build a large application, your start-up code can become much heavier than this. Needless to say, this results in some difficult to maintain code.

To solve this problem, we need a dependency injection application that reads an XML file and creates all the necessary objects needed to launch the application. The objects would then be serialized and written to a file. The start-up code would then simply read that file and directly create the objects. This makes your start-up code as simple as:

$objectFilePath = "/path/to/serialized/object/file";
$di = unserialize(file_get_contents($objectFilePath));

Open Source Dependency Injection Software

Most companies do not have much budget to create tools, such as a dependency injection framework. You can, however, find many free and open source solutions around the web. Symfony2 uses a very solid DI component that is based on Java Spring. However, I will cover programming a dependency injection solution in the near future, if that interests you. Stay tuned!


PHP Devs and Dependency Injection

I don't want to generalize, but, due to its popularity, many PHP developers are hobbyists, who love to mix PHP and HTML.

These developers normally don't plan their project; they just want to write code quickly to "get things done."

I believe that hiring these types of developers is the worst thing a manager can do. By stressing the importance of dependency injection, you can make these "hackers" disinterested in your company, while, at the same time, enticing good developers to work for you company. In other words, your project will attract good senior developers, which is the most important resource a software development company could acquire!


When to Use Dependency Injection

Dependency injection is most useful, when working on long-term projects.

From my experiences, dependency injection is most useful, when working on long-term projects - projects that are actively developed and maintained over a long period of time. This greatly reduces your costs and attracts the best developers. But, as I mentioned before, if you need a demo application to get a contract or some funding, I'd recommend digging right into the code. If you follow that path, however, be aware that the demo will need to be thrown away after you acquire contract and/or funding.


Story Time!

I love stories about developers who want to do the right thing, but their co-workers want to forego best practices and push a project out as fast as possible. What are your thoughts on dependency injection? An over-complication, or a requirement for any sustainable application?