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 SubscribeWe're kind of abusing the debug flag: it's mostly used to tell Symfony if it should hide/show errors. If you want to control how your app behaves while developing versus on production, that's normally done with environments: the prod
environment will turn on caching and turn off debugging tools. The dev
environment will do the opposite.
When debug
is false, the web debug toolbar is basically off, but the WebProfilerBundle
is still being loaded. That's a bit wasteful: why not only instantiate this in the dev
environment?
Add if $this->getEnvironment() == 'dev'
: let's only load the bundle in that scenario:
// ... lines 1 - 6 | |
class AppKernel extends Kernel | |
{ | |
public function registerBundles() | |
{ | |
// ... lines 11 - 17 | |
if ($this->getEnvironment() == 'dev') { | |
$bundles[] = new \Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); | |
} | |
// ... lines 21 - 22 | |
} | |
// ... lines 24 - 46 | |
} |
Change .env
back to ENV equals dev
and DEBUG equals 1. Refresh now: no issues.
Now change the environment to prod
and refresh. Ah, error:
There is no extension able to load the configuration for web_profiler.
This makes sense: now that the WebProfilerBundle
is not being loaded in the prod
environment, Symfony doesn't know how to handle the web_profiler
section in config.yml
. The full stack Symfony framework solves this by having environment-specific config_dev.yml
and config_prod.yml
files. But I've gotta keep things small. So, I'll show you a trick.
Conditional Configuration with a Trick
Find registerContainerConfiguration()
and add a new $isDevEnv
variable that uses the same getEnvironment()
method from before. Next, use $loader->load()
to load more configuration. But instead of passing it a file, pass it a Closure that accepts a ContainerBuilder
argument. Add the use
statement on top. Also add a use
on the Closure so we have acccess to $isDevEnv
:
// ... lines 1 - 4 | |
use Symfony\Component\DependencyInjection\ContainerBuilder; | |
// ... line 6 | |
class AppKernel extends Kernel | |
{ | |
// ... lines 9 - 24 | |
public function registerContainerConfiguration(LoaderInterface $loader) | |
{ | |
$loader->load(__DIR__.'/config/config.yml'); | |
$isDevEnvironment = $this->getEnvironment() == 'dev'; | |
// do some dynamic customizations | |
$loader->load(function (ContainerBuilder $container) use ($isDevEnvironment) { | |
// ... lines 33 - 44 | |
}); | |
} | |
} |
Now when Symfony loads, it'll load everything from config.yml
, but it'll also call this function, and we can control whatever extra configuration we want. In config.yml
, we can't have the web_profiler
stuff because this file is always loaded. Remove that:
[[ code('7dc1f3ad36') ]]
But inside the Closure, we can say: if we're in the dev
environment, then call $container->loadFromExtension()
to pass in the configuration we want. Use web_profiler
as the first agument and an array as the second with toolbar => true
:
// ... lines 1 - 6 | |
class AppKernel extends Kernel | |
{ | |
// ... lines 9 - 24 | |
public function registerContainerConfiguration(LoaderInterface $loader) | |
{ | |
// ... lines 27 - 28 | |
$isDevEnvironment = $this->getEnvironment() == 'dev'; | |
// ... lines 30 - 31 | |
$loader->load(function (ContainerBuilder $container) use ($isDevEnvironment) { | |
if ($isDevEnvironment) { | |
$container->loadFromExtension('web_profiler', array( | |
'toolbar' => true, | |
)); | |
} | |
// ... lines 38 - 44 | |
}); | |
} | |
} |
That should take care of the first error, so refresh. Closer - that error is gone, but now there's an error loading the wdt.xml
routing file. Again, this makes sense: we're still loading some routes from @WebProfilerBundle
, which simply doesn't exist in the prod
environment:
// ... lines 1 - 4 | |
_wdt: | |
resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml" | |
prefix: /_wdt | |
_profiler: | |
resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml" | |
prefix: /_profiler |
To fix this, create a routing_dev.yml
file, move the two WebProfilerBundle imports there, and at the bottom, import the main routing.yml
file:
# this is "dev-only" debugging routes: | |
# you probably don't want to put anything else in this file | |
_wdt: | |
resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml" | |
prefix: /_wdt | |
_profiler: | |
resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml" | |
prefix: /_profiler | |
_main: | |
resource: routing.yml |
Now, do you remember where we told Symfony to load routing.yml
? It's in config.yml
:
framework: | |
// ... line 2 | |
router: | |
resource: '%kernel.root_dir%/config/routing.yml' | |
// ... lines 5 - 12 |
In all cases, we're loading routing.yml
. But now we need to do something conditional: in dev
, we want to load routing_dev.yml
. Otherwise, we want to load routing.yml
. And again, we can do this in AppKernel
: this is the spot you'll go to if you need to configure something environment-specific.
If we're in the dev
environment, call $container->loadFromextension()
. This time, we need to tweak the framework
configuration to override the router.resource
value. In the array, add a router
key, set it to an array, then add resource
and set it to routing_dev.yml
:
// ... lines 1 - 6 | |
class AppKernel extends Kernel | |
{ | |
// ... lines 9 - 24 | |
public function registerContainerConfiguration(LoaderInterface $loader) | |
{ | |
// ... lines 27 - 28 | |
$isDevEnvironment = $this->getEnvironment() == 'dev'; | |
// ... lines 30 - 31 | |
$loader->load(function (ContainerBuilder $container) use ($isDevEnvironment) { | |
// ... lines 33 - 38 | |
if ($isDevEnvironment) { | |
$container->loadFromExtension('framework', array( | |
'router' => array( | |
'resource' => '%kernel.root_dir%/config/routing_dev.yml', | |
) | |
)); | |
} | |
}); | |
} | |
} |
In every environment, we load config.yml
. But in the dev
environment, we override the router resource config.
Remember, we're in the prod
environment. Refresh. No errors, no web debug toolbar. Change the environment back to dev
and refresh. Whoops: it doesn't like my webprofiler
configuration because that's a typo! Make sure you have web_profiler
:
// ... lines 1 - 24 | |
public function registerContainerConfiguration(LoaderInterface $loader) | |
{ | |
// ... lines 27 - 33 | |
$container->loadFromExtension('web_profiler', array( | |
'toolbar' => true, | |
)); | |
// ... lines 37 - 46 | |
} | |
// ... lines 48 - 49 |
Now it's happy, I'm happy and we have the toolbar.
The power of the environment is that we can have two different sets of configuration right on one machine, and toggling between them is effortless. But to minimize files, we'll keep all the environmental differences inside this Closure.
Parameters in dotenv
There's one more trick to configuration. In config.yml
we have a secret
key. This should be big, long, random and - of course - secret. It should not be committed to the repository. Normally, we'd set this to a parameter - like %secret%:
framework: | |
secret: %secret% | |
// ... lines 3 - 9 |
Then, we'd add this to our parameters.yml
file and be done. But no more! We can do this in .env
.
If I add SECRET = CHANGEME
, that'll create an environment variable called SECRET
, but it will not automatically create a Symfony parameter called secret
. If I refresh, we see the error:
You have requested a non-existent parameter "secret".
Normally, parameters are created by adding a parameters
key in any configuration file and listing them below. But we don't want to put this stuff here.
The secret is that if you prefix any environment variable with SYMFONY__
, Symfony will take what's after that and automatically turn that into a parameter:
// ... lines 1 - 4 | |
# parameters (SYMFONY__ is mapped to parameters) | |
SYMFONY__SECRET=CHANGEME |
Refresh. Now it works perfectly. Goodbye parameters.yml
, hello .env
with SYMFONY__
used as the prefix.
And don't forget to put this line in the .env.example
file so new developers have a chance to get things setup:
// ... lines 1 - 4 | |
# parameters (SYMFONY__ is mapped to parameters) | |
SYMFONY__SECRET=CHANGEME |
getting error : An exception has been thrown during the rendering of a template ("Unable to generate a URL for the named route "_wdt" as such route does not exist."). after moving the _wdt and _profiler routes into the routing_dev.yml and overriding the config file using :
if ($isDevEnv) {
$container->loadFromExtension('framework', array(
'router' => array(
'resource' => '%kernel.root_dir%/config/routing_dev.yml',
)
));
}