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

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

Login Subscribe

Your app - the PHP code you write - is a machine: it does whatever interesting thing you told it to do. But that doesn't mean your machine always has the same behavior: by giving that machine different config, you can make it work in different ways. For example, during development, you probably want your app to display errors and your logger to log all messages. But on production, you'll probably want to pass configuration to your app that tells it to hide exception messages and to only write errors to your log file.

To help with this, Symfony has a powerful concept called "environments". This has nothing to do with server environments - like your "production environment" or "staging environment". In Symfony, an environment is a set of configuration. And by default, there are two environments: dev - the set of config that logs everything and shows the big exception page - and prod, which is optimized for speed and hides error messages.

And we can see these environments in action! Open up public/index.php:

28 lines public/index.php
... lines 1 - 2
use App\Kernel;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;
require dirname(__DIR__).'/config/bootstrap.php';
if ($_SERVER['APP_DEBUG']) {
umask(0000);
Debug::enable();
}
if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
}
if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) {
Request::setTrustedHosts([$trustedHosts]);
}
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

This is your "front controller": a fancy way of saying that it's the file that's always executed first by your web server.

Where the Environment String is Set

If you scroll down a bit - most things aren't too important - this eventually instantiates an object called Kernel and passes it $_SERVER['APP_ENV']:

28 lines public/index.php
... lines 1 - 22
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
... lines 24 - 28

That APP_ENV thing is configured in another file - .env - at the root of your project:

22 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=c28f3d37eba278748f3c0427b313e86a
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
#TRUSTED_HOSTS='^(localhost|example\.com)$'
###

There it is: APP_ENV=dev:

22 lines .env
... lines 1 - 15
###> symfony/framework-bundle ###
APP_ENV=dev
... lines 18 - 22

So right now, we are running our app in the dev environment. By the way, this entire file is a way to define environment variables. Despite the similar name, environment variables are a different concept than Symfony environments... and we'll talk about them later.

Right now, the important thing to understand is that when this Kernel class is instantiated, we're currently passing the string dev as its first argument:

28 lines public/index.php
... lines 1 - 22
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
... lines 24 - 28

If you want to execute your app in the prod environment, you would change the value in .env:

22 lines .env
... lines 1 - 15
###> symfony/framework-bundle ###
APP_ENV=dev
... lines 18 - 22

We'll do exactly that in a few minutes.

Kernel: How Environments Affect things

Anyways, this Kernel class is actually not some core class hiding deep in Symfony. Nope! It lives in our app: src/Kernel.php. Open that up:

55 lines src/Kernel.php
... lines 1 - 2
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
private const CONFIG_EXTS = '.{php,xml,yaml,yml}';
public function registerBundles(): iterable
{
$contents = require $this->getProjectDir().'/config/bundles.php';
foreach ($contents as $class => $envs) {
if ($envs[$this->environment] ?? $envs['all'] ?? false) {
yield new $class();
}
}
}
public function getProjectDir(): string
{
return \dirname(__DIR__);
}
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
$container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
$container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug);
$container->setParameter('container.dumper.inline_factories', true);
$confDir = $this->getProjectDir().'/config';
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
$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): 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');
}
}

The Kernel is the heart of your application. Well, you won't need to look at it often... or write code in it... maybe ever, but it is responsible for initializing and tying everything together.

What does that mean? You can kind of think of a Symfony app as just 3 parts. First, Symfony needs to know what bundles are in the app. That's the job of registerBundles():

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 17
public function registerBundles(): iterable
{
$contents = require $this->getProjectDir().'/config/bundles.php';
foreach ($contents as $class => $envs) {
if ($envs[$this->environment] ?? $envs['all'] ?? false) {
yield new $class();
}
}
}
... lines 27 - 53
}

Then, it needs to know what config to pass to those bundles to help them configure their services. That's the job of configureContainer():

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 32
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
$container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
$container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug);
$container->setParameter('container.dumper.inline_factories', true);
$confDir = $this->getProjectDir().'/config';
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
$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 45 - 53
}

And finally, it needs to get a list of all the routes in your app. That's the job of configureRoutes():

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 45
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');
}
}

By the way, if you start a Symfony 5.1 app, you probably won't see a registerBundles() method. That's because it was moved into a core trait, but it has the exact logic that you see here.

registerBundles()

Back up in registerBundles(), the flag that we passed to Kernel - the dev string - eventually becomes the property $this->environment:

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 17
public function registerBundles(): iterable
{
... line 20
foreach ($contents as $class => $envs) {
if ($envs[$this->environment] ?? $envs['all'] ?? false) {
... line 23
}
}
}
... lines 27 - 53
}

This methods uses that. Open up config/bundles.php:

... lines 1 - 2
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::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],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Knp\Bundle\MarkdownBundle\KnpMarkdownBundle::class => ['all' => true],
];

Notice that all of the bundles classes are set to an array, like 'all' => true or some have 'dev' => true and 'test' => true. This is declaring which environments that bundle should be enabled in. Most bundles will be enabled in all environments. But some - like DebugBundle or WebProfilerBundle - are tools for development. And so, they are only enabled in the dev environment. Oh, and there is also a third environment called test, which is used if you write automated tests.

Over in registerBundles(), this loops over the bundles and uses that info to figure out if that bundle should be enabled in the current environment or not:

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 17
public function registerBundles(): iterable
{
... line 20
foreach ($contents as $class => $envs) {
if ($envs[$this->environment] ?? $envs['all'] ?? false) {
yield new $class();
}
}
}
... lines 27 - 53
}

This is why the web debug toolbar & profiler won't show up in the prod environment: the bundle that powers those isn't enabled in prod!

configureContainer: Environment-Specific Config Files

Anyways, bundles give us services and, as we've learned, we need the ability to pass config to those bundles to control those services. That's the job of configureContainer():

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 32
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
$container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php'));
$container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug);
$container->setParameter('container.dumper.inline_factories', true);
$confDir = $this->getProjectDir().'/config';
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
$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 45 - 53
}

I love this method. It's completely responsible for loading all the config files inside the config/ directory. Skip past the first 4 lines, if you have them, which set a few low-level flags.

The real magic is this $loader->load() stuff, which in a Symfony 5.1 app will look like $container->import()... but it works the same. This code does one simple thing: loads config files. The first line loads all files in the config/packages/ directory. That self::CONFIG_EXTS thing refers to a constant that tells Symfony to load any files ending in .php, .xml, .yaml, .yml. Most people use YAML config, but you can also use XML or PHP.

Anyways, this is the line that loads all the YAML files inside config/packages. I mentioned earlier that the names of these files aren't important. For example, this file is called cache.yaml even though it's technically configuring the framework bundle:

framework:
cache:
... lines 3 - 20

This shows why:

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 32
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
... lines 35 - 39
$loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob');
$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 45 - 53
}

Symfony loads all of the files - regardless of their name - and internally creates one giant, array of configuration. Heck, we could combine all the YAML files into one big file and everything would work fine.

But what I really want you to see is the next line. This says: load everything from the config/packages/ "environment" directory:

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 32
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
... lines 35 - 40
$loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob');
... lines 42 - 43
}
... lines 45 - 53
}

Because we're in the dev environment, it's loading the 4 files in config/packages/dev. This allows us to override configuration in specific environments!

For example, in the prod/ directory, open the routing.yaml file. This configures the router and sets a strict_requirements key to null:

framework:
router:
strict_requirements: null

It's not really important what this does. What is important is that the default value for this is true, but a better value for production is null. This override accomplishes that. I'll close that file.

So this whole idea of environments is, ultimately, nothing more than a configuration trick: Symfony loads everything from config/packages and then loads the files in the environment subdirectory... which lets us override the original values.

Oh, these last two lines load services.yaml and services_{environment}.yaml:

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 32
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
{
... lines 35 - 41
$loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob');
$loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob');
}
... lines 45 - 53
}

That's where we add our own services to the container and we'll talk about them soon.

configureRoutes()

Ok, we've now initialized our bundles and loaded config. The last job of Kernel is to figure out what routes our app needs. Look down at configureRoutes():

55 lines src/Kernel.php
... lines 1 - 11
class Kernel extends BaseKernel
{
... lines 14 - 45
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');
}
}

Ah, it does... pretty much the exact same thing as configureContainer(): it loads all the files from config/routes - which is just one annotations.yaml file - and then loads any extra files in config/routes/{environment}.

Let's look at one of these: config/routes/dev/web_profiler.yaml:

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

This is what's responsible for importing the web debug toolbar and profiler routes into our app! At your terminal, run:

php bin/console debug:router

Yep! These /_wdt and /_profiler routes are here thanks to that file. This is another reason why the web debug toolbar & profiler won't be available in the prod environment.

Next, let's change environments: from dev to prod and see the difference. We're also going to use our new environment knowledge to change the cache configuration only in the prod environment.

Leave a comment!

11
Login or Register to join the conversation
Szabolcs Avatar
Szabolcs Avatar Szabolcs | posted 2 years ago

Hello There!

In my Symfony 5.2 project the Kernel.php looks a litle bit different. The file-extensions "yaml" is now on all imports hardcoded.
Is now by default not xml-support? In the documentation I don't see any hint or something.

Thanks and greetings from Munich! :)

1 Reply

Hey Szabolcs

IIRC from flex recipe changes this change was made from version 5.1. Default kernel was simplified to use only YAML or PHP configuration, so if you want to use XML you need to implement it by yourself.

Cheers!

Reply
triemli Avatar
triemli Avatar triemli | posted 2 years ago | edited

Hello guys!
Can you help me?
I want to modify loaded ENV_VAR value in twig config:

`twig:

default_path: '%kernel.project_dir%/src/%env(ucfirst:APP_NAME)%/templates'`

By default <b>APP_NAME</b> has 'frontend" value. I want to rename to "Frontend"

Processor also created: https://pastebin.com/GApxf5T4

Actually the implemented method returns "Frontend". So everything is good!
But. It doesn't load my templates ;[

But if I replace it manually:
`twig:

default_path: '%kernel.project_dir%/src/Frontend/templates'`

It works and load a templates!

What i'm doing wrong?

Reply

triemli

That's odd. Can you double-check that APP_NAME does not contain any white space at the beginning or at the end of the string? Another thing to try is to not use the processor for a moment and update your variable to Frontend, let's see if by doing that change it works, if it works it would mean that the processor is doing something odd. You can set the result into a parameter and dump it to see what's actually returning

Cheers!

Reply
triemli Avatar

I tried to use it from scratch: i got an error:
There are no registered paths for namespace "__main__".
Somehow need to say for twig this path.
But idk why, if I specified default_path in twig config.

Reply

Hey triemli!

Hmm. So, in some cases, you can't use an environment variable for a piece of config, and I think this might be one of those situations. The problem is that, in order for the env vars to work, Symfony must not "resolve" the values during container builder. Basically, your container is built just once when you deploy, and the dumped container (in an ideal situation) will container (basically) getenv('APP_NAME') so that when Twig is instantiated, it gets the current environment variable. In some cases, however, Symfony needs to actually "resolve: (and get the final value) of an environment variable during the build process. That's what's happening in this case.

I'm actually not sure that this is needed... it could be a bug. But basically, the value is "semi-resolved" (to this internal placeholder value) during container build and then Symfony checks to see if that directory exists (using this semi-resolved, internal hash value). It doesn't, so it simply doesn't even add that path to Twig at all. This maybe smells like a Symfony bug to me.

But, let me say 2 more things:

A) First, I think you can work around this by adding a Twig namespace path instead of overriding the default:




twig:
    default_path: '%kernel.project_dir%/templates'
    paths:
        '%kernel.project_dir%/%env(APP_NAME)%': 'App'

You would render things as @App/path/to/file.html.twig. The only caveat I'm aware of is that whatever APP_NAME is set to when you are deploying, that directory will need to exist (else the container build will fail).

B) Second, what you're trying to do may work, but the system isn't really meant to be used this way. For one, the Twig path is normally read at "compile time" so that Symfony can "cache warm" the Twig templates: pre-compile them so that they're not compiled at runtime on the first request. You would be opting out of that. Another solution to all of this would be to wrap Twig in a custom service, give that customer service a render() call, and then your service pre-fixes the APP_NAME value when rendering. The only catch is that the default_path (or at least one of the paths would need to point to a directory that contains ALL the templates. So, in your example, it would probably need to point to src/. Then, when rendering in a controller, you might pass default/homepage.html.twig to your custom service. It would then change that to be Frontend/templates/default/homepage.html.twig.

Anyways, it's a bit messy in all directions to have things this dynamic. Another option, if you have a "set"/static number of possible APP_NAME values, would be to create one twig namespaced path (that's the paths config again) per possible value - e.g. one for "Frontend". Then, your custom service would just prefix the paths with @ then the value of APP_NAME then the path that was passed to it (e.g. <code<default/homepage.html.twig`).

Cheers!

Reply
triemli Avatar
triemli Avatar triemli | weaverryan | posted 2 years ago | edited

Thanks @weaverryan for the great answer!
I tried this configurations:

`twig:

default_path: '%kernel.project_dir%/src/%env(ucfirst:APP_NAME)%/templates'
paths:
    '%kernel.project_dir%/src/%env(ucfirst:APP_NAME)%/templates': '__main__'

`

And it works!

Basically I have this approach of multi application architecture:

https://github.com/yceruto/symfony-skeleton-vkernel/blob/master/src/VirtualKernel.php#L65

So that's why I need dynamic load. Based on APP_NAME we are load "right" configuration. In src folder many folders like <i>Frontend</i>, <i>Backend</i>, <i>Publisher</i>, <i>Landing</i>, <i>Api</i> etc...

Becuase of this all apps use the same entities, services and database. So copy-paste those big entities in separated symfony apps would be cold idea. Every even a small changes in 1 entity would require to remember fix <b>all </b>the projects.

The only what I affraid a bit about twig cache. It need to be separated too.

Reply

Hey triemli!

Nice! About the Twig cache warming, it will probably be fine without it - I just don't have any experiencing with using a system where it's not "warmed up". But it's probably not a big deal. But again, if you have a "finite" number of APP_NAME (i.e. it's not a dynamic value where a new one could be added to the database, for example), then you could have the "best" of both worlds by creating one twig "path" per possible app name - e.g.


twig:
    # ...
    paths:
        '%kernel.project_dir%/src/Frontend/templates': 'Frontend'
        '%kernel.project_dir%/src/Backend/templates': 'Backend'

... and then read the APP_NAME in a custom service that prefixes the @ and then the APP_NAME env var. The downside is that, whenever you render a template, you need to call your service instead of Twig directly (but you could create a custom controller that overrides the render() method and calls your service instead.

Cheers!

1 Reply
triemli Avatar
triemli Avatar triemli | weaverryan | posted 2 years ago | edited

weaverryan yeah, I will check how it will work in real project and maybe also try with override render() method. Also nice idea! Thanks a lot for advise!

1 Reply
Cat in space

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

This tutorial also works great for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "sentry/sentry-symfony": "^4.0", // 4.0.3
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.6.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.15", // v1.23.0
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}
userVoice