This is a massive release that has been in the works for the past six months. We’ve managed to churn out minor releases even during the development of this new major version. This continuous workflow was enabled by feature toggles — people were able to try out the new features even while using the stable version by opting in and give feedback.

But I can’t wait for everyone to get their hands on this new version because it’s so much better. And it’s designed so that everyone is able to upgrade to it immediately without breaking a sweat.

If you’re not familiar with PHPStan, it’s a static analyser for PHP focused on finding bugs in your code. It catches whole classes of bugs even before you run your app at all. See the introductory article if you want to know more about the basics!

PHPStan 0.12 is brought to you by Gábor Hojtsy (@gaborhojtsy):

PHPStan has already been tremendous help in our preparation for Drupal 9 across the whole Drupal ecosystem, as a backbone of drupal-check, Upgrade Status and drupalci deprecation testing. It allows us to find common patterns in deprecated API use, make critical tooling decisions and rally the community. Thank you!

So what’s new in PHPStan 0.12?

Generics

How this feature came into fruition in PHPStan? In mid-May this year, Arnaud Le Blanc sent me an email out of the blue that he’d like to help me out with implementing generics. This is exactly the kind of letter you print, frame and hang on the wall. Because what followed was the best collaboration I’ve had with anybody in a long time. We’ve bounced back and forth with over ~50 pull requests and hundreds of commits, gave each other feedback, tested and polished the code. And while there are still some ideas we have that could be implemented in the future, we’re quite happy with this and let others make use of it in 0.12.0.

Part of the feature (rules checking for correct usage of generic types) was implemented during the EU-FOSSA hackaton in Brussels at the beginning of October. But more on that a bit later.

New level

Levels in PHPStan serve as a vehicle for progressive introduction of the tool into the codebase and the workflow of the team. Instead of getting all the errors reported that PHPStan is capable to find, it only reports the most severe ones on level 0, lets developers fix them, and only then progress to higher levels which are more strict and fixing them actually requires to improve the overall quality of the code.

Brief overview of levels on 0.11.x and before is as follows:

0: basic checks, unknown classes, unknown functions, unknown methods called on $this, wrong number of arguments passed to those methods and functions, always undefined variables1: possibly undefined variables, unknown magic methods and properties on classes with __call and __get2: unknown methods checked on all expressions (not just $this), validating PHPDocs3: return types, types assigned to properties4: basic dead code checking - always false instanceof and other type checks, dead else branches, unreachable code after return; etc.5: checking types of arguments passed to methods and functions6: report partially wrong union types - if you call a method that only exists on some types in a union type, level 6 starts to report that; other possibly incorrect situations7: report calling methods and accessing properties on nullable types

I believe that all projects are able to achieve level 5 given some reasonable amount of time. But levels 6 and 7 are much easier to achieve on greenfield projects where these checks are enforced since the beginning. They influence how you design your objects a lot.

What I wanted to do for PHPStan 0.12 was to report any missing typehints in the analysed code. If PHPStan doesn’t know type of a variable, it cannot check for any errors on it. Besides completely missing typehints, it also checks for missing iterable value typehints in arrays and other iterables.

Adding that as a new level as level 8 would be an easy way out but wouldn’t reflect the difficulty to achieve it. It’s easier to go through your code and add missing typehints than rewrite the whole thing to satisfy levels 6 and 7.

So the new levels design looks like this:

0: basic checks
1: possibly undefined variable, ...
2: unknown methods checked on all expressions
3: return types, types assigned to properties
4: basic dead code checking
5: checking types of arguments passed to methods and functions
6: check for missing typehints
7: report partially wrong union types
8: report calling methods and accessing properties on nullable types

So who has previously been on levels 5+ should now be able to jump at least one level higher if they add typehints to all their code.

PHAR distribution by default

With PHPStan 0.12, phpstan-shim is no longer needed and will not be updated anymore. All users should move to phpstan/phpstan. PHPStan extensions continue to work as expected.

Development of PHPStan including pull requests is now happening over at phpstan/phpstan-src.

Integer range type

function foo(int $i)
{
if ($i > 2 && $i < 5) {
if ($i === 5) { } // error: this is always false
}
if ($i >= 2 && $i < 3) {
// PHPStan knows that $i is 2
}
}

With this feature, PHPStan again knows more about the code and is able to point out always true or false conditions.

Integer range type was developed by Jan Tvrdík also at the EU-FOSSA hackaton in Brussels.

Image for post
Image for post
Lukáš Unger (@lookyman_), Ondřej Mirtes (@OndrejMirtes), Jan Tvrdík (@jantvrdik)

We were honored to be invited to the event. It was a great opportunity to achieve two full days-worth of open-source work what would otherwise take weeks of constant interruptions and back-and-forths. It definitely sped up the development of PHPStan 0.12 and helped bringing it to the final release this year.

Doctrine extension: compare property type against @ORM\Column definition

/**
* @ORM\Column(type="string")
* @var string|null
*/
private $text; // "Property $text type mapping mismatch: property can contain string|null but database expects string."
/**
* @ORM\Column(type="string", nullable=true)
*/
private string $title; // "Property $one type mapping mismatch: database can contain string|null but property expects string."

You can even describe your own types for this purpose. See the README for more details.

“class-string” pseudotype

This type also pairs nicely with generics. You can use class-string<T> in the generic signature, or you can use class-string<Foo> to only accept valid class names that are subtypes of Foo.

Function calls with no effect

Examples of such functions are count or sprintf and plenty of methods on DateTimeImmutable.

$date = new \DateTimeImmutable();// "Call to method DateTimeImmutable::setTime() on a separate line has no effect."
$date->setTime(0, 0, 0);
// OK
$midnight = $date->setTime(0, 0, 0);

Baseline to the rescue!

I encourage everyone to upgrade to get the latest checks. If you’re worried how many new errors you will have to fix, I have good news for you. Thanks to the baseline feature released recently, you can get to a green build immediately and fix those errors later when you have time. But the new and changed code you write will be held to a higher standard set by the new version.

Do you like PHPStan and use it every day? Consider supporting further development of PHPStan on GitHub Sponsors. I’d really appreciate it!

If you’re a PHP developer, give PHPStan a shot. If you’re interested in various insights about software development, follow me on Twitter.

Written by

Author of PHPStan, static analysis tool for finding bugs in code without running it.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store