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.