Pragmatism in the real world

What problem does dependency injection solve?

Zend Framework 2 comes with a dependency injection container (DIC), as does Symfony 2 and Aura, along with many other PHP frameworks that target PHP 5.3 or higher nowadays. This article attempts to explore the problem that a DIC tries to solve.

Consider this simple (contrived!) example of an Album object that has an Artist object:

    class Album 
    {
        protected $artist;
        
        public function getArtistName()
        {
            return $artist->getName();
        }
    }

The question is how to we set the $artist member variable within this class?

There are a number of ways to do this. The easiest way is instantiate it directly:

    class Album 
    {
        protected $artist;
        
        public function __construct()
        {
            $this->artist = new Artist();
        }
        
        // etc
    }

This is Not Good Code (TM) on a number of levels. The most obvious problem is that suppose that at some point in the future you create a new class, SoloArtist, that overrides Artist, then Album will need to be reworked to handle this situation.

Dependency Injection is the term used when you write more flexible code that allows the dependent classes to be changed without having to change the class’ code. There are a number of different ways to inject the artist object into our album. The easiest way is to use a constructor:

    class Album 
    {
        protected $artist;
        
        public function __construct(Artist $artist)
        {
            $this->artist = $artist;
        }
        
        // etc
    }

The calling code then does this:

    $album = new Album($artist);
    // do something with $album

One problem with constructor injection is that if your class has many dependencies, then your constructor signature gets unwieldy and hard to read.

Another way is to use setters:

    class Album 
    {
        protected $artist;
        
        public function __construct()
        {
        }
        
        public function setArtist(Artist $artist)
        {
            $this->artist = $artist;
        }
        
        // etc
    }

With usage like this:

$album = new Album();
$album->setArtist($artist);
// do something with $album

Now, when you have multiple dependencies that you need to inject, each one is a separate set method and so we don’t have a long list of constructor parameters.

However, once you have multiple dependencies, the amount of code that you have to write to use this class gets bigger and bigger. As a result, if you use the class a lot, it ends up that you’re writing a lot of extra boiler-plate code every single time; just to configure the class!

The solution to this problem is called a Dependency Injection Container.

A specific container for our Album class could look something like this:

    class AlbumContainer 
    {
        public static function createAlbum()
        {
            $artist = self::createAConfiguredArtistSomehow();

            $album = new Album();
            $album->setArtist($artist);
            // more setXxx() calls here
            
            return $album;
        }

        // more methods as required
    }

(obviously, this example glosses over a number of real-world considerations…)

Now, to instantiate a new Album object, the code is:

$album = AlbumContainer::createAlbum();
// do something with $album

This makes using the class easier and gives us all the benefits of decoupling our dependencies. Having to write a new container class for every object gets old very quickly though and so to solve that, generic dependency injection containers have come into existence.

Hence, a dependency injection container is simply a component that holds dependency definitions and instantiates them for you. That is, it stores configuration information about which dependencies any registered class has and instantiates them according to the definition provided.

Don’t write your own though; use one of the many that are out there!

(Yes, I know that there’s many many posts on dependency injection out there. There’s always room for one more though! I also recommend perusing Fabien Potencier’s Dependency Injection with PHP 5.3 talk slides)

14 thoughts on “What problem does dependency injection solve?

  1. You can never have too many posts on Dependency Injection, especially ones making it clear why it allows code to be decoupled for easy reuse.

    If you have too many dependencies so that the constructor is unwieldy then the class probably has too many dependencies which needs addressing rather than finding a different way to inject them :)

    I think the container really comes into its own when there are the dependencies of the dependencies to set up as well as their dependencies and so on through the whole object graph. The boiler plate code just keeps growing and growing then if you do not use a container.

    1. Richard,

      I agree completely :)

      Though, sometimes you can end up with a service object that is interacting with a form, some mappers, mail and logging (say) which can get unwieldy in the constructor. It's rare though.

      Regards,

      Rob…

  2. I agree with Richard, if you have too many things to pass in the constructor, you have an architectural problem on another level.

    But as for constructor X setter injection i have come to terms with this:
    – constructor: required dependencies, those you cannot operate without
    – setter: optional dependencies, those that you can go around if not present

    This varies a lot depending on your logic, especially if not using a DIC or "trusting" someone else to setup the object for you, it allows for more control and certification on a certain level.

    One way to avoid a class that just interacts with too much is to use an event based system. For example, instead of having the registrationService do everything then email a confirmation mail, it fires a new-registration event that a confirmationListener can pickup and deal with.

    Interesting to see also the ZF auto-wiring solution and the Sf2 configuration based one, both have their place.

  3. Maybe I'm overlooking something obvious, but in the final example how does DI handle instantiating the Artist object in the desired way? Do you need to do something like this?

    $album::artist = new Artist();

    Furthermore, because the artist object is static it is shared among all instances. I'm struggling to see how that works well in practice.

  4. Duane,

    Yes, you'd need a way to configure the Artist object and clearly, in this contrived example, all albums would belong to the same artist. In a 'proper' DIC, you would provide information to the DIC on how to create the dependent objects.

    The code sample exists to show the principle of how it would be done. I've edited the post to create a better sample and to note that the sample isn't actually that useful anyway!

    Regards,

    Rob…

  5. Hari,

    Yes, I saw a presentation by Fabien in 2010 about DI and it was very good. I've hunted out the slides and added to the post.

    Regards,

    Rob…

  6. Rob,

    Great start to the article, but could you expand upon how you would implement the self::createAConfiguredArtistSomehow function?

    I'd like to see how you would handle creating the Artist whilst maintaining what you said in the beginning regarding not having to modify the Album class if the Artist class is overridden or modified.

  7. DIC is relatively new concept for PHP developers since most of them doesn't own pattern-oriented background. DI is simple: you have consumer, you have provider and a dependency between them that needs to be satisfied.

    DIC can be understood as a collection of Factory methods with IoC twist in it, serving this purpose and as a result, you get more holistic and concentrated result than any of the use cases would have been individually or alone.

    Nice article tho! That's true what Richard said: "You can never have too many posts on Dependency Injection". Most of them are just too analytical or except too much from users, what scares novices away.

  8. I find that all objects of a certain class need the same service injected. For me it feels more natural then to inject the service to the whole class, not to every object. But I haven't found any page talking about class-level injection.

    Does anyone here have any references about class-level injection?

Comments are closed.