A smaller development team recently decided to enforce stricter Psalm rules on their project. After some groaning, the team found that these stricter rules saved them time reviewing merge requests and caused them fewer headaches solving bugs. After singing the praises we wanted to expand these rules to the rest of the codebase, but then we ran into a problem. Psalm does not have an easy way to share rules among multiple repositories.
So we did what any self-respecting tech company would do. We build our own solution.
When starting this project we had the following goals:
A single source of Truth
Having only a single source copy of Psalm rules allows us to easily add or remove rules which are then copied to all the different projects.
Having many different repositories, we wanted to make the installation as easy as possible. Spending even five minutes per repository quickly adds up.
We did not want to disrupt our developers’ workflow with the solution. The ideal solution would solve the problem without anyone noticing.
Using these goals we looked around for possible solutions. The idea of switching to another code analysis tool was not entertained for too long because that would cause too much disruption. After that, we noticed that Psalm supports XMLInclude. The possibility to inject a common part of the rules was tempting. But after closer inspection, we were disappointed to find that this would not give us the flexibility we wanted. However, thinking about a way of hosting this file set us on a path to the solution.
We decided to build a custom Composer Plugin that would generate the desired psalm.xml based on a template we would provide.
The solution had many advantages.
- All projects that would use Psalm already use Composer
- We can maintain the rules by creating new versions of the plugin
- Installing a composer plugin is quick and easy to do
- A plugin is powerful enough for us to do what we want
- By only changing the contents psalm.xml file, the workflow does not change
The solution checked all of our boxes.
From this point, the implementation was straightforward. I’ll expand on some of the nifty features we added.
Filling in the template
The plugin looks at the files in the repository and will fill the template accordingly: it configures the baseline and psalm-stubs file if they are present. We did this using the standard SimpleXML extension for PHP.
Verification stage in CI/CD Pipeline
After deciding to use more strict psalm rules and creating this plugin, we did not want anyone to be able to cut corners. Letting the standards slip anywhere would make this entire process moot. We needed a referee to keep us to these rules.
We added a new stage to our CI/CD pipeline that would check the committed psalm.xml with one that the plugin would create. If any unexpected changes are detected, the build fails.
Running on install and update of dependency
Developers should always be working with the most up-to-date psalm rules. Not wanting to disrupt the workflow too much, we gave the plugin this responsibility. We configured the plugin’s event listener to generate a new `psalm.xml` whenever it is installed or updated.
We discovered a small oversight after implementing this feature. As the CI/CD pipeline builds the project, the plugin is installed. This causes it to overwrite the committed `psalm.xml`, making it impossible to detect any changes.
To solve this, we added a simple check to see whether the code is running in our CI environment. When that is the case, the event should be ignored.
This solution satisfies all our goals: simple to maintain, easy to install, and fully capable of running in the background. Not only will this plugin provide the rules, but enforce them as well. Hopefully saving us time and headaches in the process.
Now let’s see if this will actually lead to better code.