If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeLet'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:
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:
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!
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__
:
... 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%
:
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:
... 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
.
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:
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.
Hi jverdeyen!
Wow! Congratulations - that's a pretty serious accomplishment :).
So, here's what I can say:
A) You should compile the container, and then actually require the cached container before anything else. The core Kernel class is a good guide: https://github.com/symfony/.... This means that you won't actually use the `$container` variable in your code as your container - that code will only run when there is *no* cached container. And then you will actually require the cached container.
B) Once you're using the cached container, you'll want to set the request_stack onto *that* container. I bet the problem is that you're setting the request_stack on your cached container, and then later on (in some code you don't have printed here), you're requiring the cached container. With that setup, the cached container indeed won't have the request_stack.
Let me know if that helps! I think you're one minor step away - it looks awesome!
Yes, it's working! Thanks! I didn't know I could set a service on a cached and compiled container. Thanks for the core Kernel example! I'm learning the Symfony internals by building it with the components, from scratch. Thanks Ryan
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
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?
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?
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.
// 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
}
}
I successfully migrated a legacy application into lots of the Symfony (3.1) components (events/di/forms/twig/..). Compiling the container is working fine, although I can't get the cached container to work properly. I'm building the container from yaml files and inject the request. But when using the dumped class, the request_stack and request can't be found. How does a "normal" Symfony application injects the request? I'm only seeing the AppKernel pass the request into the HttpKernel.
Thanks! I love these screencasts!