Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Parameters

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

We know there are lots of useful service objects floating around that, internally, Symfony keeps inside something called a container. But this container thing can also hold something other than services: it can hold scalar configuration called parameters. You can use these to do some cool stuff.

debug:container --parameters

Earlier, we learned that you can get a list of every service in the container by running debug:container.

php bin/console debug:container

Big giant list. To get a list of the "parameters" in the container, add a --parameters flag:

php bin/console debug:container --parameters

There are a bunch of them. But most of these aren't very important - they're values used internally by low-level services. For example, one parameter is called kernel.charset, which is set to UTF-8. That's probably used in various places internally.

Adding Parameters

The point is: the container can also hold scalar config values and it's sometimes useful to add your own. So, how could we do that?

Go into config/packages/ and open any config file. Let's open cache.yaml because we're going to use parameters for a caching trick. Add a key called parameters:

parameters:
... lines 2 - 23

We know that the framework key means that the config below it will be passed to FrameworkBundle. The parameters key is special: it means that we're adding parameters to the container. Invent a new one called, how about, cache_adapter set to cache.adapter.apcu:

parameters:
cache_adapter: cache.adapter.apcu
... lines 3 - 23

There should now be a new parameter in the container called cache_adapter. We're not using it anywhere... but it should exist.

Reading a Parameter in a Controller

How do we use it? There are two ways. First, you could read it in a controller. Open src/Controller/QuestionController.php and find the show() method. Inside, use a new shortcut method dump($this->getParameter('cache_adapter')):

... lines 1 - 10
class QuestionController extends AbstractController
{
... lines 13 - 30
public function show($slug, MarkdownHelper $markdownHelper)
{
dump($this->getParameter('cache_adapter'));
... lines 34 - 48
}
}

If we move over and refresh the show page... there it is! The string cache.adapter.apcu.

Reading Parameters in Config Files

But this is not the most common way to use parameters. The most common way is to reference them in config files. Once a parameter exists, you can use that parameter in any config file via a special syntax. Down below in cache.yaml, remove the cache.adapter.apcu string and replace it with quotes, then %cache_adapter%:

parameters:
cache_adapter: cache.adapter.apcu
framework:
cache:
... lines 6 - 16
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
app: '%cache_adapter%'
... lines 19 - 23

Let's try it! Move over and refresh. Yes! Things are still working.

The key is that when you surround something by % signs, Symfony realizes that you are referencing a parameter. These parameters sort of work like variables inside of config files. Oh, and quotes are normally optional in YAML, but they are needed when a string starts with %. If you're ever not sure if quotes are needed around something, just play it safe and add them.

Overriding a Parameter in Dev

But so far... this isn't that interesting: we're basically setting a variable up here and using it below... which is fine, but not that useful yet.

However, we can use parameters to do our "cache adapter override" in a smarter way. Remember, in dev/cache.yaml, we're overriding the framework.cache.app key to be cache.adapter.filesystem:

framework:
cache:
app: cache.adapter.filesystem

Now, instead of overriding that config, we can override the parameter.

Add parameters: and then use the same name as the other file: cache_adapter: set to cache.adapter.filesystem:

parameters:
cache_adapter: cache.adapter.filesystem

Ok, in the main cache.yaml, we're setting the app key to the cache_adapter parameter. This is initially set to apcu, but we override it in the dev environment to be filesystem. This works because the last value wins: Symfony doesn't resolve the cache_adapter parameter until all the config files have been loaded.

We can see this in the terminal. Run:

php bin/console debug:container --parameters

And... yes! The value is cache.adapter.filesystem. How would this parameter look in the prod environment? We could change the environment in the .env file and re-run this command. Or, we can use a trick: run the command with --env=prod. That flag works for any command:

php bin/console debug:container --parameters --env=prod

This time, it's cache.adapter.apcu. Oh, and I didn't clear my cache before running this, but you really should do that before doing anything in the prod environment.

Parameters Usually Live in services.yaml

So... those are parameters! Simple config variables to help you kick butt.

But I do want to change one thing. By convention, files in the config/packages directory hold bundle configuration - like for FrameworkBundle or TwigBundle. For services and parameters that we want to add to the container directly, there's a different file: config/services.yaml.

Copy the parameter from cache.yaml:

parameters:
cache_adapter: cache.adapter.apcu
... lines 3 - 23

Remove it, and paste it here:

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# 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 8 - 29

Now, technically, this makes no difference: Symfony loads the files in config/packages and services.yaml at the same time: any config can go in any file. But defining all of your parameters in one spot is nice.

Creating services_dev.yaml

Of course, you might now be wondering: what about the parameter in config/packages/dev/cache.yaml? Remember: the class that loads these files is src/Kernel.php:

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 32
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
... lines 35 - 39
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
}
... lines 45 - 55

It loads all the files in packages/, packages/{environment} and then services.yaml. Oh, but there is one more line: it also tries to load a services_{environment}.yaml file. If you need to override a parameter or service - more on that soon - in the dev environment, this is the key.

Create that file: services_dev.yaml. Then copy the config from dev/cache.yaml and paste it here:

parameters:
cache_adapter: cache.adapter.filesystem

We can now completely delete the old cache.yaml file.

That's it! We set the cache_adapter parameter in services.yaml, override it in services_dev.yaml and reference the final value in cache.yaml. We rule.

Next, let's leverage a core parameter to disable our markdown caching in the dev environment. The trick is: how can we access configuration from inside our MarkdownHelper service?

Leave a comment!

3
Login or Register to join the conversation
Mathew Avatar
Mathew Avatar Mathew | posted 1 year ago | edited

Ryan and or amazing Symfony Cast team... I got a tricky one for you regarding parameters,
I recently upgraded our organizations symfony 3.4 app to symfony 5.4.6 Flex.
I have gotten all of the main functions of the application working, although there is one thing that I have been putting off, as its just escaping me on how to do it.
Multi Tenant Parameters
Our old 3.4 Symfony application would be able to intercept the $_SERVER['HTTP_HOST'] and use the first part of the url to determine what set of parameters to load. for example

mathew.example.com would load config/mathew/paramters.yaml
ryan.example.com would load config/ryan/parameters.yaml

Each config file would have organization specific parameters such as DB info, company name, etc.
That way we could have one application, two organizations using it, separated database information for ultimate privacy.

I think it was about the symfony 5.1 mile marker where i noticed that the kernel file changed to remove a lot of that granular control of which config files you could load.

I have modified the V5.4.6 Kernel to work, but for some reason, its not working 100 of the time.
Here is my code


class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    protected function build(ContainerBuilder $container)
    {
        $loader = $this->getContainerLoader($container);
        $configFolder = 'default';
        
        if (isset($_SERVER['HTTP_HOST'])) {
            $host = explode('.', $_SERVER['HTTP_HOST']);
            if ($host[0] === 'mathew' or $host[0] === 'mathew-staging')
                $configFolder = 'mathew';
            else
                $configFolder = $host[0]; *ryan*
        }

        $loader->load($container->getParameter('kernel.project_dir') . '/multi-tenant/'. $configFolder . '/config.yaml');
        parent::build($container);
    }
}

So this totally works. Say I got to mathew.example.com, it loads all the mathew parameters. But if I then open up an incognito tab and go to ryan.example.com it loads mathews parameters and vice versa. It keeps this behavior till I clear the cache.
Some of the other things I have attempted to try and resolve this issue are:

<blockquote> * disable app / system caching

  • disable Twig caching as those are the easiest identifiers if the right parameters loaded.
    </blockquote>

Thank you for any advise you can give.

Reply

Hey @Mathew!

Sorry for my late reply. I believe your problem is due to Symfony's internal caching, the very first time you hit your application, it'll build/compile the Symfony's container and then, it'll be cached (by creating many files inside "var/cache/{env}/Container____/"), so, the next time the application runs, it won't compile the container again, so, your strategy of dynamically choosing which config files to read ain't going to work.

I'm not sure about this, but I think you can override the Kernel::getCacheDir() method, so, it would return a different directory based on the host. Give it a try and let know if it wordked ;)

Cheers!

Reply
Mathew Avatar

Before anyone comments about keeping sensitive information such as DB connection info in a config file. I am pulling the data into the config files using AWS:Secrets Manager, so the code itself only shows a reference to the Secrets ID and Key name on AWS, not the actual credentials. :)

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial also works great for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "sentry/sentry-symfony": "^4.0", // 4.0.3
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.15", // v1.23.0
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}
userVoice