Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Explore! Environments & Config Files

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

Not unlike our space-traveling users, we are also pretty adventurous. Sure, they might be discovering intelligent life on other planets or exploring binary planets inside the habitable zone. But we! We are going to explore the config/ directory and learn all of its secrets. Seriously, this is cool stuff!

Environment?

We know that Symfony is really just a set of routes and a set of services. And we also know that the files in config/packages configure those services. But, who loads these files? And what is the importance - if any - of these sub-directories?

Well, put on your exploring pants, because we're going on a journey!

The code that runs our app is like a machine: it shows articles and will eventually allow people to login, comment and more. The machine always does the same work, but... it needs some configuration in order to do its job. Like, where to write log files or what the database name and password are.

And there's other config too, like whether to log all messages or just errors, or whether to show a big beautiful exception page - which is great for development - or something aimed at your end-users. Yep, the behavior of your app can change based on its config.

Symfony has an awesome way of handling this called environments. It has two environments out-of-the-box: dev and prod. In the dev environment, Symfony uses a set of config that's... well... great for development: big errors, log everything and show me the web debug toolbar. The prod environment uses a set of config that's optimized for speed, only logs errors, and hides technical info on error pages.

How Environments Work

Ok, I know what you're thinking: this makes sense from a high level... but how does it work? Show me the code!

Open the public/ directory and then index.php:

40 lines public/index.php
... lines 1 - 2
use App\Kernel;
use Symfony\Component\Debug\Debug;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\HttpFoundation\Request;
require __DIR__.'/../vendor/autoload.php';
// The check is to ensure we don't use .env in production
if (!isset($_SERVER['APP_ENV'])) {
if (!class_exists(Dotenv::class)) {
throw new \RuntimeException('APP_ENV environment variable is not defined. You need to define environment variables for configuration or add "symfony/dotenv" as a Composer dependency to load variables from a .env file.');
}
(new Dotenv())->load(__DIR__.'/../.env');
}
$env = $_SERVER['APP_ENV'] ?? 'dev';
$debug = $_SERVER['APP_DEBUG'] ?? ('prod' !== $env);
if ($debug) {
umask(0000);
Debug::enable();
}
if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) {
Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
}
if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) {
Request::setTrustedHosts(explode(',', $trustedHosts));
}
$kernel = new Kernel($env, $debug);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

This is the front controller: a fancy word to mean that it is the first file that's executed for every page. You don't normally worry about it, but... it's kind of interesting.

It's looking for an environment variable called APP_ENV:

40 lines public/index.php
... lines 1 - 9
// The check is to ensure we don't use .env in production
if (!isset($_SERVER['APP_ENV'])) {
... lines 12 - 15
}
... lines 17 - 40

Tip

If you start a new project today, you won't see this APP_ENV logic. It's been moved to a config/bootstrap.php file.

We're going to talk more about environment variables later, but they're just a way to store config values. One confusing thing is that environment variables are a totally different thing than what we're talking about right now: Symfony environments.

Forget how the $env variable is set for a moment, and go down to see how it's used:

40 lines public/index.php
... lines 1 - 17
$env = $_SERVER['APP_ENV'] ?? 'dev';
... lines 19 - 34
$kernel = new Kernel($env, $debug);
... lines 36 - 40

Ah! It's passed into some Kernel class! The APP_ENV variable is set in a .env file, and right now it's set to dev. Again, more on environment variables later.

Anyways, the string dev - is being passed into a Kernel class. The question is... what does that do?

Debugging the Kernel Class

Well... good news! That Kernel class is not some core part of Symfony. Nope, it lives right inside our app! Open src/Kernel.php:

62 lines src/Kernel.php
... lines 1 - 2
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
const CONFIG_EXTS = '.{php,xml,yaml,yml}';
public function getCacheDir()
{
... line 19
}
public function getLogDir()
{
... line 24
}
public function registerBundles()
{
... lines 29 - 34
}
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
... lines 39 - 47
}
protected function configureRoutes(RouteCollectionBuilder $routes)
{
... lines 52 - 59
}
}

After some configuration, there are three methods I want to look at. The first is registerBundles():

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 26
public function registerBundles()
{
$contents = require $this->getProjectDir().'/config/bundles.php';
foreach ($contents as $class => $envs) {
if (isset($envs['all']) || isset($envs[$this->environment])) {
yield new $class();
}
}
}
... lines 36 - 60
}

This is what loads the config/bundles.php file:

... lines 1 - 2
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
Knp\Bundle\MarkdownBundle\KnpMarkdownBundle::class => ['all' => true],
];

And check this out: some of the bundles are only loaded in specific environments. Like, the WebServerBundle is only loaded in the dev environment:

... lines 1 - 2
return [
... line 4
Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
... lines 6 - 11
];

And the DebugBundle is similar. Most are loaded in all environments.

The code in Kernel handles this: you can pretty easily guess that $this->environment is set to the environment, so, dev!

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 26
public function registerBundles()
{
$contents = require $this->getProjectDir().'/config/bundles.php';
foreach ($contents as $class => $envs) {
if (isset($envs['all']) || isset($envs[$this->environment])) {
... line 32
}
}
}
... lines 36 - 60
}

The other two important methods are configureContainer()... which basically means "configure services"... and configureRoutes():

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 36
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
$container->setParameter('container.autowiring.strict_mode', true);
$container->setParameter('container.dumper.inline_class_loader', true);
$confDir = $this->getProjectDir().'/config';
$loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
if (is_dir($confDir.'/packages/'.$this->environment)) {
$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');
}
protected function configureRoutes(RouteCollectionBuilder $routes)
{
$confDir = $this->getProjectDir().'/config';
if (is_dir($confDir.'/routes/')) {
$routes->import($confDir.'/routes/*'.self::CONFIG_EXTS, '/', 'glob');
}
if (is_dir($confDir.'/routes/'.$this->environment)) {
$routes->import($confDir.'/routes/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
}
$routes->import($confDir.'/routes'.self::CONFIG_EXTS, '/', 'glob');
}
}

Of course! Because - say it with me now:

Symfony is just a set of services and routes.

Ok, I'll stop jamming that point down your throat.

Package File Loading

Look at configureContainer() first:

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 36
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
$container->setParameter('container.autowiring.strict_mode', true);
$container->setParameter('container.dumper.inline_class_loader', true);
$confDir = $this->getProjectDir().'/config';
$loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
if (is_dir($confDir.'/packages/'.$this->environment)) {
$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 49 - 60
}

When Symfony boots, it needs config: it needs to know where to log or how to connect to the database. To get all of the config, it calls this one method. You can ignore these first two lines: they're internal optimizations.

After, it's uses some sort of $loader to load configuration files:

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 36
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
... lines 39 - 41
$loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
if (is_dir($confDir.'/packages/'.$this->environment)) {
$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 49 - 60
}

This CONFIG_EXTS constant is just a fancy way to load any PHP, XML or YAML files:

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 14
const CONFIG_EXTS = '.{php,xml,yaml,yml}';
... lines 16 - 60
}

First, it loads any files that live directly in packages/:

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 36
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
... lines 39 - 41
$loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
... lines 43 - 47
}
... lines 49 - 60
}

But then, it looks to see if there is an environment-specific sub-directory, like packages/dev. And if there is, it loads all of those files:

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 36
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
... lines 39 - 42
if (is_dir($confDir.'/packages/'.$this->environment)) {
$loader->load($confDir.'/packages/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
}
... lines 46 - 47
}
... lines 49 - 60
}

Right now, in the dev environment, it will load 5 additional files. The order of how this happens is the key: any overlapping config in the environment-specific files override those from the main files in packages/.

For example, open the main routing.yaml. This is not very important, but it sets some strict_requirements flag to ~... which is null in YAML:

framework:
router:
strict_requirements: ~

But then in the dev environment, that's overridden: strict_requirements is set to true:

framework:
router:
strict_requirements: true

To prove it, find your terminal and run:

./bin/console debug:config framework

Since we're in the dev environment right now... yep! The strict_requirements value is true!

This also highlights something we talked about earlier: the names of the files are not important... at all. This could be called hal9000.yaml and not change a thing. The important part is the root key, which tells Symfony which bundle is being configured.

Usually, the filename matches the root key... ya know for sanity. But, it doesn't have to. The organization of these files is subjective: it's meant to make as much sense as possible. The routing.yaml file actually configures something under the framework key.

My big point is this: all of these files are really part of the same configuration system and, technically, their contents could be copied into one giant file called my_big_old_config_file.yaml.

Oh and I said earlier that Symfony comes with only two environments: dev and prod. Well... I lied: there is also a test environment used for automated testing. And... you can create more!

Go back to Kernel.php. The last file that's loaded is services.yaml:

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 36
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
{
... lines 39 - 45
$loader->load($confDir.'/services'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/services_'.$this->environment.self::CONFIG_EXTS, 'glob');
}
... lines 49 - 60
}

More on that file later. It can also have an environment-specific version, like services_test.yaml.

Route Loading

And the configureRoutes() method is pretty much the same: it automatically loads everything from the config/routes directory and then looks for an environment-specific subdirectory:

62 lines src/Kernel.php
... lines 1 - 10
class Kernel extends BaseKernel
{
... lines 13 - 49
protected function configureRoutes(RouteCollectionBuilder $routes)
{
$confDir = $this->getProjectDir().'/config';
if (is_dir($confDir.'/routes/')) {
$routes->import($confDir.'/routes/*'.self::CONFIG_EXTS, '/', 'glob');
}
if (is_dir($confDir.'/routes/'.$this->environment)) {
$routes->import($confDir.'/routes/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
}
$routes->import($confDir.'/routes'.self::CONFIG_EXTS, '/', 'glob');
}
}

So.. yea! All of the files inside config/ either configure services or configure routes. No biggie.

But now, with our new-found knowledge, let's tweak the cache service to behave differently in the dev environment. And, let's learn how to change to the prod environment.

Leave a comment!

8
Login or Register to join the conversation
Helmi Avatar
Helmi Avatar Helmi | posted 4 years ago | edited

I think that it's more intuitive if the boot function was outside the handle function
<br />$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);<br />$kernel->boot();<br />$request = Request::createFromGlobals();<br />$response = $kernel->handle($request);<br />$response->send();<br />$kernel->terminate($request, $response);<br />

So the handle request function only captures the request

Reply

Hey Helmi

I think it's fine the way it is because I don't think it should be the user responsibility to boot up the kernel, unless you have a valid use-case for it

Cheers!

1 Reply
mehdi Avatar
mehdi Avatar mehdi | posted 4 years ago | edited

Hello,

In the <b>routing loading paragraph </b>you said : <i>And the configureRoutes() method is pretty much the same: it automatically loads everything from the config/routes directory and then looks for an environment-specific subdirectory:</i>.

In symfony 4.3 this is the <i>configureRoutes</i> function, if you read it , I think they change the order of loading routing files, environment-specific subdirectory first, then it loads config/routes directory

`
protected function configureRoutes(RouteCollectionBuilder $routes): void
{
$confDir = $this->getProjectDir().'/config';

$routes->import($confDir.'/{routes}/'.$this->environment.'/**/'.self::CONFIG_EXTS, '/', 'glob'); $routes->import($confDir.'/{routes}/'.self::CONFIG_EXTS, '/', 'glob');
$routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
}
`

Reply

Hey mehdi

Woh, nice catch! You made dug deep. I found the commit when Symfony changed that line: https://github.com/symfony/...
The reason is at the top of the page

Cheers!

Reply
Mike P. Avatar
Mike P. Avatar Mike P. | posted 4 years ago

November 2018 Update:
A) The .env.dist file no longer exists. Its contents should be moved to your .env file (see the next point).

Source: https://symfony.com/doc/cur...

Reply

Hey Mike P.!

You're totally right! And we've added some notes to the video about that to help make sure people are aware. Was there a spot in this video where we talked about .env.dist that you found confusing because we didn't make this Nov 2018 updates obvious? Let us know - we'll definitely add any other notes we need to help people.

Cheers!

Reply

For a personal application I'm coding up on Symfony 4, I added this application specific parameters under config/services.yml https://gist.github.com/elv...

Now my question is, how is the best way/practice for my code under /src (Controllers, Services, Helpers, etc) to get this parameters values?

Thanks,

Reply

Hey Elvism,

Well, there're a few possible ways but it depends. For example, if we're talking about controllers - you can extend Symfony base controller and then you'll have "$this->getParameter()" shortcut, or, if you have an access to the DI container - call it via "$this->container->getParameter()". Fo example, if you want to get a value of "app.pool_acct" parameters, get it with:
$this->getParameter('app.pool_acct').

If we're talking about services, you need to pass parameters explicitly on service definition of as an argument like:
App\Service\MyService:
arguments:
- '%app.pool_acct%'

Where "%...%" will be replaced with the parameter value. Or even cooler, look for new "bind" feature here: https://knpuniversity.com/s...

Cheers!

1 Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0" // v4.0.14
    },
    "require-dev": {
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.0.2
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}
userVoice