Building a simple Dependency Injection Container with PHP


If you are looking for a small Dependency Injection Container with PHP maybe you need have look to Pimple.

Pimple is a small Dependency Injection Container for PHP 5.3 that consists of just one file and one class (about 50 lines of code).

Now, keeping with my aim of reinvent the wheel, we will create a simple Dependency Injection Container basically to understand how does it work. Let’s start.

First of all: Do we really need a Dependency Injection Container (DIC)? If you are asking yourself this question, maybe you need to have look to Fabien Poetencier’s article.

We are going to work with a teorical problem like this:

Imagine we are going to build a service that uses one external REST API. We define your application with three classes:

  • App. The main application.
  • Proxy The part of the application that speaks with the external API
  • Curl. One curl wrapper to perform our http connections to the REST API

Our first approach can be:

<?php
class App
{
    private $proxy;

    public function __construct()
    {
        echo "App::__construct\n";
        $this->proxy = new Proxy();
    }

    public function hello()
    {
        return $this->proxy->hello();
    }
}

class Proxy
{
    private $curl;

    public function __construct()
    {
        $this->curl = new Curl();
    }

    public function hello()
    {
        echo "Proxy::__construct\n";
        return $this->curl->doGet();
    }
}

class Curl
{
    public function doGet()
    {
        echo "Curl::doGet\n";
        return "Hello";
    }
}

$app = new App();
echo $app->hello();

If we execute the script:

php example1.php 

App::__construct
Proxy::__construct
Curl::doGet
Hello

It works but we have one problem. Our application is strongly coupled. As we can see App creates a new instance of Proxy within the constructor and Proxy creates a new instance of Curl. That’s a problem especially if we want to use TDD. What happens if we want to mock Curl requests to test the application without using the real external service?. Dependency injection can help us here. We can change our application to:

<?php
class App
{
    private $proxy;

    public function __construct(Proxy $proxy)
    {
        echo "App::__construct\n";
        $this->proxy = $proxy;
    }

    public function hello()
    {
        return $this->proxy->hello();
    }
}

class Proxy
{
    private $curl;

    public function __construct(Curl $curl)
    {
        $this->curl = $curl;
    }

    public function hello()
    {
        echo "Proxy::__construct\n";
        return $this->curl->doGet();
    }
}

class Curl
{
    public function doGet()
    {
        echo "Curl::doGet\n";
        return "Hello";
    }
}


$app = new App(new Proxy(new Curl()));
echo $app->hello();

The outcome is exactly the same but now we can easily use mocks and use different configurations depending on the environment. Maybe your testing development does not have access to the real REST server.

Now our application isn’t coupled but as we can see our Dependency Injection becomes a mess. That’s one problem with DI. It’s pretty straightforward to inject simple things but when we have dependencies over a set of classes that’s becomes a difficult task. Because of that we can use Dependency Injection Containers.

If we choose Pimple as Dependency Injection Container we can refactor our application to:

<?php
class App
{
    private $proxy;

    public function __construct(Pimple $container)
    {
        echo "App::__construct\n";
        $this->proxy = $container['Proxy'];
    }

    public function hello()
    {
        return $this->proxy->hello();
    }
}

class Proxy
{
    private $curl;

    public function __construct(Pimple $container)
    {
        $this->curl = $container['Curl'];
    }

    public function hello()
    {
        echo "Proxy::__construct\n";
        return $this->curl->doGet();
    }
}

class Curl
{
    public function doGet()
    {
        echo "Curl::doGet\n";
        return "Hello";
    }
}

require_once 'Pimple.php';

$container = new Pimple();
$container['Curl'] = function ($c) {return new Curl();};
$container['Proxy'] = function ($c) {return new Proxy($c);};

$app = new App($container);
echo $app->hello();

But what is my problem with Pimple? Basically my problem is that my IDE cannot autocomplete correctly $container is an instance of Pimple not the “real” instance. OK It instantiated on demand the classes but it’s done at runtime and the IDE don’t know about that. We can solve it using an extra PHPDoc to give hints to the IDE but we also can use a different approach. Instead of using Pimple we can use this script:

<?php
class App
{
    private $proxy;

    public function __construct(Container $container)
    {
        echo "App::__construct\n";
        $this->proxy = $container->getProxy();
    }

    public function hello()
    {
        echo "App::hello\n";
        return $this->proxy->hello();
    }
}

class Proxy
{
    private $curl;

    public function __construct(Container $container)
    {
        echo "Proxy::__construct\n";
        $this->curl = $container->getCurl();
    }

    public function hello()
    {
        return $this->curl->doGet();
    }
}

class Curl
{
    public function doGet()
    {
        echo "Curl::doGet\n";
        return "Hello";
    }
}

class Container
{
    public function getProxy()
    {
        return new Proxy($this);
    }

    public function getCurl()
    {
        return new Curl();
    }
}

$app = new App(new Container());
echo $app->hello();

The idea is the same than Pimple but now we have created our custom Dependency Injection Container without extending any library and now our IDE will autocomplete the fucntion names without problems. If we want to share objects instead creating new ones each time we call the factory function of the container we can change a little bit our Container (the same way than Pimple::share) with a simple singleton pattern:

class Container
{
    static $proxy;
    public function shareProxy()
    {
        if (NULL === self::$proxy) self::$proxy = new Proxy($this);
        return self::$proxy;
    }

    public function getCurl()
    {
        return new Curl();
    }
}

And that’s all. What do you think?

Advertisements

About Gonzalo Ayuso

Web Architect. PHP, Python, Node, Angular, ionic, PostgreSQL, Linux, ... Always learning.

Posted on July 9, 2012, in Dependency Injection, php, Technology and tagged , , . Bookmark the permalink. 19 Comments.

  1. why would sharing the same object be more advantageous than to create new ones? How symfony2 DI does it, the same as pimple?

    • Share one instance or create a new one depends on our needs. Sometimes we want to share instances (for exaple Database connections).

      Maybe this cycle of life of our instances haven’t got the same importance in PHP than J2EE applications. When PHP request ends, all instances are destroyed. We don’t have the same persistence between requests than J2EE or .NET applications.

      SF2 allows to create or share instances. It have the same idea than Pimple.

  2. But passing the full container as a parameter isn’t one of those things to avoid? You should only pass the real dependencies of each class

    • If you only need to pass one or two dependencies you can pass each one within the constructor, but if you need to pass more, your constructor can become a mess difficult to mantain. You can decide to pass dependencies trough a setter, but then your you need to check if required dependency is loaded or not before using them.

      If you use a DIC, you will group all dependencies inside the Controller and your code will remain as decoupled as passing each dependency and it’s also clean. You must also take into account that the DIC is not a monster with all the instances created. It’s a set of factory/singleton functions that creates the instances on demand (only when factory method is called). Pimple and “my alternative version” works in the same way.

      We need to avoid coupling classes and the usage of Dependency injection Containers is only one pattern we can use to handle Dependencies and Dependencies trees centrally

      • Ok, I understand that but I think that if you need more dependencies than you can manage through a constructor you probably need to refactor this class 😉

        Also, this DIC is not a monster but if your project is large enough you probably won’t to pass the full DIC to each class in your project. I think it’s more something of personal taste but I don’t like having the DIC as a parameter.

        Anyway thanks for the post it’s very clear.

      • Gonzalo Ayuso

        Past days I was discussing in twitter about this problem. What happens if the DIC is getting bigger?. Probably the solution is the solution that sf2 has implemented (http://symfony.com/doc/current/book/service_container.html).
        Maybe to create a configurarion files describing the dependencies in deep and use a Registry pattern to fetch them dynamically. Anyway I don’t really like this solution. I’m still thinking on it 🙂

  3. Wow, finally – someone explained DI in a way that anyone can understand! This is a great little write-up, thank you!

    I also love the idea of using methods with defined return-types, but I’d like to point out that your custom container-type may have some shortcomings that Pimple implements very elegantly: shared vs non-shared dependency patterns, without having to repeatedly implement the pattern by hand – and the ability to “extend” dependencies without actually loading them. These requirements were solved very elegantly by Pimple, using a very minimal amount of code.

    You also have no straight-forward support for creating a Container for testing, since you have no direct way to replace the definitions of the dependencies it delivers; so next, you would need to extend your class with setProxy() and setCurl() methods, etc.

    When errors occur, there’s a good chance you will want to enumerate the items in the container too, for diagnostic purposes – another thing Pimple provides. Eventually you end up just implementing Pimple again.

    So my personal preference would be to extend the Container-class from Pimple instead – you can still add the methods with defined return-types and use those in your application, so your codebase gets better IDE support.

    That said, your article clearly demonstrates what a DI container actually is and does – even if that’s not how I’d recommend implementing it in the real world, this article should provide lots of people with some valuable insight!

    • Gonzalo Ayuso

      Wow! Extend Pipmple class using methods with defined return-types. I did not take into account. It mixs the good parts of both worlds. I need to work on it. Txs!

  4. I am sorry, but I think you just implemented a Factory Pattern, and not a DIC.

    The whole idea of DIC is to configure it (select which classes get returned for each request) at bootstrap.
    With your solution you have to implement each getSomething() by hand.

    • Gonzalo Ayuso

      I need to implement getSomething() by hand, but if you use Pimple you also need to create a closure with the method. The idea is the same (I take it reading the code of Pimple indeed). The Container is a set of factory (and singleton also) methods. We use it to collect all dependences centrally. Pimple has some extra features (error trace for example). It and creates (factory) the new instance or shares (singleton) when user access to class properties as array elements (Pimple implements ArrayAccess).

      I only add methods with defined return-types because I like to help to the IDE with the autocompletion (with Pimple we need to use PHPDoc if we want to help to the IDE).

      I really like to debate about this kind of things 🙂

  5. I understand your need for autocompletion. 🙂

    If you need it, you could “mix” the factory and the DIC patterns, and create something like this:

    interface HttpClient;
    interface Proxy;

    class Container
    {
    protected $pieces = array();

    public function setProxy(Proxy $p)
    {
    $this->pieces[‘proxy’] = $p;
    return $this;
    }

    /**
    * @return Proxy
    */
    public function getProxy()
    {
    return $this->pieces[‘proxy’];
    }

    … the same for HttpClient …
    }

    so you could configure it at bootstrap-time:

    class MyProxy implements Proxy { … }
    class MyCurl implements HttpClient { … }

    $container = new Container();
    $container
    ->setProxy(new MyProxy())
    ->setHttpClient(new MyHttpContainer())
    ;

    $app = new App($container);
    app->run();

    Now you can reuse your container, just adding classes of dependency (say: Mailer, DBAdapter, ViewRender, Logger, etc.)

    This “pattern” can be complicated at will (allowing different choices per dependency class, for example) 🙂

  6. Here is a little idea:

    How about dependency injection with Traits?

    ————————————-

    trait database {

    private function query() {
    // Bla bla bla
    }

    }

    class user {

    use database;

    public function getID ( $user_name )
    {
    $id = $this->query(‘SELECT id FROM user WHERE id = $1’, $user_name);
    return $id;
    }

    }

    $user = new user();
    $user->getID(‘me’);

    ————————————-

    Been thinking about this, as this solves some of the basic problems. For testing, its not harder then DI containers, or DI with closures. You can just mock the trait…

    This actually opens up a complete different way of simplified DI… and traits also get auto loaded… In this database example, you can solve the “permantent” connection problem, by using the same method like Pimple. People falls back on a static variable to keep the closure reference, so just make a static $connection variable in the database class.

    • I still haven’t embrace traits. I see them like a polite way of old style copy-paste. Provably I’m wrong, but I still prefer “traditional” things.

      Also I see problems within your approach. What happens if I need to mock traits? Imagine for example that I want to run tests with my development database in my development environment. I need to change the code and use different traits. OK maybe we can use one connection string from one configuration file, but if we want to mock the connection of one external webservice? I don’t see the light with traits.

  7. This is a very clear article and provides some depth in discussion of DI but the only thing that I am not sure about is that in your example, the container/Pimple is very intrusive in the implementation. The container should be transparent to all business classes. Maybe you have some other reasons that you use DI this way. I would like to know your opinion.

    • 100% agree. When I read again this post written one year ago it’s giving me the creeps. Inject the container it isn’t a good thing (I din’t know it when I wrote the article). We should avoid it as a plague. Touche! 🙂

    • No kidding Jethro …

      Pimple is a basically good idea, but when it comes down to actual code implementation on large projects, it quickly starts to become a large mess. So does the whole Dependency Injection ( let it be constructor, or setter implementations ) using Pimple.

      Let alone how IDE’s handle Pimple ( coding support is limited unless you specifically define the PhpDoc param ( in other words, doing twice the amount of work, and a higher risk of sync problems between the code / Doc, as the code grows and things get added / removed / changed )).

  8. Awesome tutorial, custom solution without Pimple is out of box.

    Congrats!

  1. Pingback: Dependency Injection Containers with PHP. When Pimple is not enough. « Gonzalo Ayuso | Web Architect

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

%d bloggers like this: