Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Creating a Container in the Wild

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

A whole mini-series on Symfony's Dependency Injection Container? Yes! Do you want to really understand how Symfony works - and also Drupal 8? Then you're in the right place.

That's because Symfony is 2 parts. The first is the request/routing/controller/response and event listener flow we talked about in the first Symfony Journey part. The second half is all about the container. Understand it, and you'll unlock everything.

Setting up the Playground

Symfony normally gives you the built container. Instead of that, let's do a DIY project and create that by hand. Actually, let's get out of Symfony entirely. Inside the directory of our project, create a new folder called dino_container. We're going to create a PHP file in here where we can mess around - how about roar.php.

This file is all alone - it has nothing to do with the framework or our project at all. We're flying solo.

I'll add a namespace Dino\Play - but only because it makes PhpStorm auto-complete my use statements nicely.

Let's require the project's autoloader - so go up one directory, then get vendor/autoload.php:

namespace Dino\Play;
use Monolog\Logger;
require __DIR__.'/../vendor/autoload.php';
... lines 8 - 11

Great, now we can access Symfony's DependencyInjection classes and a few other libraries we'll use, like Monolog.

Using Monolog Straight-Up

In fact, forget about containers and Symfony and all that weird stuff. Let's just use the Monolog library to log some stuff. That's simple, just $logger = new Logger(). The first argument is the channel - it's like a category - you'll see that word main in the logs. Now log something: $logger->info(), then ROOOAR:

... lines 1 - 4
use Monolog\Logger;
require __DIR__.'/../vendor/autoload.php';
$logger = new Logger('main');

Ok, let's see if we can get this script to yell back at us. Run it with:

php dino_container/roar.php

Fantastic! If you don't do anything, Monolog spits your messages out into stderr.

To pretend like this little file is an application, I'll create a runApp() function that does the yelling. Pass it a $logger argument and move our info() call inside:

... lines 1 - 8
$logger = new Logger('main');
function runApp(Logger $logger)

I'm just doing this to separate my setup code - the part where I configure objects like the Logger - from my real application, which in this case, roars at us. It still works like before.

Create a Container

Now, to the container? First, the basics:

  1. A service is just a fancy name a computer science major made up to describe a useful object. A logger is a useful object, so it's a service. A mailer object, a database connection object and an object that talks to your coffee maker's API: all useful objects, all services.

  2. A container is an object, but it's really just an associative array that holds all your service objects. You ask it for a service by some nickname, and it gives you back that object. And it has some other super-powers that we'll see later.

Got it? Great, create a $container variable and set it to a new ContainerBuilder object.

... lines 1 - 5
use Symfony\Component\DependencyInjection\ContainerBuilder;
... lines 7 - 9
$container = new ContainerBuilder();
... lines 11 - 19

Hello Mr Container! Later, we'll see why Mr Container is called a builder.

Working with it is simple: use set to put a service into it, and get to fetch that back later. Call set and pass it the string logger. That's the key for the service - it's like a nickname, and we could use anything we want.

TIP The standard is to use lowercase characters, numbers, underscores and periods. Some other characters are illegal and while service ids are case insensitive, using lower-cased characters is faster. Want details? See github.com/knpuniversity/symfony-journey/issues/5.

Then pass the $logger object:

... lines 1 - 9
$container = new ContainerBuilder();
$logger = new Logger('main');
$container->set('logger', $logger);
... lines 13 - 19

Now, pass $container to runApp instead of the logger and update its argument. To fetch the logger from the container, I'll say $container->get() then the key - logger:

... lines 1 - 11
$container->set('logger', $logger);
function runApp(ContainerBuilder $container)

The logger service goes into the container with set, and it comes back out with get. No magic.

Test it out:

php dino_container/roar.php

Yep, still roaring.

Adding a Second Service

A real project will have a lot of services - maybe hundreds. Let's add a second one. When you log something, monolog passes that to handlers, and they actually do the work, like adding it to a log file or a database.

Create a new StreamHandler object - we can use it to save things to a file. We'll stream logs into a dino.log file:

... lines 1 - 3
... lines 5 - 12
$handler = new StreamHandler(__DIR__.'/dino.log');
... lines 14 - 22

Next, pass an array as the second argument to our Logger with this inside:

... lines 1 - 12
$handler = new StreamHandler(__DIR__.'/dino.log');
$logger = new Logger('main', array($handler));
... lines 15 - 22

Cool, so try it out. Oh, no more message! It's OK. As soon as you pass at least one handler, Monolog uses that instead of dumping things out to the terminal. But we do now have a dino.log.

With things working, let's also put the stream handler into the container. So, $container->set() - and here we can make up any name, so how about logger.stream_handler. Then pass it the $streamHandler variable:

... lines 1 - 12
$handler = new StreamHandler(__DIR__.'/dino.log');
$container->set('logger.stream_handler', $handler);
... lines 15 - 23

Down in the $logger, just fetch it out with $container->get('logger.stream_handler'):

... lines 1 - 13
$container->set('logger.stream_handler', $handler);
$logger = new Logger('main', array($container->get('logger.stream_handler')));
... lines 16 - 23

PhpStorm is highlighting that line - don't let it boss you around. It gets a little confused when I create a Container from scratch inside a Symfony project.

Try it out:

php dino_container/roar.php
tail dino_container/dino.log

Good, no errors, and when we tail the log, 2 messages - awesome!

Up to now, the container isn't much more than a simple associative array. We put 2 things in, we get 2 things out. But we're not really exercising the true power of the container, yet.

Leave a comment!

Login or Register to join the conversation

Hey Ryan!
Could you tell me which library do you use for syntax highlighting in this tutorial, if it is not a commercial secret of course? :)
I like feature to expand whole script if I need, it's a very cool! Does it your custom solution or you use some third-party library for it?

1 Reply

Wow, looks like you wrote this before becoming the part of the team. :)


Hey Serge,

Haha, you're right! It was about 3 years ago, wow... Nice catch btw! ;)


1 Reply

Ah, glad you like it! The expanding code-block is something internal - the projects are built with a tool that manages a "diff" for each step (https://github.com/knpunive... and those are used to fuel the code blocks later. No more "out of date" code blocks :). Keep watch - when that's ready, parts of it will get released to help make collaboration on the code blocks possible.

But the syntax highlighting itself is totally open source :). Behind the scenes. Highlight.js is used to transform the code blocks into markup. On the frontend, the "Tomorrow Night" theme from highlight.js is used (it's one of the selections on the demo): https://highlightjs.org/sta....

Hope that helps :). Eventually I hope the "expanding" can be available to use on other sites (e.g. blogs) because it's nice to be able to see the whole context!


Yes, it helps, thanks! I suspect that highlight.js is used by `hljs` class wrapped each code-block, but expanding code-blocks are awesome! :)


I know that this tutorial is quite old but I just like to point out that I guess that there has been some changes to monolog since then...

I believe that currently monolog wont spit the stuff out to stderr if there is no handler attached so the script wont actually "roar" at all :(
So I guess you have to attach a StreamHandler('php://stderr') right from the start.

I wonder how the Symfony container thingy has changed from version 2 until now - heading on to the next chapter ;)


Hey @El KuKu!

Ah, thanks for the note about that - I’m sure that’s true!

> I wonder how the Symfony container thingy has changed from version 2 until now - heading on to the next chapter ;)

The answer is... a LOT has changed. But also... not a lot. If you look at many of the files we open in this tutorial, they now look much different. The container also now has autowiring, autoconfigure, auto registration, service locators and private services by default. On the other hand, all the philosophies that we go through remain exactly the same: the idea of the ContainerBuilder, service definitions, compiler passes and the dumping of the container to a cached PHP file are all still totally relevant.

We do have it on our list to update this tutorial some time - it’s a great topic, and plenty has changed.


Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.x-dev", // 2.6.x-dev
        "doctrine/orm": "~2.2,>=2.2.3", // v2.4.6
        "doctrine/doctrine-bundle": "~1.2", // v1.2.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.5.0
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.7
        "symfony/monolog-bundle": "~2.4", // v2.6.1
        "sensio/distribution-bundle": "~3.0", // v3.0.9
        "sensio/framework-extra-bundle": "~3.0", // v3.0.3
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "~0.2" // 0.2
    "require-dev": {
        "sensio/generator-bundle": "~2.3" // v2.4.0