Using the Symfony2 DIC as a standalone component

    This post is part of the ”Symfony2 components in your own userland” series; here is a list of all the articles contained this series:

  1. Using the Symfony2 Dependency Injection Container as a standalone component
  2. Launching PHPUnit tests from a browser with Symfony2
  3. Using the Console component to write an interactive installer for Composer

The Symfony2 components are powerful libraries that you can easily integrate in your own code: in this article we will se how to integrate the dependency injection container in a framework-free PHP small library.

Premise

have you ever heard about the QOTD protocol1?

It’s a standard protocol, defined in RFC-0865, for a dummy client/server interaction that allows a server to listen on port 17 and emit a quote in ASCII text whenever a connection is opened by a client.

To give you an example, try this from the command line1:

1
telnet alpha.mike-r.com 17

First love is only a little foolishness and a lot of curiosity, no
really self-respecting woman would take advantage of it.

So today we are going to see how to implement a QOTD script in PHP, using Symfony2’s DIC: the mini-library that we are going to write is dummy and really easy, so you won’t get lost following its flow; I won’t use any autoloader – apart for the DIC stuff – so the code will exactly look like the ancient PHP, the one you daily need to refactor.

QOTD scripts

We have 3 scripts that compose our small library; the first one is the entry point:

index.php
1
2
3
4
5
6
<?php

require 'QOTD.php';

$qotd = new QOTD();
echo $qotd->enchantUs() . "\n";

and then the QOTD class:

Quotes Of The Day generator class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

class QOTD
{
  protected $wtfMode;
  protected $quotes = array(
      "michael jordan" => array(
          "Always turn a negative situation into a positive situation",
          "I can accept failure, everyone fails at something. But I can't accept not trying"
      ),
      "Mahatma Gandhi"  => array(
          "A coward is incapable of exhibiting love; it is the prerogative of the brave"
      ),
  );

  public function __construct($wtfMode = false)
  {
      $this->wtfMode = $wtfMode;
  }

  public function enchantUs()
  {
      $authorQuotes = $this->quotes[array_rand($this->quotes)];

      if ($this->wtfMode === true && is_int(time(true)/2)) {
        return "WTFed!!!";
      }

      return $authorQuotes[array_rand($authorQuotes)];
  }
}

As you see, the implementation is trivial: we have an array of $quotes and in the enchantUs() method we extract a random quote from that array: note that there is a boolean parameter – in the constructor – which enables or disables the WTF mode; when the mode is active, if

1
2
3
<?php

is_int(time(true)/2)

is true the QOTD class will output WTFed!!! instead of the usual quote from $quotes.

Let’s say that we also want to creare a class for Michael Jordan’s quotes:

Michael Jordan quotes
1
2
3
4
5
6
7
8
9
10
11
<?php

class JordanQOTD extends QOTD
{
  protected $quotes = array(
      "michael jordan" => array(
          "Always turn a negative situation into a positive situation",
          "I can accept failure, everyone fails at something. But I can't accept not trying"
      ),
  );
}

which basically restricts the $quotes to MJ’s ones3.

If we want to change the class used to output quotes, we just need to edit the index.php:

index.php
1
2
3
4
5
6
7
<?php

require 'QOTD.php';
require 'JordanQOTD.php';

$qotd = new JordanQOTD();
echo $qotd->enchantUs() . "\n";

Enter Symfony2 Dependency Injection Container

The boss just told us that we’ll need to implement some more modes and lots of person-specific quote classes, with some other logic to decide which QOTD class to use and so on: your first decision is to try to parametrize the configuration of the QOTD “service”, using a DIC ; although the problem and its design are quite simple, it would be a good choice to have a single, central point to manage services used in your code and their configuration.

First of all, create a composer.json file in the root of your project, to manage the dependency to the DIC:

composer.json
1
2
3
4
5
6
{
    "require": {
        "php": ">=5.3.2",
        "symfony/dependency-injection": "2.0.10"
    }
}

then download Composer and install the DIC:

composer.json
1
2
3
wget http://getcomposer.org/composer.phar

php composer.phar install

Now you can edit the index.php to add some configuration:

index.php
1
2
3
4
5
6
7
8
<?php

require __DIR__ . '/vendor/.composer/autoload.php';
require 'QOTD.php';
require 'JordanQOTD.php';
require 'container.php';

echo $container->get('QOTD')->enchantUs() . "\n";

The container.php uses the Symfony2 DIC to register a QOTD service with a QOTD.mode argument:

configuration of the container
1
2
3
4
5
6
7
8
9
10
<?php

use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->register('QOTD', '%qotd.class%')
          ->addArgument('%QOTD.mode%');

$container->setParameter('qotd.class', 'QOTD');
$container->setParameter('QOTD.mode', false);

For example, we can modify the configuration to enable the WTF mode:

enabling WTF mode
1
2
3
4
5
6
7
8
9
10
<?php

use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->register('QOTD', '%qotd.class%')
          ->addArgument('%QOTD.mode%');

$container->setParameter('qotd.class', 'QOTD');
$container->setParameter('QOTD.mode', true);

or to change the service class, to only output MJ’s quotes:

changing the service class
1
2
3
4
5
6
7
8
9
10
<?php

use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->register('QOTD', '%qotd.class%')
          ->addArgument('%QOTD.mode%');

$container->setParameter('qotd.class', 'JordanQOTD');
$container->setParameter('QOTD.mode', false);

As you might understand, with a DIC it’s possible to drastically change the behaviour of your application editing a configuration file, with a bunch of additional lines of code into your own application; another great thing is that you can also use different “languages” to configure the DIC, for example YAML.

To do so, add the required dependency to composer:

composer.json
1
2
3
4
5
6
7
8
{
    "require": {
        "php": ">=5.3.2",
        "symfony/dependency-injection": "2.0.10",
        "symfony/config": "2.0.10",
        "symfony/yaml": "2.0.10"
    }
}
composer.json
1
php composer.phar update

then edit the container.php:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

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

$container = new ContainerBuilder();
$container->register('QOTD', '%qotd.mode%')
          ->addArgument('%mode%');

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

and then configure the DIC creating a container.yml:

1
2
3
4
5
services:
  QOTD:
    class: QOTD
    arguments:
      mode: true

INI is another – not documented in Symfony2’s docs – option:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
use Symfony\Component\Config\FileLocator;

$container = new ContainerBuilder();
$container->register('QOTD', '%qotd.class%')
          ->addArgument('%qotd.mode%');

$loader = new IniFileLoader($container, new FileLocator(__DIR__));
$loader->load('container.ini');
1
2
3
[parameters]
  qotd.class = "QOTD"
  qotd.mode = 1

There you go: with a few lines you have a completely working instance of the Symfony2 dependency injection container: organizing dependencies and services’ instantiation becomes very easy with this kind of layer and, since its implementation is trivial, I recommend you to gain familiarity with this component.

Notes
  1. Quote Of The Day ↩
  2. alpha-mike is the only known public service that implements QOTD protocol ↩
  3. This is not an optimal design for resolving such this kind of thing, but we’ll use it as it’s fairly simple to understand ↩

In the mood for some more reading?

...or check the archives.