Modernize Symfony Configs

Symfony configuration is one of the changes that are difficult to spot until they're removed in the next major version. Then you must Google the "invalid option id error message" and hope for a solution. That doesn't sound like an excellent way to spend your weekend, does it?

Symfony actually adds a deprecation message to those options, but they're not easy to spot.

Today, we'll show you how to spot them with the help of Rector, PHPStan, and one other fantastic tool.

Symfony security is known for major changes in almost every Symfony version. What worked in the past...

# config/security.yml
security:
    enable_authenticator_manager: true

...can be changed or removed.

This actually does not work in Symfony 7. We want to be warned early, as soon as the change happens, and without running our code. Other PHP projects use @deprecation annotations that highlight deprecated methods, properties, constants, and classes right in code in your favorite IDE.

How about YAML? It does not, so we first have to migrate to PHP.

1. Move from YAML to PHP

It is not necessary to bother with a manual flip. Use symplify/config-transformer to automate the process instead:

composer require symplify/config-transformer --dev
vendor/bin/config-transformer switch-format config/security.yml

It parses YAML, maps it into PHP format, and prints it out:

# config/security.php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->extension('security', [
        'enable_authenticator_manager' => true,
    ]);
};

Job done!

If it's your first PHP fluent config, don't forget to update Kernel to load PHP files too.


Now we have PHP configs, but in reality - it's just another form of array full of strings.

Symfony can do better, at least since Symfony 5.3.


2. Move from an array of strings to typed PHP objects

Instead of arrays, we want something we can use in our IDE - fully typed objects with methods autocomplete.

Are you on Symfony 5.3+? If not, upgrade first with Rector. Then, you can use Config Builder Classes.

But there is a catch - it's tricky to tell your IDE and PHPStan about them because they're not part of the Symfony code. They're generated on the fly based on your current Symfony version.


To streamline the generating, use tomasvotruba/symfony-config-generator tool that detects available extension classes and generates config builder classes for us:

composer require tomasvotruba/symfony-config-generator --dev
vendor/bin/symfony-config-generator

Check the generate classes in your Symfony cache directory:

/var/cache/Symfony

We have config builder classes available and PHP with arrays of string.


How can we flip those arrays to config builder classes?

Rector to the rescue! Symfony rules for Rector contain one little gem that helps us automate 99 % of work - the StringExtensionToConfigBuilderRector rule. Add it to your rector.php config:

# rector.php
use Rector\Config\RectorConfig;
use Rector\Symfony\CodeQuality\Rector\Closure\StringExtensionToConfigBuilderRector;

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->rules([
        StringExtensionToConfigBuilderRector::class,
    ]);
};

Then run Rector on the config file:

vendor/bin/rector p config/security.php

And voilá - here is your state of art Symfony security config:

# config/security.php
use Symfony\Config\SecurityConfig;

return static function (SecurityConfig $securityConfig): void {
    $securityConfig->enableAuthenticatorManager(true);
};

Note: some elements like firewall, roles, etc. will need a separate variable call - PHPStan will navigate you.


You have just increased the value of your configs by order of magnitude:

  • full IDE autocomplete of any security configuration
  • no more copy-pasting "firewall" string and reading Symfony docs options
  • you get warnings by PHPStan if we ever make a typo

To make PHPStan work and not complain about missing classes located in /var/cache, we have to load classes in PHPStan config:

# phpstan.neon
parameters:
    bootstrapFiles:
        - var/cache/Symfony/Config/SecurityConfig.php
        - var/cache/Symfony/Config/Security/ProviderConfig.php
        - var/cache/Symfony/Config/Security/AccessControlConfig.php
        - var/cache/Symfony/Config/Security/AccessDecisionManagerConfig.php
        - var/cache/Symfony/Config/Security/FirewallConfig.php
        - var/cache/Symfony/Config/Security/PasswordHasherConfig.php
        - var/cache/Symfony/Config/Security/ProviderConfig.php

3. Harvest full power of Static Analysis

We'd love to work with the Symfony project in such a shape, but wait - there is more.

What we really want is to get early warnings about deprecations right in our CI before we decide to upgrade. The configs are unavailable in the Symfony Github repository, as they're generated on the fly.

But when we look at the file in our project:

var/cache/Symfony/Config/SecurityConfig.php

We'll see the enableAuthenticatorManager() method is actually deprecated in Symfony 6.4:

    /**
     * @default true
     * @param ParamConfigurator|bool $value
     * @deprecated The "enable_authenticator_manager" option at "security" is deprecated.
     * @return $this
     */
    public function enableAuthenticatorManager($value): static
    {
        // ...
    }

Now we have:

  • configs in PHP methods
  • PHPStan available
  • we use a method with the @deprecated marker

But that's not enough to let CI help us. One piece is missing... care to guess?


PHPStan provides an extension that reports @deprecated elements right in the CI:

composer require phpstan/phpstan-deprecation-rules --dev

Note: include phpstan/extension-installer if you don't have it yet:

composer require phpstan/extension-installer --dev

Let's run PHPStan on our config to see the result:

vendor/bin/phpstan a config/security.php

The PHPStan fails and let us know:

1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

------ -------------------------------------------------------------------------
 Line   config.php
------ -------------------------------------------------------------------------
 8      Call to deprecated method enableAuthenticatorManager()
        of class Symfony\Config\SecurityConfig:
        The "enable_authenticator_manager" option at "security" is deprecated.
------ -------------------------------------------------------------------------

Now you're covered and can start fixing deprecations before they're gone completely.


Setting up can take a while for the first time, but you'll get quickly into it. The value returns a thousand folds in the long term as you'll get the best Symfony configuration there is.


Happy coding!