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 SubscribeWe 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.
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.
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.
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
.
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.
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.
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.
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
:
... 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?
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!
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. :)
// 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
}
}
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
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
</blockquote>
Thank you for any advise you can give.