Posted by Barney Laurance on Jun 07, 2019

Psalm for PHP: Hard to pronounce, easier to type

I remember a colleague in a previous job repeating the mantra “program to interfaces, not implementations”.

This sounded like good advice, but it felt a little abstract to me, since in PHP variables do not have types. The values of the variables have types, but the type of an object in memory is never an interface — it’s always a class.

I thought that while I could try to keep an interface in mind while coding, the only real test was whether my code works when I run it, and at that point PHP will allow me to call any function available on the class. It won’t know what interface I had in my head while I was programming.

The understanding only came when I switched from Vim, which I was using without any plugins, to PHPStorm. Once you’re using PHPStorm variables do have types, not in the PHP Engine but in PHPStorm’s understanding of the file. And so now a variable can have an interface type, and PHPStorm would complain loudly if I tried to call a function that doesn’t exist on the interface. In other words I was using static analysis in PHP for the first time, and was exposed to static types, in addition to PHP’s dynamic types.

But even with PhpStorm there were still a few times when I’d make a spelling mistake in a variable, not notice the complaint from PhpStorm and find the message ‘Fatal error: Call to a member function on a non-object’ some days later in server logs, or make some other mistake that could have been checked statically and not find out about until later.

So while I think an IDE that does static analysis is extremely valuable, I don’t think it’s enough. There are several stand-alone static analysis tools available for PHP, and at MyBuilder we’ve been using Psalm, from our IAC sister company Vimeo on our code for a new project started this year, as well as our internal Money library which the new project depends on.

Psalm was created by Matthew Brown and colleagues at Vimeo to help them work on their existing large codebase. Whenever you introduce a new quality check tool to an existing codebase it’s likely to find many things to complain about. Since an empty codebase has no bugs1, a new project was a good opportunity to introduce Psalm.

As a team we expect every pull request to have a passing Jenkins build before we merge it, and Psalm will make sure we don’t get a passing build if it finds any errors. Personally I find it makes me a lot more comfortable to know that there are many types of issue that I won’t accidentally add to our codebase.

Of course static analysis is no substitute for code review or testing, but I think it complements both well. There’s little point reviewing code that doesn’t even pass unit tests, and I’d suggest there’s similarly little point reviewing, or running tests on code that the static analyszer has already found a problem with. Once you know the tests are passing, you don’t have to question that while reviewing, and instead you can expend the effort on more interesting questions, like whether the changes to the code are useful and make it easy to understand and change in future.

Similarly once you know static analysis has passed, and you learn what sort of things it can check, you don’t have to question whether the types are accurate, and instead you should be able to find more useful questions to ask.

Psalm will read types from docblocs as well as from the normal PHP type declarations, and in docblocks it understands several types of types that PHP itself doesn’t (yet) understand, including unions, generics, object-like arrays and others. As we’re starting to use Typescript on the front end, having a more sophisticated type system in PHP too is very welcome.

As I get used to Psalm’s type system, I find myself more and more willing to rely on it and let it substitute for some checks at runtime — why check for something that static analysis says won’t happen? There’s a risk of lock-in here, since once a codebase starts to rely on static analysis it becomes dangerous to ever change it and not run the analysis, but I think it’s sometimes worth accepting this risk with Psalm.

For example, we have a class that depends on Symfony’s UserProviderInterface, and another class that implements it. Since the dockblock for UserProviderInterface#loadUserByUsername says that it returns a UserInterface object, I was happy to delete a check for a null return value in our code, confident that Psalm will not allow us to return null from our implementation.

Psalm is developing fast, and I think it will soon be a lot more widely known in the PHP community, in large part due to the work Marco Pivetta aka Ocramius has been doing to promote it recently. Doctrine Collections were recently annotated with generic type information for Psalm, Ocramius has released a tool for library authors to use to run Psalm automatically on installation in projects and stop people using them wrong, and Sebastian Bergmann released a version of PHPUnit with annotations that make Psalm more useful. I’ve also added generic type annotations to jms/serializer, and contributed a few improvements to Psalm.

So I’d strongly recommend adding a static analysis tool to your build pipeline, and while I haven’t tried the alternatives Psalm seems to be a good choice. You can test it out in your browser, and adding it to your build might be as simple as:

$ composer require --dev vimeo/psalm
$ ./vendor/bin/psalm --init
$ echo "./vendor/bin/psalm" >> test.sh

  1. Unless you consider a missing ‘must-have’ feature to be a bug, in which case you might consider writing a new program to be the same as debugging an empty directory. 

Jobs at MyBuilder

We need an experienced software engineer who loves their craft and wants to share their hard-earned knowledge.

View vacancies
comments powered by Disqus