Buy

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

Login Subscribe

The container is a fancy, scary word for a simple concept: the object that holds all of the services in our app. But actually, the container can also hold a second type of thing: normal boring config values! These are called parameters and, it turns out they're pretty handy!

Open config/packages/framework.yaml. We configured the cache system to use this cache.adapter.apcu service:

31 lines config/packages/framework.yaml
framework:
... lines 2 - 16
cache:
... lines 18 - 28
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
app: cache.adapter.apcu

And then, in the dev environment only, we're overriding that to use cache.adapter.filesystem:

4 lines config/packages/dev/framework.yaml
framework:
cache:
app: cache.adapter.filesystem

Creating a Parameter

Simple enough! But parameters can make this even easier. Check this out: inside any configuration file - because, remember, all of these files are loaded by the same system - you can add a parameters key. And below that, you can invent whatever keys you want.

Let's invent one called cache_adapter. Set its value to cache.adapter.apcu:

34 lines config/packages/framework.yaml
parameters:
cache_adapter: cache.adapter.apcu
... lines 3 - 34

Using a Parameter

This basically creates a variable. And now we can reference this variable in any of these configuration files. How? Remove cache.adapter.apcu and, inside quotes, replace it with %cache_adapter%:

34 lines config/packages/framework.yaml
parameters:
cache_adapter: cache.adapter.apcu
framework:
... lines 5 - 19
cache:
... lines 21 - 31
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
app: '%cache_adapter%'

Yep, whenever you surround a string with percent signs, Symfony will replace this with that parameter's value.

Overriding a Parameter

So yea... parameters are basically config variables. And, what programmer doesn't like variables!?

The cool thing is, now that we have a parameter called cache_adapter, inside of the dev config, we can shorten things. Change the key to parameters and override cache_adapter: cache.adapter.filesystem:

3 lines config/packages/dev/framework.yaml
parameters:
cache_adapter: 'cache.adapter.filesystem'

Oh, and you may have noticed that sometimes I use quotes in YAML and sometimes I don't. Yay consistency! YAML is super friendly... and so most of the time, quotes aren't needed. But sometimes, like when a value starts with % or contains @, you do need them. Sheesh! Don't worry too much: if you're not sure, use quotes. You'll get a clear error anyways when you do need them.

Ok, let's see if this works! Open MarkdownHelper and dump($this->cache):

38 lines src/Service/MarkdownHelper.php
... lines 1 - 8
class MarkdownHelper
{
... lines 11 - 21
public function parse(string $source): string
{
... lines 24 - 27
dump($this->cache);die;
... lines 29 - 35
}
}

In your browser, wave hello to this astronaut. Then, refresh! Yes! It is still using the filesystem adapter, since we're in the dev environment.

Moving Parameters to services.yaml

Now that we know that any config file can define parameters... let's stop putting them everywhere! I mean, usually, for organization, we like to only define parameters in one-ish files: services.yaml. Let's remove the parameter from the main framework.yaml and add it there:

33 lines config/services.yaml
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
cache_adapter: cache.adapter.apcu
... lines 5 - 33

But... we have a problem. When you refresh now, woh! We're suddenly using the APCU adapter, even though we're in the dev environment! Whaaaat?

Remember the order that these files are loaded: files in config/packages are loaded first, then anything in config/packages/dev, and last, services.yaml. That means that the config in services.yaml is overriding our dev config file!

Boo! How can we fix that? Create a new config file called services_dev.yaml. This is the built-in way to create an environment-specific services file. And you can see that we actually started with one for the test environment. Inside, copy the code from the dev framework.yaml and paste it here:

3 lines config/services_dev.yaml
parameters:
cache_adapter: 'cache.adapter.filesystem'

Oh, and delete the old framework.yaml file. Now, refresh!

Woo! It works!

And that's really it! In framework.yaml, we just reference the parameter...

31 lines config/packages/framework.yaml
framework:
... lines 2 - 16
cache:
... lines 18 - 28
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
app: '%cache_adapter%'

Which can be set in any other file. Like in this case: we set it in services.yaml and override it in services_dev.yaml:

33 lines config/services.yaml
... lines 1 - 2
parameters:
cache_adapter: cache.adapter.apcu
... lines 5 - 33

3 lines config/services_dev.yaml
parameters:
cache_adapter: 'cache.adapter.filesystem'

Actually, if you think about it, since framework.yaml is loaded first, the parameter isn't even defined at this point. But that's ok: you can reference a parameter, even if it's not set until later. Nice!

Using a Parameter in a Service

But wait, there's more! We can also use parameters inside our code - like in MarkdownParser. Suppose that we want to completely disable caching when we're in the dev environment.

How can we do that? Add a new argument called $isDebug:

44 lines src/Service/MarkdownHelper.php
... lines 1 - 8
class MarkdownHelper
{
... lines 11 - 15
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $markdownLogger, bool $isDebug)
{
... lines 18 - 21
}
... lines 23 - 42
}

Yep, in addition to other services, if your service has any config - like isDebug or an API key - those should also be passed as constructor arguments.

The idea is that we will configure Symfony to pass true or false based on our environment. I'll press Alt+Enter and select "Initialize fields" so that PhpStorm creates and sets that property for me:

44 lines src/Service/MarkdownHelper.php
... lines 1 - 8
class MarkdownHelper
{
... lines 11 - 13
private $isDebug;
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $markdownLogger, bool $isDebug)
{
... lines 18 - 20
$this->isDebug = $isDebug;
}
... lines 23 - 42
}

Below, we can say: if $this->isDebug, then just return the uncached value:

44 lines src/Service/MarkdownHelper.php
... lines 1 - 8
class MarkdownHelper
{
... lines 11 - 13
private $isDebug;
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $markdownLogger, bool $isDebug)
{
... lines 18 - 20
$this->isDebug = $isDebug;
}
public function parse(string $source): string
{
... lines 26 - 29
// skip caching entirely in debug
if ($this->isDebug) {
return $this->markdown->transform($source);
}
... lines 34 - 41
}
}

Notice: this is the first time that we've had a constructor argument that is not a service. This is important: Symfony will not be able to autowire this value. Sure, we gave it a bool type-hint, but that's not enough for Symfony to guess what we want. Oh, and reverse my logic - I had it backwards!

44 lines src/Service/MarkdownHelper.php
... lines 1 - 8
class MarkdownHelper
{
... lines 11 - 23
public function parse(string $source): string
{
... lines 26 - 30
if ($this->isDebug) {
... line 32
}
... lines 34 - 41
}
}

To see that the argument cannot be autowired, refresh! Yep! A clear message:

Cannot autowire service MarkdownHelper: argument $isDebug must have a type-hint or be given a value explicitly.

This is the other main situation when autowiring does not work. But... just like before, it's no problem! If Symfony can't figure out what value to pass to an argument, just tell it! In services.yaml, we could configure the argument for just this one service. But that's no fun! Add another global bind instead: $isDebug and just hardcode it to true for now:

34 lines config/services.yaml
... lines 1 - 5
services:
# default configuration for services in *this* file
_defaults:
... lines 9 - 14
# setup special, global autowiring rules
bind:
... line 17
$isDebug: true
... lines 19 - 34

Ok, move over and... refresh! Yea! It works! And if you check out the caching section of the profiler... yes! No calls!

Built-in kernel.* Parameters

To set the $isDebug argument to the correct value, we could create a parameter, set it to false in services.yaml, override it in services_dev.yaml, and use it under bind.

But don't do it! Symfony already has a parameter we can use! In your terminal, the debug:container command normally lists services. But if you pass --parameters, well, you can guess what it prints:

php bin/console debug:container --parameters

Just like with services, most of these are internal values you don't care about. But, there are several that are useful: they start with kernel., like kernel.debug. That parameter is true most of the time, but is false in the prod environment.

Oh, and kernel.project_dir is also really handy. Copy kernel.debug, move back to services.yaml, and use %kernel.debug%:

34 lines config/services.yaml
... lines 1 - 5
services:
# default configuration for services in *this* file
_defaults:
... lines 9 - 14
# setup special, global autowiring rules
bind:
... line 17
$isDebug: '%kernel.debug%'
... lines 19 - 34

Try it! Refresh! It still works!

Ok, it's time to talk a little bit more about controllers. It turns out, they're services too!

Leave a comment!

  • 2018-03-26 Victor Bocharsky

    Hey Yahya,

    Thanks for more detailed explanation! Haha, maybe too much. Well, I see the value to use one lang for admin panel but ability to fill in content into the DB in different langs, i.e. not changing language of admin panel each time you need to fill in content in a new lang. But what about separating langs for admin/fronted - I don't think it's worth it. If you use a lang on frontend - I think you'd 99% want to use the same lang in admin panel :)

    Anyway, thanks for your ideas!

    Cheers!

  • 2018-03-23 Yahya A. Erturan

    Admin Panel should have languages too :) So we have three language stream - 1: Main Data Stream (Adding pages to database via admin panel in several (if enabled) languages.) - 2: Frontend Stream (Displaying the records for selected languages in frontend) - 3: Backend Stream (Languages of Admin Panel - New Page / Nouvelle Page / Nuova Pagina / новая страница) - I have started to afraid of maybe it is too much real world for a tutorial :)

  • 2018-03-19 Victor Bocharsky

    Hey Yahya,

    About the last, so after you filled in the EN information to your products, you're just looking for a way to fill in the same information but in FR and DE languages at the same time not switching language of the admin panel, right? So probably yeah, A2LixTranslationBundle I something you need, or I think it's also possible to implement manually with KnpLabsDoctrineBehaviors. Anyway, thanks for explanation! We'll consider these topics as well.

    Cheers!

  • 2018-03-16 Yahya A. Erturan

    I'd be really happy to see such a tutorial :)

    > to handle `article_meta` and `article_lang` tables....
    KnpLabs/DoctrineBehaviors, A2Lix TranslationBundle as I saw in a tutorial after digging in Google :)

    > different language streams for frontend and backend
    For example, website's default language is English. Other available languages are German and French. As we are mostly working for international companies, in Admin panel (which is in English), our client is entering products in English. And asking their branch in France to enter details in French via Admin panel (which is still in English). We are handling it in our current system (which is highly modified version of CI 3 - and getting resemble to Symfony more day by day (thanks to your tutorials)) but at one point (after mastering in Doctrine and MultiLingual issues) we want to switch Symfony fully :)

  • 2018-03-16 Victor Bocharsky

    Hey Yahya,

    Thanks for these topics! Well, most of them well be covered for sure. And I think I will reconsider others and try to include them too. Meanwhile, I'm not sure that I completely understand a few of them:
    > to handle `article_meta` and `article_lang` tables (there is a bundle for that but I am eager to learn how it really works),
    Just to be clear, what bundle are you talking about here?

    > different language streams for frontend and backend
    Can you explain a bit what "language streams" do you mean? For example, is it something like setting FR lang for admin panel but EN for the client's part? Because, I have never thought about it. Do you have a good use case where it may be really useful?

    Cheers!

  • 2018-03-16 Yahya A. Erturan

    I am thrilled that you are planning it already :) I am looking for a real life examples:

    * redirect based on browser language or saving preferred language to a cookie. (For homepage of course)
    * Slugs for each language. I think (but IMHO) hiding default language in url and displaying it in other languages is a good practice.
    * to handle `article_meta` and `article_lang` tables (there is a bundle for that but I am eager to learn how it really works),
    * to switch between languages,
    * different language streams for frontend and backend,
    * building sitemaps for each language
    * etc.,

    Sorry you hava asked :)

  • 2018-03-16 weaverryan

    Hey Yahya A. Erturan!

    Yea, I love bind :). We ARE planning a tutorial about everything translations... but I would love to know what you're looking for the in that tutorial, so we can make sure we cover it - it's currently in the planning stages, so the timing is perfect.

    Cheers!

  • 2018-03-15 Yahya A. Erturan

    `bind` is a killing-feature, thank you.

    Meanwhile, are you planning to make a course for MultiLingual usage of Symfony and Doctrine. (not just talking about translations :)) It'd be lovely.

    Thanks for great work.