Buy

Let's finish this up by converting both handlers to Yaml. Do the "stdout" logger first - it's easier. Under the services key, add a new entry for logger.std_out_logger and give it the class name:

15 lines dino_container/config/services.yml
services:
logger:
... lines 3 - 10
logger.std_out_handler:
class: Monolog\Handler\StreamHandler
... lines 13 - 15

Peak back - this has one argument. So add the arguments key and give it the php://stdout. Those quotes are optional, and if you want, you can put the arguments up onto one line, inside square brackets:

15 lines dino_container/config/services.yml
services:
... lines 2 - 10
logger.std_out_handler:
class: Monolog\Handler\StreamHandler
arguments: ['php://stdout']
... lines 14 - 15

And as long as this still prints to the screen, life is good:

php dino_container/roar.php

Perfect!

Adding a Parameter in PHP

Now let's move the other handler. But this one is a little trickier: its argument has a PHP expression - __DIR__. That's trouble.

But hey, ignore it for now! Copy the service name and put it into services.yml. The order of services does not matter. Pass it the class and give it a single argument. This will not work, but I'll copy the __DIR__.'/dino.log in as the argument:

That's the basic idea, but since that __DIR__ stuff is PHP code, this won't work. But the solution is really nice.

The container holds more than services. It also has a simple key-value configuration system called parameters. In PHP, to add a parameter, just say $container->setParameter() and invent a name. How about root_dir? And we'll set its value to __DIR__:

23 lines dino_container/roar.php
... lines 1 - 10
$container = new ContainerBuilder();
$container->setParameter('root_dir', __DIR__);
... lines 13 - 23

That doesn't do anything, but now we can use that root_dir parameter anywhere else when we're building the container.

To use a parameter in Yaml, say %root_dir%:

19 lines dino_container/config/services.yml
services:
... lines 2 - 10
logger.stream_handler:
class: Monolog\Handler\StreamHandler
arguments: ['%root_dir%/dino.log']
... lines 14 - 19

With everything in Yaml, we can clean up! We don't need any Definition code at all in roar.php - just create the container, set the parameter and load the yaml file:

23 lines dino_container/roar.php
... lines 1 - 10
$container = new ContainerBuilder();
$container->setParameter('root_dir', __DIR__);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/config'));
$loader->load('services.yml');
runApp($container);
... lines 18 - 23

Ok, moment of truth!

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

It still prints! And it's still adding to our log file. And now all that service Definition code is sitting in services.yml.

Parameters in Yaml

Of course, you can also add parameters in Yaml. Add a parameters root key somewhere - order doesn't matter - and invent one called logger_start_message. Copy the string from the debug call and paste it. Now that we have a second parameter, we can grab the key and use it inside two percents:

22 lines dino_container/config/services.yml
parameters:
logger_startup_message: 'Logger just got started!!!'
... line 3
services:
logger:
... lines 6 - 9
calls:
... line 11
- ['debug', ['%logger_startup_message%']]
... lines 13 - 22

And this still works just like before.

This last point is actually really important. Yaml files that build the container only have three valid root keys: services, parameters and another called imports, which just loads other files. And that makes sense. After all, a container is nothing more than a collection of services and parameters. This point will be really important later. Because in Symfony, files like config.yml violate this rule with root keys like framework and twig.

With all this hard work behind us, we're about to see one of the coolest features of the container, and the reason why it's so fast.

Leave a comment!

  • 2015-09-04 Shairyar Baig

    Thank you so much for a detailed answer, makes a lot of sense. Out of curiosity I was using the Extension class just to play around and get an understanding of how things work.

    Once again many thanks for the wonderful explanation.

  • 2015-09-03 weaverryan

    Hey Shairyar!

    Ok, nice job with this! Creating extensions can be challenging, but you also don't need an extension class unless you're building a re-usable bundle. Otherwise - it's overkill: if you want to set a "app.comments" parameter, you could add a "parameters" key at the top of config.yml and set it there. The extension class is just if you need to have things like this easily configured by some external developer (because you've shared your bundle).

    To answer your questions, let me explain how all of this works :).

    A) Suppose you want to set a parameter called "app.comments" to 10 (this is basically what you're doing with your code). There are several ways to do this, the easiest is:


    # config.yml
    parameters:
    app.comments: 10

    Yep, that's it :). So, why would you make it harder and make an Extension class? Well, because you are sharing your bundle and you want to make it very easy for other people to control this value. So, then you create an AppExtension, with this code:


    $container->setParameter('app.comments', 10);

    And you remove the "app.comments" part from config.yml. Now, you have EXACTLY the same thing as before, and it's actually *still* not quite controllable by your user. So, you finally go *all* the way to your solution, where you create a "comments" value that can be configured by the user. Your AppExtension and Configuration will look exactly like what you have. And in config.yml, you will have:


    app:
    comments: 10

    So, now I'll answer your questions :)

    1) Yes, you can set a parameter directly with *only* an Extension class. That's the solution I show above with $container->setParameter('app.comments', 10); But, if you're just doing this, why not set the parameter in a simple configuration file (like config.yml)?

    2) In your setup, yes, you must have an app key with a comments key below it in order for all of this to work. But, two important things. First, the fact that we have an "app" key and a "comments" key below it has absolutely nothing to do with the fact that an "app.comments" parameter is finally set. The "app" in config.yml is what tells Symfony that the configuration below it is passed to the "App"Extension class. The "comments" is then passed to that class, which is why you're referencing "comments" in your AppExtension. In other words, you could have "foobar: 10" in config.yml and still set a parameter called app.comments, as long as you updated your Configuration and AppExtension files in 1 spot each. Second, you could omit the config.yml stuff completely if you gave the "comments" configuration key a default value in the Configuration class. You could set this to default to 10.

    3) About Twig, yes! First, passing a value from a controller to a template is always the simplest method. But, there are several ways to create "global variables" in Twig. One of them is simply in config.yml:


    twig:
    globals:
    comments: 10

    If you did this, you could use code like {% for i..comments %} in Twig and it would work. This does *not* set any "dependency injection parameters" - because we don't need that in this case.

    Phew! How does this all sound?

  • 2015-08-30 Shairyar Baig

    Okay so I went through the topic of dependency injection extensions of this video and that made a lot of sense and I was able to achieve what I wanted.

    This is what my AppExtension class looks like




    class AppExtension extends Extension
    {

    public function load(array $configs, ContainerBuilder $container)
    {

    $loader = new Loader\xmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
    $loader->load('services.xml');

    $configuration = new Configuration();
    $config = $this->processConfiguration($configuration, $configs);

    $container->setParameter('app.comments', $config['comments']);

    }

    public function getAlias(){
    return 'app';

    }
    }


    This is my configuration class and I understand now that this class is also important if one needs to set parameters, earlier I did not have this setup




    class Configuration implements ConfigurationInterface
    {

    public function getConfigTreeBuilder()
    {
    $treeBuilder = new TreeBuilder();
    $rootNode = $treeBuilder->root('app');
    $rootNode->children()->scalarNode('comments')->end();
    return $treeBuilder;
    }
    }



    Now I can send the value of comments from controller to twig.

    I still do have some questions just so that my concept is clear.

    1) Can we not set the value of parameter comments in extension or configuration class? Please note that comment is just a dummy name I am using as an example.

    2) If the above is true then must we add the app.comment in config.yml? or it is important that we add it in config.yml as this is the only way for controller to access it and pass the value to twig?

    3) Is there anyway to access comment value directly in twig rather than passing the value to twig via controller?

  • 2015-08-30 Shairyar Baig

    Hi,

    I am just playing around the default symfony install which comes with `AppBundle` and I am trying to set parameters using the `AppExtension` class that is inside the `DependencyInjection` folder. Reading the docs online it says that the load() method should help me do that and following is how my load method looks like

    I am trying set the parameter `comments`




    class AppExtension extends Extension
    {
    public function load(array $configs, ContainerBuilder $container)
    {

    $loader = new Loader\xmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
    $loader->load('services.xml');

    $configuration = new Configuration();
    $config = $this->processConfiguration($configuration, $configs);
    $config['comments'] = "some value";
    $container->setParameter('app.comments', $config['comments']);

    }

    public function getAlias(){
    return 'app';

    }
    }


    I am not getting any error when i run and I am not sure how can i access the parameter in my twig template that i defined above, is this the right way to set the parameter? if yes then how can i access it via twig