Advertisement
PHP

Dependency Injection in PHP

by

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?

Related Posts
  • Code
    PHP
    Object-Oriented Programming in WordPress: ScopeWordpress oop
    In continuing our discussion of object-oriented programming in WordPress, we need to begin talking about the idea of scope. In short, this refers to the idea as to how classes can control how their attributes and functions are accessed (or whether or not they can even be accessed). This is yet another core idea of object-oriented programming after which we should be in good shape to begin working on an actual WordPress plugin.Read More…
  • Code
    Web Development
    Refactoring Legacy Code: Part 2 - Magic Strings & ConstantsRefactoring wide retina preview
    Learn techniques for how to deal with complex and complicated unknown legacy code, how to understand it, and finally updating the Golden Master tests.Read More…
  • Code
    PHP
    SOLID: Part 4 - The Dependency Inversion Principle4 dip retina
    The Single Responsibility (SRP), Open/Closed (OCP), Liskov Substitution, Interface Segregation, and Dependency Inversion. Five agile principles that should guide you every time you write code.Read More…
  • Code
    PHP
    Validation and Exception Handling: From the UI to the BackendProcedural to oop php retina preview
    Sooner or later in your programming career you will be faced with the dilemma of validation and exception handling. This was the case with me and my team also. A couple or so years ago we reached a point when we had to take architectural actions to accommodate all the exceptional cases our quite large software project needed to handle. Below is a list of practices we came to value and apply when it comes to validation and exception handling.Read More…
  • Code
    JavaScript & AJAX
    Ember.js TestingEmber components retina preview
    When I started playing around with Ember.js almost a year ago, the testability story left something to be desired. You could unit test an object without any trouble, but a unit test is only one way to get feedback when you're building a software product. In addition to unit tests, I wanted a way to verify the integration of multiple components. So like most people testing rich JavaScript applications, I reached for the mother of all testing tools, Selenium.Read More…
  • Code
    PHP
    Authentication With Laravel 4Laravel 4 auth retina preview
    Authentication is required for virtually any type of web application. In this tutorial, I'd like to show you how you can go about creating a small authentication application using Laravel 4. We'll start from the very beginning by creating our Laravel app using composer, creating the database, loading in the Twitter Bootstrap, creating a main layout, registering users, logging in and out, and protecting routes using filters. We've got a lot of code to cover, so let's get started!Read More…