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!
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 | |
### |
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 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.
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.
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 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!
Gracias por avisarnos, los subtitulos en español para este video ya están disponibles. Saludos!
Why is the course still not free? I recently received an email explaining that it was already released.
// 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
}
}
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.