PHPStan with Doctrine

Ondřej Popelka
3 min readDec 9, 2020

Recently PHPStorm 2020.3 was released with first class PHPStan support. Eager to try it I enabled it for a current project. The project is a bit of a pickle because it was put on hold a year ago. When resumed we had to start with rewriting half of the code and major refactoring. In the meantime I also boldly updated all the dependencies irrespective of any BC breaks.

This is how it looked:

212 errors — pretty bad for an application which was passing the max PHPStan level a year ago.

I’m fine

The Setup

We’re using PHPStan from within composer.json like so:

... snip ...
"scripts": {
"tests": [
"php bin/phpunit --coverage-clover build/logs/clover.xml"
"phpstan": "phpstan analyse --no-progress --level=max src tests -c phpstan.neon",
"phpcs": "phpcs --extensions=php src tests",
"app-init": "php bin/console app:init",
"build": [
"ci": [
"@composer validate --no-check-all --strict",

This is a useful concept, because all the steps are executed as simple composer ci in our build pipeline. However it also means the analyse parameters are unavailable to PHPStorm so I had to change that to "phpstan": "phpstan analyse — no-progress -c phpstan.neon" and move the analyse configuration to phpstan.neon :

level: max
- src
- tests

Now PHPStorm recognizes PHPStan configuration and automatically configures the analysis. Great!


Now I can get to list fixing the errors. The most prominent ones being:

  • Doctrine\DBAL\Driver\ResultStatement::fetchAllAssociative()
  • Keboola\JobQueueInternalClient\Client::method()
  • Keboola\JobQueueInternalClient\Client::expects()

So the first step was to add and update PHPStan extensions:

"phpstan/phpstan-doctrine": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",

Which didn’t really help much. After a while of fiddling I realized that some deprecations happened in latest version 2 of Doctrine DBAL and that the type hints are a bit out of sync. I’m still stuck with DBAL 2 because of Doctrine migrations. Luckily this was mostly solved by updating the actual code from:

$failedJobs = $this->dbConnection->executeQuery(
'SELECT * FROM job_failures ORDER BY failure_number DESC'


$failedJobs = $this->dbConnection->fetchAllAssociative(
'SELECT * FROM job_failures ORDER BY failure_number DESC'

Yeah, the best thing you can do about deprecated code is to actually fix it :).


Fixing the errors of type Keboola\JobQueueInternalClient\Client::method() was piece of cake. This is caused by mock properties which were type hinted all over the code. All I had to do was add intersection types — therefore change:

/** @var StorageClientFactory */
protected $storageClientFactory;


/** @var StorageClientFactory&MockObject */
protected StorageClientFactory $storageClientFactory;

Fixing Doctrine and PHPStan solved the most problems, there were a few other changes like deprecated autoload_files to bootstrapFiles. But I was mostly left with the usual PHPStan stuff. The last thing that I was left with remained:

Unable to resolve the template type T in call to method static method Doctrine\DBAL\DriverManager::getConnection()

This took me a while to find, but turned out to be again related to a “bug” in Doctrine itself. And that’s pretty much it. So the final phpstan.neon config turned out to be:

level: max
checkMissingIterableValueType: false
- src
- tests
- bin/.phpunit/phpunit-6.5-0/vendor/autoload.php
# See
- '#Call to an undefined method Doctrine\\DBAL\\Driver\\ResultStatement\:\:fetchAllAssociative\(\)#'
# See
- '#Unable to resolve the template type T in call to method static method Doctrine\\DBAL\\DriverManager::getConnection\(\)#'
# Unfinished stuff
- tests/Daemon/KillJobTest.php
- tests/Daemon/BootTest.php
- vendor/phpstan/phpstan-doctrine/extension.neon
- vendor/phpstan/phpstan-phpunit/extension.neon

I could also replace the includes with the PHPStan extension installer but I like to keep things explicit.