Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Automating Upgrades with Rector

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Now that we're on Symfony 5.4, our job is simple: hunt down and update all of our deprecated code. As soon as we do that, it will be safe to upgrade to Symfony 6. That's because the only difference between Symfony 5.4 and 6.0 is that all the deprecated code paths are removed.

Fortunately, Symfony is amazing and tells us - via the web debug toolbar - exactly what code is deprecated. But understanding what all of these mean... isn't always easy. So before we even try, we're going to automate as much of this as possible. And we're going to do that with a tool called Rector.

Installing Rector

Head to https://github.com/rectorphp/rector. This is an awesome command-line tool with one job: to automate all sorts of upgrades to your code, like upgrading your code from Symfony 5.0 compatible code to Symfony 5.4 compatible code. Or upgrading your code to be PHP 8 compatible. It's a powerful tool... and if you want to learn more about it, they even released a book where you can go deeper... and also help support the project.

All right, let's get this thing installed! Head over to your terminal and run:

composer require rector/rector --dev

Beautiful! In order for rector to work, it needs a config file. And we can bootstrap one by running rector with:

./vendor/bin/rector init

Awesome! That creates the rector.php file... which we can see over at the root of our project.

Tip

The latest version of Rector will generate config that looks a bit different than this. But don't worry, it still works exactly the same.

Inside of this callback function, our job is to configure which types of upgrades we want to apply. These are called "rules" or sometimes "set lists" or rules. We're going to start with a set of Symfony upgrades.

26 lines rector.php
... lines 1 - 9
return static function (ContainerConfigurator $containerConfigurator): void {
// get parameters
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PATHS, [
__DIR__ . '/src'
]);
// Define what rule sets will be applied
$containerConfigurator->import(LevelSetList::UP_TO_PHP_74);
// get services (needed for register a single rule)
// $services = $containerConfigurator->services();
// register a single rule
// $services->set(TypedPropertyRector::class);
};

Configuring Rector for the Symfony Upgrade

If you look back at the documentation, you'll see a link to a Symfony repository where it tells you about a bunch of Symfony "rules" - fancy word for "upgrades" - that they've already prepared! That was nice of them!

Tip

The config on this page will now look different than in the video. But, it still works the same. Copy the latest version into your app.

Below, copy the inside of their callback function... and paste it over what we have.

24 lines rector.php
... lines 1 - 7
use Rector\Symfony\Set\SymfonySetList;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
... line 10
return static function (ContainerConfigurator $containerConfigurator): void {
// region Symfony Container
$parameters = $containerConfigurator->parameters();
$parameters->set(
Option::SYMFONY_CONTAINER_XML_PATH_PARAMETER,
__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml'
);
// endregion
$containerConfigurator->import(SymfonySetList::SYMFONY_52);
$containerConfigurator->import(SymfonySetList::SYMFONY_CODE_QUALITY);
$containerConfigurator->import(SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION);
};

This points Rector to a cache file that helps it do its job... and most importantly, it tells Rector that we want to upgrade our code to be Symfony 5.2 compatible, as well as upgrade our code to some Symfony code quality standards and "constructor" injection. If you want to know more about what these do, you could follow the constants to check out the code.

But, wait, we don't want to upgrade our code to Symfony 5.2! We want to upgrade it all the way to Symfony 5.4. You might expect me to just put "54" here. And we could do that. But instead, I'm going to use SymfonyLevelSetList::UP_TO_SYMFONY_54. Oh... it looks like I also need to add a use statement for SymfonySetList::. Let me retype that, hit "tab" and... great!

25 lines rector.php
... lines 1 - 7
use Rector\Symfony\Set\SymfonyLevelSetList;
use Rector\Symfony\Set\SymfonySetList;
... lines 10 - 11
return static function (ContainerConfigurator $containerConfigurator): void {
... lines 13 - 20
$containerConfigurator->import(SymfonyLevelSetList::UP_TO_SYMFONY_54);
... lines 22 - 23
};

Anyways. We need to upgrade our code from 5.0 to 5.1... then 5.1 to 5.2.. and so on up to Symfony 5.4. That's what UP_TO_SYMFONY_54 means: it will include all of the "rules" for upgrading our code to 5.1, 5.2, 5.3 and finally 5.4.

And... that's it! We're ready to run this. But before we do, I'm curious what changes this will make. So let's add all of the changes to git... and commit. Perfect!

Running Rector

To run Rector, say ./vendor/bin/rector process src/. We could also point this at the config/ or templates/ directories... but the vast majority of the changes it will make apply to our classes in src/:

vendor/bin/rector process src/

And... it's working! Awesome! Eight files were changed by Rector. Let's scroll to the top. This is cool: it shows you the file that was changed, the actual change and, below, which rules caused that change.

One modificiation it made is UserPasswordEncoderInterface to UserPasswordHasherInterface. That's a good change: the old interface is deprecated in favor of the new one. It also changed UsernameNotFoundException to UserNotFoundException. Another good, low-level update to some deprecated code.

There was also a change to a class in Kernel... and a few other similar things. Near the bottom, the Symfony code quality set list added a Response return type to every controller. That's optional... but nice!

So it didn't make a ton of changes, but it did fix a few deprecations without us needing to do anything.

Though... it's not perfect. One problem is that, sometimes, Rector will mess with your coding style. That's because rector doesn't really understand what your coding style is... and so it doesn't even try. But that's by design and will be easy to fix.

Second, while it did change the interface from UserPasswordEncoderInterface to UserPasswordHasherInterface, it inlined the whole class name... instead of adding a use statement.

... lines 1 - 11
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
... lines 13 - 24
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
... lines 27 - 36
public function __construct(SessionInterface $session, EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, \Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface $passwordEncoder)
{
... lines 39 - 43
}
... lines 45 - 119
}

And third, it didn't change any variable names. So even though it changed this argument to UserPasswordHasherInterface, the argument is still called $passwordEncoder... along with the property. Worse, the UserPasswordHasherInterface has a different method on it... and it didn't update the code down here to use that new method name.

... lines 1 - 24
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
... lines 27 - 34
private $passwordEncoder;
... line 36
public function __construct(SessionInterface $session, EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, \Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface $passwordEncoder)
{
... lines 39 - 42
$this->passwordEncoder = $passwordEncoder;
}
... lines 45 - 119
}

So Rector is a great starting point to catch a bunch of changes. But we're going to need to take what we've found and finish the job. Let's do that next. We'll do part of that by hand... but a lot of it automatically with PHP CS Fixer.

Leave a comment!

10
Login or Register to join the conversation

Hi, when I try to run

php vendor/bin/rector process src/

I get this error:

         [ERROR] Argument 1 passed to                                                                                           
         Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator::__construct() must be an      
         instance of Symfony\Component\DependencyInjection\ContainerBuilder, instance of                                
         RectorPrefix202304\Symfony\Component\DependencyInjection\ContainerBuilder given, called in                     
         /Users/andremoens/Documents/onebranchtorulethemall/vendor/rector/rector/vendor/symfony/dependency-inje
         ction/Loader/PhpFileLoader.php on line 67

Has anyone a clue how to fix this?

Reply

Hey @andremoens!

Sorry for the slow reply! That's not a fun error :/. I did find an issue on this - https://github.com/rectorphp/rector/issues/6698

If I'm reading it correctly, if you upgrade Rector AND use the new config format for Rector (if you upgrade rector and run ./vendor/bin/rector init you should get this new format), then it might not be an issue anymore... but I can't guarantee that :). The new config format should look something like this - https://github.com/rectorphp/rector-symfony#use-sets

Let me know if that helps!

Cheers!

Reply

Hi @weaverryan, you're my hero!

Thanks for your help! It's working now....

I now can continue my journey on upgrading to Symfony 6

Reply
Gouchene Avatar

Hey
When I execute this commande "./vendor/bin/rector init", the response : "." is not recognized as an internal control !!
I'm on window. Can someone help me? Than's.

Reply

Hey Gouchene,

That sounds like a Windows error. I believe it's due to the way you're trying to execute rector, instead of using ./path/to/file execute it through PHP php vendor/bin/rector init

Cheers!

Reply
Gouchene Avatar

Hey MollKhan
Thanks for your help, it allows me to continue the course.

Cheers !

1 Reply
Thomas-Peterson Avatar
Thomas-Peterson Avatar Thomas-Peterson | posted 11 months ago

you should update reactor config to look like https://github.com/rectorphp/rector-symfony

Reply

Hey Thomas,

Unfortunately, we can't change the code that was already recorded on the video. However, we can add notes to the code script and video. We will consider adding a note about it, thanks for pointing into it!

Cheers!

Reply

Haha, yea, they updated the config format WHILE I was recording the video - so I actually mention this new format later. But yes, we should add a note in this chapter about it so people aren't surprised when things look different.

Cheers!

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.6", // v3.6.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.5
        "doctrine/annotations": "^1.13", // 1.13.2
        "doctrine/dbal": "^3.3", // 3.3.5
        "doctrine/doctrine-bundle": "^2.0", // 2.6.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.0", // 2.11.2
        "knplabs/knp-markdown-bundle": "^1.8", // 1.10.0
        "knplabs/knp-time-bundle": "^1.18", // v1.18.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.6
        "sentry/sentry-symfony": "^4.0", // 4.2.8
        "stof/doctrine-extensions-bundle": "^1.5", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.7
        "symfony/console": "6.0.*", // v6.0.7
        "symfony/dotenv": "6.0.*", // v6.0.5
        "symfony/flex": "^2.1", // v2.1.7
        "symfony/form": "6.0.*", // v6.0.7
        "symfony/framework-bundle": "6.0.*", // v6.0.7
        "symfony/mailer": "6.0.*", // v6.0.5
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/property-access": "6.0.*", // v6.0.7
        "symfony/property-info": "6.0.*", // v6.0.7
        "symfony/proxy-manager-bridge": "6.0.*", // v6.0.6
        "symfony/routing": "6.0.*", // v6.0.5
        "symfony/runtime": "6.0.*", // v6.0.7
        "symfony/security-bundle": "6.0.*", // v6.0.5
        "symfony/serializer": "6.0.*", // v6.0.7
        "symfony/stopwatch": "6.0.*", // v6.0.5
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-chartjs": "^2.0", // v2.1.0
        "symfony/validator": "6.0.*", // v6.0.7
        "symfony/webpack-encore-bundle": "^1.7", // v1.14.0
        "symfony/yaml": "6.0.*", // v6.0.3
        "symfonycasts/verify-email-bundle": "^1.7", // v1.10.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/string-extra": "^3.3", // v3.3.5
        "twig/twig": "^2.12|^3.0" // v3.3.10
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.1
        "phpunit/phpunit": "^9.5", // 9.5.20
        "rector/rector": "^0.12.17", // 0.12.20
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/maker-bundle": "^1.15", // v1.38.0
        "symfony/var-dumper": "6.0.*", // v6.0.6
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.6
        "zenstruck/foundry": "^1.16" // v1.18.0
    }
}
userVoice