Learning from open source PHP projects - Part 2 - Static analysis
As to where PHPStan and Psalm differ, I was going to try and explain that, and then as I lack the experience I prefer to share a comment from Ondřej Mirtes, where he gives some insights about the differences between the two systems.
Let's check our 5 open source projects again and look at what they use:
- Flowphp uses PHPStan
- psl uses Mago
- BetterReflection uses Psalm and PHPStan
- EventSauce uses PHPStan
- Tempest Framework uses PHPStan
So, in our very little group, we can see that PHPStan is almost everywhere. But we also see that Psalm is not out of the radar yet.
PHPStan
Let's compare the PHPStan configurations first. The strictness level can go from 0 (very limited checks) to 9 for PHPStan v1, and up to 10 for PHPStan v2 (highest amounts of checks). If you play videogames, it's a bit like choosing your difficulty level. But the more difficult it is, the more confident you will be in your code. At the time of writing, here are the levels for each projects:
- EventSauce: PHPStan v1 - Level 9
- Flow: PHPStan v2 - Level 8
- BetterReflection: PHPStan v2 - Level 6
- Tempest: PHPStan v2 - Level 5
My initial thought was that probably the oldest projects in the list would use lowest levels of checks, but that is not necessarily the case. EventSauce for example was started in 2017 and has the highest level. While Tempest is a relatively new project and uses level 5. So this might indicate also that there might be some preferences from the developing team to keep some flexibility in the pace of development.
Fun fact: in the comment from PHPStan's creator I linked above, he shares that (at least back in 2023) the PHPStan source code itself "uses just level 8 and strict-rules".
Well, let's see what we can learn by looking at those configuration files.
Tempest's configuration has an interesting feature I didn't know about, that comes from a package: disallowedFunctionCalls. Within Tempest, they use it to prohibit usage of methods such as exec(), eval(), phpinfo(). Quite useful especially when you receive code from people who might be doing mischievous things with your project. On top of this dd(), dump() and var_dump() are also prohibited. Quite the time-saver, has I cannot recall the amount of time I had to ask remains of variable dumps to be removed from a pull request. Better let PHPStan prohibit that for us. Definitely something I'll reuse in other projects.
Another interesting feature used by Tempest is the phpat package. It provides an easy to use API to define authorized relationship between classes in the project. Readers being familiar with architecture definitions of a project know all too well that as soon as they turn their eyes, other developers will happily break the rules, leaving the project in architecture limbo in no time. This helps teams to stay true to their architectural decisions.
BetterReflection is using a package for PHPStan and PHPUnit called phpstan-phpunit. It allows PHPStan to interpret mock objects created by PHPUnit as an intersection of the object that is mocked and the PHPUnit_Framework_MockObject_MockObject class.
All projects have exclusion filters, so there are always a few cases where you want these warnings to be silenced. Most notably the test classes seems to be muted a lot.
Psalm
Psalm also has error levels, and it's in reverse order compared to PHPStan: level 1 is the strictest, while level 8 is the least strict. Both BetterReflection and psl use level 1.
Just like PHPStan, Psalm allows the use of a baseline, so it can easily be integrated into an existing project.
The configuration files of both projects do not learn us much besides little tweaks linked to the particularities of the codebase.
Static analysis is a deep subject, and deserves many posts dedicated to it. Here I was especially interested to see what tools are being used and how.
Next up: testing best practices in PHP open source projects.