Skip to main content

drupal-check and phpstan-drupal are Drupal 9 and PHP 8 ready

Published on

I am happy to announce that phpstan-drupal and drupal-check are Drupal 9 and PHP 8 ready! Both projects have CI pipelines running tests and analysis to make sure they're compatible as we start the journey for Drupal 10 (đŸ˜± already? I know!)

Florida DrupalCamp sponsored 20 hours of development. Since my funding is from the community, I have been working on these projects via a live stream on Twitch every Wednesday.

Of the sponsored 20 hours thus far, I have used 7 hours total. Maybe a little more since I haven't included time spent in Drupal Slack very well. I'll explain how I'll use the remaining hours at the end of this update.


The 0.12.9 version was released on March 10th. This release allowed Upgrade Status to create an alpha release with PHP 8 support. It also unblocked drupal-check as well. I want to give thanks to andypost and liber_t for pull requests to fix errors when running on PHP 8. My work was getting the testing pipelines to verify everything was working with PHP 8 and Drupal 9.

If you missed the live stream, I hit a fun error due to Drupal's workaround PHPUnit 8. PHPUnit 8 added a void return type to the setUp method. Drupal tests are littered with overrides of this method and do not specify the return type, and therefore are not compatible overrides. What does that mean? When you try to analyze or execute the file, you get the following error:

PHP Fatal error:  Declaration of Drupal\Tests\UnitTestCase::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): void

How doe Drupal work around this? Shouldn't everything be breaking?! Drupal provides various PHPUnit compatibility tools. In fact, a bug in PHP-Parser was crashing on a class alias within a trait for PHPUnit compatibility not too long ago. While reading through Drupal's PHPUnit bootstrap file, I found this ClassWriter class. It finds the PHPUnit\Framework\TestCase class before it has been loaded and on-demand mutates the base test class and copies it into the sites/simpletest folder. Yeah. Drupal modifies a class at runtime, moves it into a new location, and autoloads it to replace the default code.

That meant Drupal could not be statically analyzed if PHPUnit 8 is installed without running the mutation. I needed to find a fix that worked when running phpstan-drupal's own PHPUnit test suite and analyzing a Drupal codebase. If you want to watch my visual despair, here is a link to that part of the stream:

In the end, I had to create a file that replicated the code in ClassWriter that would be autoloaded before Drupal was analyzed or any PHPUnit classes loaded during tests. The result was a drupal-phpunit-hack.php file added to phpstan-drupal's autoload definition. It creates a mutated test class into its own codebase. 


I also made the 1.1.8 release for drupal-check. It felt good to triage the project and its issues. Overall, I am not sure if drupal-check actually needed a release since I have removed the Phar installation option (which provides all dependencies bundled into a single executable.) Technically, users just needed to run composer update to receive the updated phpstan-drupal dependency. And that got me wondering: how are people installing and using this tool?

I first created the drupal-check project to ease the installation and configuration of PHPStan. Since then, it's gotten a lot easier. Like, this easy:

composer require \
  phpstan/phpstan \
  phpstan/extension-installer \
  phpstan-deprecation-rules \

Then you just run PHPStan.

# Full static analysis
php vendor/bin/phpstan analyze web/modules/custom

# Deprecations only
php vendor/bin/phpstan analyze --level 0 web/modules/custom

During the live stream, I popped over to Twitter and asked how folks are installing drupal-check with their projects. It turns out a lot of folks are using drupal-check over PHPStan directly. 62% have it as a local dependency on their project. Which I am glad to see! Originally it started as a standalone Phar and then as a globally installed tool.

I feel we, as a community, have shifted from globally available commands to those scoped into our projects. I wonder if this was a Drupal thing or a shift in the PHP community as a whole.

I wasn't sure if drupal-check should live on, but it seems like the community really likes it. So I will keep on maintaining it, and it shall continue!

The path forward to Drupal 10!

As I said, roughly 13 hours are remaining of my sponsored development time. Thanks to undpaul and Intracto and my other GitHub Sponsors, I have a guaranteed three hours a month of development.

Even though these projects have new releases, we have only scratched the surface with PHP 8 testing, and I'm sure we'll find a handful more bugs. For instance, it turns out phpstan-drupal does not properly support files loaded through the procedural module_load_include function! That means some files are ignored. The phpstan-drupal extension provides inspection of services, but I found out it doesn't report when undefined services are called. So if a module on Drupal 9 called entity.manager, there is no error reported đŸ˜±.

I also plan on helping contribute to the Drupal Rector project so that it is also ready for Drupal 9 and PHP 8. Rector automates code fixes and was also an integral piece of the Drupal 8 to Drupal 9 upgrade lifecycle. It currently does not support Drupal 9, needs updating for the latest version of Rector and probably PHP 8 support.

I am also working on two ideas that would provide services that agencies and developers could subscribe to help fund and focus development time on these tools beyond sponsorships (which can be hard for organizations to justify to their financial and executive teams.)