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)

About these ads

About Gonzalo Ayuso

Web Architect specialized in Open Source technologies. PHP, Python, JQuery, Dojo, PostgreSQL, CouchDB and node.js but always learning.

Posted on September 3, 2012, in Dependency Injection, php, Symfony, Technology and tagged , , , , , . Bookmark the permalink. 19 Comments.

  1. I wonder if $loader “compiles” the yaml and generates a container php class that is invoked each request, or the services.yml is read each request. Both possibilities scares me… but i´m curious about it… :-)

    • 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)

  2. 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… :-) )

  3. XML… Insane ? :) Configuring service in XML is more flexible and concise than YAML in my point of view.

  4. Of course, if you’re on PHP 5.4, there’s always the Aura DI container: https://github.com/auraphp/Aura.Di

    • 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)

  5. 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’]->…;
    ““

  6. 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. Pingback: Dependency Injection Containers with PHP. When Pimple is not enough. | Gonzalo Ayuso | Web Architect « towerysdev2

  2. Pingback: Handling several DBAL Database connections in Symfony2 through the Dependency Injection Container with PHP « Gonzalo Ayuso | Web Architect

  3. Pingback: How to configure Symfony’s Service Container to use Twitter API « Gonzalo Ayuso | Web Architect

  4. Pingback: Scaling Silex applications « Gonzalo Ayuso | Web Architect

  5. Pingback: 30+ Links to PHP Training Materials, News about Zend Optimizer+, MySQL 5.6 Release and More | Zfort Group Blog

  6. Pingback: Dependency Injection: Service Locator, Factory, Constructor Injection, DIC | Mendoweb Blog

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

Follow

Get every new post delivered to your Inbox.

Join 998 other followers

%d bloggers like this: