How we Upgraded Pehapkari.cz from Symfony 4 to 5 in 25 days
Tomáš VotrubaA month ago, Symfony 5 has been released. Upgrading of such a small web as our community website must be easy, right?
Well, that's what we thought. Were we right or wrong?
This post is based on the real problems we faced when we upgraded our website. It is full of experience with pieces of explanation, real code snippets in diffs, painful frustration of Symfony ecosystem and bright light at the end of the tunnel.
Are you ready? Let's dive in ↓
1. Easy picks
Twig 2 → 3
Before, you could connect for
with if
like this:
{% for post in posts if post.isPublic() %}
{{ post.title }}
{% endfor %}
Now the filter
has to be used:
{% for post in posts|filter(post => post.isPublic()) %}
{{ post.title }}
{% endfor %}
Thanks Patrik for the tip
2. Rector Helps You with PHP
Do you want to know, what is needed for the upgrade to Symfony 5? Just read upgrade notes in Symfony repository.
For PHP stuff, use Rector:
# install Rector
composer require rector/rector --dev
# or in case of conflicts
composer require rector/rector-prefixed --dev
Rector has minimal sets, meaning each minor version is standalone and independent. What does that mean? For upgrading from Symfony 4 to 5, you need to run all the minor version sets:
vendor/bin/rector process bin src packages --set symfony41
Verify, check that CI passes and then continue with following Symfony minor versions:
vendor/bin/rector process bin src packages --set symfony42
vendor/bin/rector process bin src packages --set symfony43
vendor/bin/rector process bin src packages --set symfony44
vendor/bin/rector process bin src packages --set symfony50
3. Update composer.json
before composer update
Easy Admin Bundle
{
"require": {
- "alterphp/easyadmin-extension-bundle": "^2.1",
+ "alterphp/easyadmin-extension-bundle": "^3.0",
}
}
Doctrine
{
"require": {
- "doctrine/cache": "^1.8",
+ "doctrine/cache": "^1.10",
- "doctrine/doctrine-bundle": "^1.11",
+ "doctrine/doctrine-bundle": "^2.0",
- "doctrine/orm": "^2.6",
+ "doctrine/orm": "^2.7",
}
}
Doctrine Behaviors
{
"require": {
- "stof/doctrine-extensions-bundle": "^1.3",
- "knplabs/doctrine-behaviors": "^1.6"
+ "knplabs/doctrine-behaviors": "^2.0"
}
}
Sentry
{
"require": {
- "sentry/sentry-symfony": "^3.2",
+ "sentry/sentry-symfony": "^3.4",
}
}
Twig Extensions were Removed
{
"require": {
- "twig/extensions": "^1.5"
}
}
This might be scary at first, depends on how many of those functions have you used.
Look at the README on Github to find out more:
4. Symfony Packages in composer.json
Do you use Flex and config *
version?
{
"require": {
"symfony/console": "*",
"symfony/event-disptacher": "*"
},
"extra": {
"symfony": {
"require": "^4.4"
}
}
}
Not sure why, but in some cases, it failed and blocked from the upgrading. I had to switch to explicit version per package, to resolve it:
{
"require": {
- "symfony/console": "*",
+ "symfony/console": "^4.4",
- "symfony/event-disptacher": "*"
+ "symfony/event-disptacher": "^4.4"
- },
+ }
- "extra": {
- "symfony": {
- "require": "^4.4"
- }
- }
}
Then switch to Symfony 5:
-"symfony/asset": "^4.4",
+"symfony/asset": "^5.0",
-"symfony/console": "^4.4",
+"symfony/console": "^5.0",
// etc.
But some packages are released out of monorepo cycle:
-"symfony/maker-bundle": "^1.14",
+"symfony/maker-bundle": "^1.13",
All right, now you run...
composer update
...and get new packages with Symfony 5... or probably a lot of composer version conflicts.
Symfony Packages WTFs in
In Symfony 5, some packages were removed:
-"symfony/error-renderer": "^4.4",
-"symfony/web-server-bundle": "^4.4",
Some packages were replaced by new ones:
-"symfony/error-debug": "^4.4",
+"symfony/error-handler": "^5.0",
And some package were split into more smaller ones:
-"symfony/security": "^4.4",
+"symfony/security-core": "^5.0",
+"symfony/security-http": "^5.0",
+"symfony/security-csrf": "^5.0",
+"symfony/security-guard": "^5.0",
5. Rector, ECS, and PHPStan
These were production dependencies, but what about dev ones? Both have the same rules - they need to allow Symfony 5 installation.
The safest way is to use prefixed versions, which don't care about a Symfony version:
-"phpstan/phpstan": "^0.11",
+"phpstan/phpstan": "^0.12",
-"rector/rector": "^0.5",
+"rector/rector-prefixed": "^0.6",
-"symplify/easy-coding-standard": "^0.11",
+"symplify/easy-coding-standard": "^0.12",
Update your composer.json
to include a package that you need.
Then run:
composer update
Still conflicts?
6. And The Biggest Symfony Upgrade Blocker is...
If you don't do open-source, you probably don't use the git tag
feature. It seems that the tagging of a package is a very difficult process. Even packages with million downloads/month had the latest 15 months ago.
What are Tags For?
Let's say you want to use symplify/easy-coding-standard
that supports Symfony 5. Here is the deal:
- the latest
symplify/easy-coding-standard
version 6 doesn't support it symplify/easy-coding-standard
dev-master (~= what you see on GitHub) supports it- but it's not tagged yet and composer forbids to install dev version; e.g. sentry-symfony at time of writing this post
- so you'd have to require its dev version and force composer to install it
{
"require-dev": {
"symplify/easy-coding-standard": "dev-master"
},
"prefer-stable": true,
"minimum-stability": "dev"
}
- it's a hackish solution, but it somehow works
Now imagine one of your package you require requires some other package, that requires another package, that doesn't allow Symfony 5 in tagged version, but in master
. Well, you've just finished.
That's why it's very important to know to tag a package regularly:
git tag v2.0.0
git push --tags
That's all! Still, many packages support Symfony 5 in the master but are not tagged yet... to be true, not once for the last 2 years. Why? The human factor, maintainers are afraid of negative feedback, of back-compatibility breaks, lack of test coverage takes their confidence, etc.
Tagging Cancer of PHP Ecosystem
These packages block Symfony 5 upgrade for months:
- stof/gedmo extension (last release in 2017)
- knplabs/doctrine-behaviors (last stable release in 2018)
- behat/transliterator (last release in 2017) - this comment sums it up very nicely
United We Stand
This will be resolved in the future by an open-source monorepo approach, but we're not there yet.
In the meantime, please complain at issues, ask for help and offer to become maintainer until it changes (or until somebody forks it and tags the fork).
One good example for all - I complained and offered help at knplabs/doctrine-behaviors
, got maintainer rights in 3 hours and made + merged 30 pull-request in the last month.
You see, it works :)
7. Still Conflicts?
Ok, so you have the right version of packages, everything is stable and allows Symfony 5. Yet still, the composer says "conflicts, cannot install x/y".
To my surprise, the composer is very bad at solving complex conflicts. Composer reports false positive and blocks your install, because of installing packages in /vendor
or overly strict composer.lock
. I spent 30-60 minutes trying to figure out what the heck is conflicting on every Symfony training I had in the last 2 months. Now I'm able to do it in 3 minutes.
How?
- remove
/vendor
- remove
composer.lock
- run
composer update
It works so well I do it more often than resolving conflicts manually.
8. Cleanup bundles.php
- Doctrine Cache was only released for Symfony 4.4 and is not supported for any further version.
return [
- Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
];
Switch the dead gedmo/stof doctrine extensions for the maintained KnpLabs/DoctrineBehaviors. I'll write a standalone post about this migration, once a stable version is out (check me, pls :)).
return [
- Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
];
We also had some troubles with Switfmailer Bundle:
return [
- Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
]
The Mailer component will take over Swiftmailer in the future, so this is just a start.
9. Clear config/packages
# config/packages/framework.yaml
framework:
...
- templating:
- engines: ["twig"]
Don't forget to remove all extension configs. In our case it was:
-config/packages/stof_doctrine_extensions.yaml
-config/packages/swiftmailer.yaml
-config/packages/dev/swiftmailer.yaml
-config/packages/test/swiftmailer.yaml
-config/packages/twig_extensions.yaml
-config/routes/dev/twig.yaml
Small update of the EasyAdmin bundle:
# config/routes/easy_admin.yaml
easy_admin_bundle:
- resource: '@EasyAdminBundle/Controller/AdminController.php'
+ resource: '@EasyAdminBundle/Controller/EasyAdminController.php'
And that's all folks!
Got any questions?
Feel free to explore it, ask, read comments or share your problems.
Check the PR on Github
Have we Missed Something?
Of course, we did! Every application has a different set of blocking dependencies and different sets of used Symfony features that might have changed.
Share your issues in comments or edit this post on Github to make list complete!
Thanks for your help and enjoy your Christmas, wherever you are!