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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOur 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 | |
{ | |
'/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:
# 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 | |
###< symfony/framework-bundle ### |
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
:
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.
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.