Buy

Creating service definitions in PHP is just one way to do things: you can configure this same stuff purely in Yaml or XML. And since Symfony uses Yaml files, let's use them too.

Up top, create a $loader object - set it to new YamlFileLoader() from the DependencyInjection component. This takes two arguments: the $container and a new FileLocator. That's a really simple object that says: "Yo, look for files inside of a config directory". To make it read a Yaml file, call load() and point it at a new file called services.yml.

44 lines dino_container/roar.php
... lines 1 - 11
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/config'));
$loader->load('services.yml');
... lines 15 - 44

This code tells the container to go find service definitions in this file.

Now create a config/ directory and put that services.yml in there. Our goal is to move all of this Definition stuff into that Yaml file. We'll start with the logger. I'll comment out all of the $loggerDefinition stuff, but keep it as reference:

44 lines dino_container/roar.php
... lines 1 - 15
/*
$loggerDefinition = new Definition('Monolog\Logger');
... lines 18 - 27
*/
... lines 29 - 44

Definitions in services.yml

The whole purpose of this Yaml file is to build service Definition objects. So it should be no surprise why we start with a services key. Next, since the nickname of the service is logger, put that. Indented below this is anything needing to configure this Definition object: to train the container on how to create the logger service.

Almost every service will at least have two parts: the class - set it to Monolog\Logger and arguments. We know we have 2 arguments. The first is the string main and the second is an array with a reference to another service. To add the first, just say main:

6 lines dino_container/config/services.yml
services:
logger:
class: Monolog\Logger
arguments:
- 'main'

Before we put the second argument, let's just make sure things are not exploding so far:

php dino_container/roar.php

No explosion! It's printing out to the screen, but if you look in the log, it's not adding anything there - the new ones should be from 8:46.

When we say "go load services.yml", it creates a new Definition object for logger. But that logger doesn't have any handlers yet, so it's reverting to that default mode where it just dumps to the screen.

To hook up the first handler, our constructor needs a second argument. Add another line with a dash, then paste logger.stream_handler. If we did just this, it'll pass this in as a string. In PHP code, this is where we passed in a Reference object. To make this create a Reference, we put an @ symbol in front. I'll surround this in quotes - but you don't technically need to:

8 lines dino_container/config/services.yml
services:
logger:
class: Monolog\Logger
arguments:
- 'main'
- '@logger.stream_handler'
... lines 7 - 8

Try it! Woh, explosion! Argument 2 should be an array, but I'm passing an object. I was sloppy. For my second argument, I'm passing literally one object. But we know the second argument is an array of objects. So in Yaml, we need to surround this with square brackets to make that an array:

8 lines dino_container/config/services.yml
services:
logger:
class: Monolog\Logger
arguments:
- 'main'
- ['@logger.stream_handler']
... lines 7 - 8

This time, no errors!

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

The console message is gone, but the log file gets it.

The big point is that you can create Definition objects by hand, OR use a config file to do that for you. When services.yml is loaded, it is creating those same Definition objects.

And as you'll see in a bit, if you want to get really advanced, you'll want to understand both ways.

addMethodCall in Yaml

Next, we need to move over the addMethodCall stuff. In Yaml, add a calls key. The bummer of the calls key is that it has a funny syntax. Add a new line with a dash like arguments. We know that the method is called debug and we need to pass that method a single string argument. In Yaml, it translates to this. Inside square brackets pass debug, then another set of square brackets for the arguments. If we wanted to pass three arguments, we'd just put a comma-separated list. We'll just paste the message in as the only argument:

10 lines dino_container/config/services.yml
services:
logger:
class: Monolog\Logger
... lines 4 - 6
calls:
- ['debug', ['Logger just got started!!!']]
... lines 9 - 10

I know that's ugly. But under the hood, that's just calling addMethodCall on the Definition and passing it debug and this arguments array. Let's go back to the terminal and try it:

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

Tail the logs, and boom! Our extra "logger has started" message is back. Now let's do the same for the other method call. It's exactly the same, except the argument is a service. Copy that name, add a new line under calls, say pushHandler, @ then the paste our handler name:

11 lines dino_container/config/services.yml
services:
logger:
... lines 3 - 6
calls:
- ['pushHandler', ['@logger.std_out_handler']]
- ['debug', ['Logger just got started!!!']]
... lines 10 - 11

Test it out.

php dino_container/roar.php

Yes! Both handlers are back! And congrats! Our entire logger definition is now in Yaml. And this is a pretty complicated example - most services are just a class and arguments. Celebrate by removing the commented-out $loggerDefinition stuff.

Leave a comment!

  • 2016-07-25 shing

    Awesome. Thanks for the quick reply!

  • 2016-07-25 weaverryan

    Hi there!

    I just tried it, and I *am* able to Refactor->Rename a YAML key (PhpStorm 2016.1 with activated Symfony plugin). However, it doesn't really work - I think it only has "YAML" intelligence - somehow maybe replacing other YAML keys that reference this (I'm not really sure). What I mean is: when I refactored the name of the service, it did *not* refactor my usage of that service from inside a controller.

    So, I also find and replace to do this. It's why I like to make sure my service names are *just* long enough to be unique strings in my project. I also use "git grep service_name" to quickly find the usages.

    In other words, afaik, you're not missing anything - I don't think it works sadly!

  • 2016-07-25 shing

    Is it me or it's not possible to refactor name for yaml services in phpstorm?

    I'm using phpstorm 2016.2 and when I try to refactor and rename, it wont let me with an "error message" that has a thunderbolt and ''is not a valid identifier. I'm currently doing find all and replace.

  • 2015-06-08 Michael Sypes

    Ah, yes! I left out an array nesting. (I am going to stick with my original stance, however, that the syntax used for each could be the same, in terms of using dashes or brackets to indicate arrays) Readers/Viewers should still check out both of our links to the Symfony documents, so they are clear in understanding what the YAML is presenting.

    Great series! Great site!

  • 2015-06-08 weaverryan

    Hey Michael!

    For a minute, I thought you were right - and I was excited because your way looks much easier to me! The problem is that there can be multiple calls, so the syntax would *really* look like this:


    services:
    my_service:
    class: KnpU\SomeClass
    calls:
    -
    - 'debug'
    - ['Logger just started!']
    -
    - 'info'
    - ['It is still starting!!']

    But in Symfony 2.7, there *is* a slightly shorter syntax http://symfony.com/blog/new-in... - it's still a bit complex, but better.

    Cheers!

  • 2015-06-05 Michael Sypes

    There's an implied complexity where there is none: "The bummer of the calls key is that it has a funny syntax..."
    As I understand YAML (see http://symfony.com/doc/current..., the calls key could have the same syntax as that used by the arguments key, i.e.

    calls:
    - 'debug'
    - ['Logger just got started!!!']

    or vice-versa, arguments could be written as calls is

    arguments: ['main', ['@logger.stream_handler']]

    Feel free to correct me if I'm wrong