If you haven't seen or heard, the Drupal 10 readiness effort is heavy underway. Wait. Drupal 10? Yeah. Drupal 8 will be at its end of life in two months, and Drupal 10 is to be released sometime next year. As the maintainer of phpstan-drupal, there is actually a lot of responsibility on my plate. It serves as the glue to power drupal-check and the Upgrade Status module.
But, first... did you know?
Did you know that this tooling is used to scan every single contributed project on Drupal.org? It is. Here's the Jenkins job for Drupal 10 and Drupal 9. You can see a dashboard on Acquia's developer portal that displays the results, thanks to Gábor Hojtsy: Drupal 10, Drupal 9. When you review these reports, you will see information about Rector. That's where the drupal-rector project maintained by Palantir.net comes in.
Before I get too far, I would like to make sure credit is given where it is due. The phpstan-drupal library is just glue to make PHPStan static code analysis integrate with your Drupal codebase. Thank you, Ondřej Mirtes, for creating this project! Secondly, there is the Rector project. This project builds on top of PHPStan to provide automated refactoring of your PHP code! Thank you, Tomas Votruba, for the Rector project.
Oh, did you know Drupal.org has a Project Update Bot? It accelerated Drupal 9 extensions by providing patches with fixes and will do the same for Drupal 10. Thanks for your work on that, Ted Bowman.
I know this is a pretty large introduction for the blog post. But, my goal is to explain the breadth and scope of some of the underlying complexities and collaboration that must take place. Not just between members of the Drupal community but within the PHP community at large.
I also want to thank Florida DrupalCamp for their sponsorship in the spring of 2020, which allowed me to spend time to get phpstan-drupal ready for Drupal 9 analysis and PHP 8 bug fixes. I would also like to thank all of my sponsors on GitHub that provide roughly three hours of maintenance time a month – and those on Open Collective for phpstan-drupal.
And, finally, I want to give a shout-out to Palantir.net for sponsoring my time to work on drupal-rector! They have sponsored ten live development streams to work on the project to cover Drupal 9 deprecations as we prepare for Drupal 10. This effort has resulted in 95% of deprecated Drupal APIs being covered by Rector rules for automated fixes. We can also add Rector rules to fix PHPUnit and Symfony code – which will be added soon (follow PR#176 PR#177.) You can find the playlist recorded live streams here, on my YouTube: https://www.youtube.com/playlist?list=PLLfOIalqTGRj7m5uI_cRysgkm7KGVWPZt
A tool for a specific job
Every so often, I am asked a few questions:
- Should I run drupal-check or Upgrade Status?
- Why do I need Upgrade Status if I have drupal-rector?
- Is drupal-check required, or can I only use PHPStan?
- Do I need all of these tools? What can I run in my continuous integration builds?
I want to cover each of the tools briefly and why you would use one or the other. I'm going to start at the very low level and then work my way up.
First, though, let's clarify this: PHPStan is used for analyzing and reporting. Rector is for fixing.
PHPStan & phpstan-drupal
I do not use drupal-check in any of my projects. I install PHPStan directly with the required extensions phpstan-drupal and phpstan-deprecation-rules. Here's my nifty one-liner Composer command to add all the dependencies:
composer require --dev phpstan/phpstan \ phpstan/extension-installer \ mglaman/phpstan-drupal \ phpstan/phpstan-deprecation-rules
The phpstan/extension-installer package autoconfigures PHPStan to load the phpstan-drupal package and phpstan/phpstan-deprecation-rules. This is the package that enables reporting usages of deprecated code.
Then, I create a
phpstan.neon file in my project's root directory. All I do is specify an analysis level, and that's it!
parameters: level: 5
All I do is run PHPStan against my custom code. I get static code analysis and deprecated code usage reporting.
php vendor/bin/phpstan.phar analyze web/modules/custom
drupal-check (a PHPStan wrapper)
When I first wrote phpstan-drupal, the Drupal community still had many folks balking at having to use Composer. The phpstan/extension-installer package didn't exist, either. The setup wasn't horrible, but it required a few steps. The project was inspired and born in an email exchange with Dries.
2) Can't a standard
phpstan.neonbe dropped in? It's easier to configure or rename a configuration file, than to create one from scratch, especially if you don't have your your blog post in front of you.
And so, drupal-check was born! It started as a bundled Phar that could be shipped as a standalone binary and not as a project dependency. It generates an on-demand PHPStan configuration that performs deprecated code analysis only and requires zero configuration.
composer require --dev mglaman/drupal-check
drupal-check is a basic abstraction around PHPStan that connects a few pieces. Instead of having to require PHPStan with the required extensions and configure it, you just run drupal-check.
Since most folks have begun adding it as a project dependency, I have dropped the Phar builds to remove additional complexity. I thought about dropping the project completely in favor of direct PHPStan usage. But it has become widely adopted and would be extremely disruptive. One idea I have had, and finally opened an issue for, is an "eject" command to make it easier to migrate from drupal-check to PHPStan directly.
The Upgrade Status module
Enter the Upgrade Status module. Initially, Upgrade Status was essentially the same as drupal-check – just as a Drupal module to provide a user interface and some additional information. Under the hood, Upgrade Status runs PHPStan for you to provide deprecated code analysis.
$output = ; $error_filename = $this->temporaryDirectory . '/phpstan_error_output'; $command = $this->binPath . '/phpstan analyse --memory-limit=-1 --error-format=json -c ' . $this->phpstanNeonPath . ' ' . $project_dir . ' 2> ' . $error_filename; exec($command, $output);
Drupal is a very stateful application. PHPStan is meant for static code analysis, and Drupal has various things tucked away in its state (installed modules, Twig templates, hooks, asset libraries, etc.) Upgrade Status bridges the gaps for analyzing Drupal where PHPStan cannot.
Upgrade Status does provide Drush commands – that is how the Drupal.org Jenkins task executes Upgrade Status against all of those contributed projects. The kicker is that you must have a fully installed Drupal site for it to run.
php vendor/bin/drush upgrade_status:analyze --all --ignore-uninstalled
I recommend using Upgrade Status locally to help get a holistic view of your upgrade status (perfect naming, isn't it?) It also aggregates data from Drupal.org for known upgrade readiness information for contributed projects, local scanning of custom modules, and server requirements. I recently upgraded ContribKanban from Drupal 8 to Drupal 9, and Upgrade Status reminded me I should upgrade from Drush 9 to Drush 10.
I fully believe you can use PHPStan or drupal-check alongside Upgrade Status in your continuous integration builds. Here are example jobs for ContribKanban (it's a project that needs little maintenance, so I want "all the checks" to prevent breaks.) I run PHPStan to catch deprecated code usages and Upgrade Status to ensure there aren't any other problems.
phpcs: docker: - image: circleci/php:7.3-cli steps: - setup-build - install-composer - run: name: phpcs command: ./bin/phpcs web/modules/custom phpstan: docker: - image: circleci/php:7.3-cli steps: - setup-build - install-composer - run: name: phpstan command: ./bin/phpstan analyse web/modules/custom --debug upgrade_status: docker: - image: circleci/php:7.3-cli steps: - setup-build - install-composer - run: name: Install backend command: ./bin/drush -y site-install --account-pass=admin - run: name: upgrade_status command: ./bin/drush upgrade_status:analyze --all --ignore-uninstalled
Rector / drupal-rector
I am sure you have also noticed the acceleration in which software is developed and released shipped out. PHPUnit has a new major version every year (except 2021, PHPUnit didn't ship.) The Symfony framework releases minor versions every six months and majors every two years. And Drupal will begin falling more in line with the Symfony release cycle due to security timelines. That is a lot of code that changes fairly quickly. Especially when you're in an agency and need to balance client budgets, that's where Rector saves the day and makes automated code changes possible.
Rector does not detect deprecated code. It iterates over the source code and provides manipulations. It works by having registered Rector rules. These rules know how to analyze the code, determine if it should run, and then rewrite the code. This is done via the PHP-Parser library by manipulating the abstract syntax tree representation of the PHP code. (If that sounds confusing and ridiculous, it is. You should read an early blog post of mine where AST parsing of traits for PHP 8 support of traits broke class aliasing done by Drupal.)
Whether you use PHPStan, drupal-check, or Upgrade Status, drupal-rector should be installed on your project and leveraged to automate code fixes! It's super easy to add and get started.
composer require --dev palantirnet/drupal-rector
That installs drupal-rector, and then you need to copy the sample configuration.
cp vendor/palantirnet/drupal-rector/rector.php .
And you're set! Now you need to run Rector on a specific directory, and it will automate fixes for now.
php vendor/bin/rector process web/modules/custom
Oh, and you can run Rector in your continuous integration builds, too. It will report any failures (code needing to be fixed) and provides a visual diff.
What is next with these tools, are we done?
No! We're not done.
PHP 8 readiness
Did you know PHP 8 is right around the corner? I mean, it's here. But did you know that Drupal 10 will require it? There is a meta issue opened for PHPStan to provide better detection of backward-incompatible code. That means we can run PHPStan on PHP 7.4 and find deprecated native functions and PHP 8 incompatibilities before running the code on PHP 8.
Luckily, a lot of the code can be automatically fixed by leveraging Rector. We will need to decide what should be added to the drupal-rector configuration by default and what end-users should add themselves.
I don't think we will see much more in terms of deprecations for Drupal 9, but there is still time for them to arrive. That means we need to write Rector rules to help automate those fixes. In fact, there is a policy discussion about making Rector rules a requirement for the deprecation process: https://www.drupal.org/project/drupal/issues/3228433.
Contributions & end-user testing
We need end-users to test and report bugs! There are a lot of use cases where phpstan-drupal isn't wiring up PHPStan properly to catch deprecations. Or maybe PHPStan isn't at all (like detecting deprecated native functions.) These Rector rules rewrite code, but we only have so many samples to test on and ensure they're doing their job properly.
I think it is worth talking about funding. As the rise of Drupal 10 readiness began, I began to feel extremely overwhelmed with managing phpstan-drupal and drupal-check in my free time. I made a plan to put drupal-check and phpstan-drupal on indefinite development hiatus until I could figure out sustainable development funding. That worked itself out a bit faster than I expected. As I said earlier in the blog post, Florida DrupalCamp sponsored my time to get things in shape and ready.
These projects aren't going anywhere. They are far too integral to the Drupal community and our major version upgrade process.
I currently receive $300/mo in sponsorships from GitHub Sponsors. The Open Collective for phpstan-drupal is about $15/mo. That lends to about 3h 15m of work a month on phpstan-drupal and drupal-check and various Upgrade Status pings.
Honestly, I think that's enough for maintenance. Not so much for feature development, but it keeps the projects rolling. I have also learned it is better to estimate a cost for a feature and raise funding that way versus finding ways to maintain recurring revenue to work on the project.
I haven't been as active on phpstan-drupal and drupal-check because they're working just fine. Yes, they could do more. But Palantir.net is funding my time to get drupal-rector ready for Drupal 10. And I have to run Bluehorn Digital and my client work at the same time.
I'm also doing live contribution development streams on my Twitch channel as an experimental way to work on open source projects, share knowledge, and raise some revenue (which is the engagement with Palantir.net for drupal-rector.) So hit that follow button! (and maybe sub?)
Thanks for reading!
I know this was a looooong blog post. I appreciate that you took the time to read and reach the end! 💖