What it currently checks for?

Existence of classes used in instanceof, catch, typehints and other language constructs. PHP does not check this and just stays instead, rendering the surrounded code unused.

Existence and accessibility of called methods and functions. It also checks the number of passed arguments.

Whether a method returns the same type it declares to return.

Existence and visibility of accessed properties. It will also point out if a different type from the declared one is assigned to the property.

Correct number of parameters passed to sprintf/printf calls based on format strings.

Existence of variables while respecting scopes of branches and loops.

Useless casting like (string) ‘foo’ and strict comparisons (=== and !==) with different types as operands which always result in false.

The list is growing with every release. But it’s not the only thing that makes PHPStan useful.

PHPStan is fast…

It manages to check the whole codebase in a single pass. It doesn’t need to go through the code multiple times. And it only needs to go through the code you wish to analyze, e.g. the code you written. It doesn’t need to parse and analyze 3rd party dependencies. Instead, it uses reflection to find out useful information about somebody else’s code your codebase uses.

PHPStan is able to check our codebase (6000 files, 600k LOCs) in around a minute. And it checks itself under a second.

…and extensible

Even with current static typing practices, a developer can sometimes justify using dynamic features of PHP like __get, __set and __call magic methods. They allow to define new properties and methods dynamically in runtime. Normally, static analysis would complain about accessing undefined properties and methods, but there’s a mechanism for telling the engine the rules how exactly the new properties and methods are created.

This is made possible thanks to a custom abstraction over native PHP reflection which allows the user to define extensions. For more details, check the Class reflection extensions section in the documentation.

Some methods’ return type depends on its arguments. It can depend on what class name you pass to it or it may return object of the same class as the object you passed. This is what Dynamic return type extensions are for.

And last but not least, if you come up with a new check PHPStan could perform, you can write and use it yourself. It’s possible to come up with framework-specific rules like checking if entities and fields referenced in a DQL query exist or if all generated links in your MVC framework of choice lead to existing controllers.

Choose your level of strictness

Other tools I tried out suffer from the initial experience of trying to integrate them into existing codebases. They spill thousands and thousands of errors which discourage further use.

Instead, I looked back to how I integrated PHPStan into our codebase during its initial development. Its first versions weren’t as capable as the current one, they didn’t find as many errors. But it was ideal from the integration perspective — When I had time, I wrote a new rule, I fixed what it found in our codebase and merged the new version with the fixes into master. We used the new version for a few weeks to find errors it was capable to find and the cycle repeated. This gradual increasing of strictness proved to be really beneficial, so I set out to simulate it even with current capabilities of PHPStan.

By default, PHPStan checks only code it’s sure about — constants, instantiation, methods called on $this, statically called methods, functions and existing classes in various language constructs. By increasing the level (from the default 0 up to the current 4), you also increase the number of assumptions it makes about the code and the number of rules it checks.

You can also create your own rulesets if the built-in levels do not suit your needs.