Dependency Injection Containers with PHP. When Pimple is not enough.


Two months ago I wrote an article about Dependency Injection with PHP and Pimple. After the post I was speaking about it with a group of colleagues and someone threw a question:

What happens if your container grows up? Does Pimple scale well?

The answer is not so easy. Pimple is really simple and good for small projects, but it becomes a little mess when we need to scale. Normally when I face a problem like that I like to checkout the Symfony2 code. It usually implements a good solution. There’s something I really like from Symfony2: it’s a brilliant component library and we can use those components within our projects instead of using the full stack framework. So why don’t we only use the Dependency Injection component from SF2 instead Pimple to solve the problem? Let’s start:

We are going to use composer to load our dependencies so we start writing our composer.json file. We want to use yaml files so we need to add “symfony/yaml” and “symfony/config” in addition to “symfony/dependency-injection”:

{
    "require": {
        "symfony/dependency-injection": "dev-master",
        "symfony/yaml": "dev-master",
        "symfony/config": "dev-master"
    },
    "autoload":{
        "psr-0":{
            "":"lib/"
        }
    },
}

Now we can run “composer install” command and we already have our vendors and our autolader properly set.

We are going to build exactly the same example than in the previous post:

<?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";
    }
}

Now we create our file “services.yml” describing our dependency injection container behaviour:

services:
  app:
    class:     App
    arguments: [@Proxy]
  proxy:
    class:     Proxy
    arguments: [@Curl]
  curl:
    class:     Curl

and finally we can build the script:

<?php
include __DIR__ . "/vendor/autoload.php";

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yml');

$container->get('app')->hello();

IMHO is as simple a Pimple but much more flexible, customizable and it’s also well documented. For example we can split our yaml files into different ones and load them:

<?php
include __DIR__ . "/vendor/autoload.php";

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services1.yml');
$loader->load('services2.yml');
$container->get('app')->hello();

An we also can use imports in our yaml files:

imports:
  - { resource: services2.yml }
services:
  app:
    class:     App
    arguments: [@Proxy]

If you don’t like yaml syntax and you prefer XML (I know. It looks insane :)) you can use it, or even programatically with PHP.

What do you think?

Source code in github (see the README to install the vendors)

22 thoughts on “Dependency Injection Containers with PHP. When Pimple is not enough.

    1. AFAIK Symfony caches everything, but probably if we use the standalone component we need to build our cache system ourself (I hope that it’s and there isn’t any “magic” cache system that caches without my permission :)). I will try to check it (another issue you added to my todo list).

      Anyway I don’t think that parse YAML file and create container class involves significant performance problems (And I’m a micro-optimization junkie). Yes. I know. That’s PHP and everything dies within each request (that’s the good a and the bad part of PHP)

  1. I think Dependency Injection Containers fits better with Java architecture (when you start your tomcat app, all the injections are made once and all your services are initialized). The same with ORMs.
    Because the PHP architecture I think is better to use a different way to organice your code. Maybe based on traits and bindTo (disclaimer: this ideas are under active development… 🙂 )

    1. I don’t understand the problem. I’ve just fork the project and test are ok (I need to hack a little bit composer,json to install it. Problems with Behat). Have you got a failing test? (tests are easy to understand)

  2. You can scale pimple containers in a manageable way. Check out the “Packaging a Container for reusability” section in http://pimple.sensiolabs.org.

    Here’s the idea:

    ““php
    class SomeContainer extends Pimple
    {
    public function __construct()
    {
    $this[‘parameter’] = ‘foo’;
    $this[‘object’] = function () { return stdClass(); };
    }
    }

    $container = new Pimple();

    // define your project parameters and objects
    // …

    // embed the SomeContainer container
    $container[’embedded’] = $container->share(function () { return new SomeContainer(); });

    // use it
    $container[’embedded’][‘object’]->…;
    ““

  3. How do you import envirronment variables in the Symfony DI ? for instance , i want to configure doctrine with yaml metadatas , how would you define the path to the yaml files folder in your yaml di configuration file ? thanks

      1. cool!. I will check it out. I normally use SF’s DI with silex with a code similar than the code of this post but creating a service provider sounds good (sounds like the good way, indeed)

    1. setting up variables is pretty straightforward. just add parameters section. Check it out here for example: https://gonzalo123.com/2013/01/14/handling-several-dbal-database-connections-in-symfony2-through-the-dependency-injection-container-with-php/

      Symfony projects uses a cool extensions to use Doctrine within the DI, but if you don’t use sf (as MVC) and only de DI component you must create the the service (or create a service). In the previous link I show how to use DBAL as a service. Configure Doctrine as a raw service is in my todo-list (but as I normally don’t use Doctrine it isn’t in the top :))

Leave a reply to Agustín Gutiérrez Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.