Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Environments

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 $12.00

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

Login Subscribe

Our application is like a machine: it's a set of services and PHP classes that do work... and ultimately render some pages. But we can make our machine work differently by feeding it different configuration.

For example, in SongController, we're using the $logger service to log some information:

... lines 1 - 10
class SongController extends AbstractController
{
#[Route('/api/songs/{id<\d+>}', methods: ['GET'], name: 'api_songs_get_one')]
public function getSong(int $id, LoggerInterface $logger): Response
{
... lines 16 - 27
}
}

If we feed the logger some configuration that says "log everything", it will log everything, including low level debug messages. But if we change the config to say "only log errors", then this will only log errors. In other words, the same machine can behave differently based on our configuration. And sometimes, like with logging, we might need that configuration to be different while we're developing locally versus on production.

To handle this, Symfony has an important concept called "environments". I don't mean environments like local vs staging vs beta vs production. A Symfony environment is a set of configuration.

For example, you can run your code in the dev environment with a set of config that's designed for development. Or you can run your app in the prod environment with a set of config that's optimized for production. Let me show you!

The APP_ENV Variable

In the root of our project, we have a .env file:

20 lines .env
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=4777a99cd6c61ce84969bd1338737c38
###

We're going to talk more about this file later. But see this APP_ENV=dev? This tells Symfony that the current environment is dev, which is perfect for local development. When we deploy to production, we'll change this to prod. More on that in a few minutes.

But... what difference does that make? What happens in our app when we change this from dev to prod? To answer, let me close some folders... and open public/index.php:

10 lines public/index.php
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

Remember: this is our front controller. It's the first file that's executed on every request. We don't really care much about this file, but its job is important: it boots up Symfony.

What's interesting is that it reads the APP_ENV value and passes it as the first argument to this Kernel class. And... this Kernel class is actually in our code! It lives at src/Kernel.php.

Cool. So what I want to know now is: What does the first argument to Kernel control?

If we open the class we find... absolutely nothing. It's empty. That's because the majority of the logic lives in this trait. Hold "cmd" or "control" and click MicroKernelTrait to open that up.

The config/packages/{ENV} Directory

The job of the Kernel is to load all of the services and routes in our app. If you scroll down, it has a method called configureContainer(). Ooh! We know what the container is now! And check out what it does! It takes this $container object and imports $configDir.'/{packages}/*.{php,yaml}'. This line says:

Yo container! I want to load all of the files from the config/packages/ directory.

It loads all of those files, and then it passes the configuration from each to whatever bundle is defined as the root key. But what's really interesting for environments is this next line: import $configDir.'/{packages}/'.$this->environment.'/*.{php,yaml}'. If you dug a little, you'd learn that $this->environment is equal to the first argument that's passed to Kernel!

In other words, in the dev environment, this will be dev. So, in addition to the main config files, this will also load anything in the config/packages/dev/ directory. Yup, we can add extra config there that overrides the main configuration in the dev environment. For example, we could add logging config that tells the logger to log everything!

Below this, we also load a file called services.yaml and, if we have it, services_dev.yaml. We'll talk more about services.yaml real soon.

The when@{ENV} Config

So, if you want to add environment-specific configuration, you can put it in the correct environment directory. But there's one other way. It's a pretty new feature and we saw it at the bottom of twig.yaml. It's the when@ syntax:

... lines 1 - 3
when@test:
twig:
strict_variables: true

In Symfony, by default, there are three environments: dev, prod, and then if you run automated tests, there's an environment called test. Inside of twig.yaml, by saying, when@test, it means that this configuration will only be loaded if the environment is equal to test.

The best example of this might be in monolog.yaml. monolog is the bundle that controls the logger service. It does have some configuration that's used in all environments. But, below this, it has when@dev. We won't talk too much about the specific monolog configuration, but this controls how log messages are handled. In the dev environment, this says that it should log everything and it should log to a file, using this fancy %kernel.logs_dir% syntax that we'll learn about soon.

Anyways, this points to a var/logs/dev.log file and the level: debug part means that it will log every single message to dev.log... regardless of how important or unimportant that message is.

Below this, for the prod environment, it's quite different. The most important line is action_level: error. That says:

Hi Ms Logger! This app probably logs a ton of messages, but I only want you to actually save messages that are an error importance level or higher.

That makes sense! In production, we don't want our log files filling up with tons and tons of debug messages. With this, we only log error messages.

The big point is this: by using these tricks, we can configure our services differently based on the environment.

Environment-Specific Routing

And, we can even do the same thing with routes! Sometimes you have entire routes that you only want to load in a certain environment. Back in MicroKernelTrait, if you go down, there's a method called configureRoutes(). This is what's responsible for loading all of our routes... and it's very similar to the other code. It loads $configDir.'/{routes}/*.{php,yaml}' as well as this dev environment directory, if you have one. We don't.

You can also use the when@dev trick. This file is responsible for registering the routes used by the web debug toolbar. We don't want the web debug toolbar in production... so these routes are only imported in the dev environment.

when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

Heck, certain bundles are only enabled in some environments! If you open config/bundles.php, we have the name of the bundle... and then on the right, the environments in which that bundle should be enabled. This all means all environments.... and most are enabled in all environments.

The WebProfilerBundle however - the bundle that gives us the web debug toolbar and profiler - is only loaded in the dev and test environments. Yup, the entire bundle - and the services it provides - are never loaded in the prod environment.

So, now that we understand the basics of environments, let's see if we can switch our application to the prod environment. And then, as a challenge, we'll configure our cache service differently in dev. That's next.

Leave a comment!

7
Login or Register to join the conversation
Akili Avatar
Akili Avatar Akili | posted 24 days ago | edited

Hey SymfonyCasts and Ryan!

great work! I'm learning a lot about Symfony 6 under the hood. I will purchase the rest of the Symfony 6 courses that are available. there was one other thing I wanted to ask. For someone building an application for production for the first time (using the Symfony 6 framework) without a team or help from anyone. How would I know that I have researched and gathered all the accurate materials and docs needed to build a fully functional application that is production ready? Does SymfonyCast have an area that teaches this? Or is there a checklist that I need to be aware of? What steps should I be taking that would lead me in the right direction? I understand that reading the Symfony docs is very important, with that said, I'm sure there are important steps that I'm unaware of.

Reply
Victor Avatar Victor | SFCASTS | Akili | posted 22 days ago | edited

Hey Akili,

Well, first of all, writing some tests is always a good idea :) Tests will help you to make sure that application works as expected, e.g. that regular users does not have access to your admin page, or that the checkout process works well and users can buy products on your website, etc. Writing unit/integration/function tests is an important part of any application creation and will not recommend skipping them :) Of course what test and how to test is up to you, and it depends on your needs.

Second, always keep your applications up to date, i.e. regularly update dependencies if you can... it will help to get rid of some known security issues that were recently found and fixed. And that's where your tests will really help you - you will be able to test the whole system and make sure everything still works as expected by running those tests. But it also depends on how well-written tests you have :)

But as soon as your app is on production - you will need to check that the app is loaded in "prod" mode :) You can also check this docs page: https://symfony.com/doc/current/setup/web_server_configuration.html - to set up your production server well and do some Symfony-specific optimization. For more performance optimizations see: https://symfony.com/doc/current/performance.html .

But basically, as soon your app is on prod and you see it's working in prod env mode, and works as expected - everything should be good. Other things depend on the quality of your code, how well you write it, etc.

p.s. in case you're interested in how to deploy your app to the production properly - I will suggest watching our "Deploy with Ansistrano" course: https://symfonycasts.com/screencast/ansistrano :)

Cheers!

Reply

Hola!
Los subtítulos en Español no funcionan en este video.

Reply

Gracias por avisarnos, los subtitulos en español para este video ya están disponibles. Saludos!

Reply

Ah, gracias! Estamos investigando lo que está pasando.

Saludos!

Reply

Why is the course still not free? I recently received an email explaining that it was already released.

Reply

Hey danielperez9403

I answered you here https://symfonycasts.com/sc...

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "knplabs/knp-time-bundle": "^1.18", // v1.19.0
        "symfony/asset": "6.1.*", // v6.1.0-RC1
        "symfony/console": "6.1.*", // v6.1.0-RC1
        "symfony/dotenv": "6.1.*", // v6.1.0-RC1
        "symfony/flex": "^2", // v2.1.8
        "symfony/framework-bundle": "6.1.*", // v6.1.0-RC1
        "symfony/http-client": "6.1.*", // v6.1.0-RC1
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/runtime": "6.1.*", // v6.1.0-RC1
        "symfony/twig-bundle": "6.1.*", // v6.1.0-RC1
        "symfony/ux-turbo": "^2.0", // v2.1.1
        "symfony/webpack-encore-bundle": "^1.13", // v1.14.1
        "symfony/yaml": "6.1.*", // v6.1.0-RC1
        "twig/extra-bundle": "^2.12|^3.0", // v3.4.0
        "twig/twig": "^2.12|^3.0" // v3.4.0
    },
    "require-dev": {
        "symfony/debug-bundle": "6.1.*", // v6.1.0-RC1
        "symfony/maker-bundle": "^1.41", // v1.42.0
        "symfony/stopwatch": "6.1.*", // v6.1.0-RC1
        "symfony/web-profiler-bundle": "6.1.*" // v6.1.0-RC1
    }
}
userVoice