Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Yaml for Service Definitions

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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.

... 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:

... 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:

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:

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:

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:

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:

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!

12
Login or Register to join the conversation
Helmi Avatar

For me this is the most interesting course that you will not find anywhere else. Thanks guys, and please upgrade to symfony 4 if possible :D

Reply

Hey Helmi!

I love this course too :). It's on our list to update - but probably not until Symfony 5.

Cheers!

1 Reply

Lol, so it goes! We HAVE released a deep dive into the HttpKernel for Symfony 5 - https://symfonycasts.com/sc... - but haven't gotten to this one yet!

Reply
Default user avatar
Default user avatar NagiReddy | posted 5 years ago

Hi, how can we parse this yaml through java? If we parse the yaml file in java,will it call the java method

Reply

In that case I think you will need to find a yaml parser written in Java

Cheers!

Reply
Default user avatar
Default user avatar Michael Sypes | posted 5 years ago

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/curr..., 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

Reply

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-symfony-2-7-dependency-injection-improvements#improved-yaml-service-definition-syntax - it's still a bit complex, but better.

Cheers!

2 Reply
Default user avatar
Default user avatar Michael Sypes | weaverryan | posted 5 years ago

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!

Reply
Default user avatar

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.

Reply

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!

Reply
Default user avatar

Awesome. Thanks for the quick reply!

Reply
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
    }
}
userVoice