Automating Upgrades with Rector
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:
Tip
In newer versions of Rector, instead of ./vendor/bin/rector init, just run
./vendor/bin/rector to do the same thing.
./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.
| // ... 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.
| // ... 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!
| // ... 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.
20 Comments
gives back
"Command init is not defined"
You should instead now use
which does the same.
Thanks @weekender
To avoid that, you can tell rector to import automatically dependencies with :
$rectorConfig->importNames();That's awesome - thanks for sharing that!
Do we just do ?
There's no levelset I could find
Hey Slawomir,
What levelset exactly are you looking for? It might be so that you're on a different version from the one that is used in the video. I see we're using v0.12.20 of rector/rector package in the video
Cheers!
I'm using v1.2.2. There's a levelset in the video with up_to_symfony54 but there aren't any
SymfonyLevelSetList::UP_TO_SYMFONY54. Does this mean using symfony54 will include everything before or do we have to import everything before that ?Hey Slawomir,
Yes, that seems to be deleted in https://github.com/rectorphp/rector-symfony/pull/609 because it was deprecated. Yeah, now I believe you need to use more specific set lists from the SymfonySetList, and if you need to jump over a few minor versions - you probably need to process them one by one now.
I hope that helps!
Cheers!
Hi. Love your screencasts on Symfony! I'm sure I'm doing something dumb using this code on SF 5.4.28 and Rector ^0.13.9:
Can you help me resolve:
Cheers,
JPS
Hey Jamuel,
Your Rector config looks fine. Try to upgrade Rector to the latest which is v0.18.3. I suppose those might be some internal issues that were fixed in a newer version. Also, try to clear the Symfony cache with
rm -rf var/cache/before running Rector just in case.Also, are you sure you're in the root dir of your Symfony project? Do you recognize that
/private/var/folders/d9/bv_yx_fx5hg9hj1t9v1lcym80000gn/T/PXtNQdfolder? It seems like you're in that temporary folder somehow and trying to run the Rector from it.Also, please, make sure you have valid PHP syntax in your project code, it might be so that you accidentally removed some chars and now code is broken, try to run the project first to see it works well in the browser and no errors.
P.S. I see you're using php@7.4 that is a legacy PHP version and dropped in Brew package manager, i.e. you can't update or install it anymore. Maybe try a different (newer) PHP version.
I hope this helps!
Cheers!
Hi, when I try to run
php vendor/bin/rector process src/I get this error:
Has anyone a clue how to fix this?
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 inityou 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-setsLet me know if that helps!
Cheers!
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
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.
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/fileexecute it through PHPphp vendor/bin/rector initCheers!
Hey MollKhan
Thanks for your help, it allows me to continue the course.
Cheers !
you should update reactor config to look like https://github.com/rectorphp/rector-symfony
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!
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!
"Houston: no signs of life"
Start the conversation!