PHPStan is a static analysis system for PHP projects. It finds bugs in your codebase by inspecting the source files. You don’t need to run your code or manually write tests to discover issues!
The term “static analysis” is defined as debugging code without actually executing it. It’s most often used with interpreted languages, such as PHP, as the issues it finds tend to surface at the compilation stage in compiled languages.
Static analysis tends to give the best results when run within a well-structured, strongly-typed codebase. PHPStan’s documentation advises that “modern object-oriented code” stands to benefit the most, as these projects give PHPStan more information to work with.
Adding PHPStan to Your Project
To run PHPStan, you’ll need to have PHP 7.1 or newer. This requirement only applies to the version of PHP used to execute PHPStan itself. The tool is capable of analysing source files targeting older versions of PHP.
It’s recommended you use Composer to add PHPStan as a dependency:
composer require --dev phpstan/phpstan
The PHPStan binary will be added to your project at
vendor/bin/phpstan. You can now use it to analyse your codebase for the first time:
vendor/bin/phpstan analyse src
The command shown above will run PHPStan’s tests against all the source files in your
src directory. Depending on the size of your codebase, this might take a few minutes to complete.
You’ll see a green “[OK] No errors” message if all the tests pass. Otherwise, the list of errors encountered will be displayed. Follow the guidance shown to resolve each error before re-running PHPStan.
Error Types and Levels
PHPStan includes a plethora of checks covering a wide variety of possible codebase issues. Some of the most common ones you’ll encounter include the following:
- Type system issues – Assigning a typed property an invalid value, or passing incorrectly typed parameters to a method. This also includes contract issues, such as a class incorrectly implementing an interface.
- Function invocations – Passing too many, or not enough, parameters when calling a function or method (e.g. 3 instead of 4).
- Unknown classes, methods and functions – Trying to use something which doesn’t exist within the codebase.
- Access to undefined/possiby undefined variables – Trying to use a variable that is not defined in a given scope, or that which may not always have a value but is used in a context where one is assumed.
- Dead code checking – Flagging of useless code, such as boolean comparisons which will always resolve to the same value and code branches which will not never get executed (e.g. code following a
returnstatement within functions).
Rules are sorted into 9 different “levels”, labelled from 0 to 8. The special level
max acts as an alias for the highest possible level. Over time, PHPStan may add additional numerical levels, so using
max ensures you always get the strictest possible checks.
By default, PHPStan executes level 0. This includes only the most fundamental of tests. It’s a good idea to get your codebase passing each level individually before advancing to the next one. Mature projects are likely to run into another set of issues with each new level.
To change the level PHPStan uses, you can pass the
--level command line parameter:
vendor/bin/phpstan analyse src --level 8
In addition to the built-in checks, PHPStan extensions are available to add even more functionality. You can also write your own rules from scratch. This is useful when you’re deprecating functionality that developers should no longer use in any new code. We’ll cover creating custom rules in a future article.
Beyond initial experimentation, using PHPStan’s command-line interface can quickly become tiresome. It’s best to add a configuration file to your project which can then be committed to source control for all your developers to use.
PHPStan uses the Neon configuration file format, which has a syntax very similar to YAML. Create a
phpstan.neon file in your project’s root directory. This file is automatically loaded whenever PHPStan starts, so you can now run the
analyse command with no further arguments:
To override the configuration file which is used, pass the
vendor/bin/phpstan analyse --configuration /phpstan-config.neon
You now need to populate your
phpstan.neon file with some content. A good starting point might look like this:
parameters: level: 0 paths: - src
This basic configuration file should give the same output as the command-line invocation shown earlier. You can add additional directories to scan as new lines in the
paths section. To exclude files and directories, add them to an
excludes_analyse leaf within the same
Occasionally, PHPStan may surface an issue which is unavoidable. If there’s a problem which you cannot immediately address, you may wish to explicitly ignore it in order to allow the tests to pass.
This is particularly important when you want to move up to another level of checks, or you’re using PHPStan in a CI environment where a failed run will stop your build deploying. Even so, don’t take this course of action lightly – you should only choose to ignore a reported error if you’re certain it will be safe to do so.
Once you’ve made the decision, add a new
ignoreErrors section within the
parameters of your configuration file. You have to define the message to match, as a regex, and the paths to apply the exclusion to:
parameters: level: 0 paths: - src ignoreErrors: - message: '/Return type string of method ExampleClass::example() is not covariant(.*).' path: src/ExampleClass.php
You can optionally specify
paths as an array of paths, replacing the single
path key shown above.
PHPStan’s strictness can be adjusted by a series of configuration variables. These allow you to finetune the checks which are made, outside of the levels system described above. Some of these are potentially controversial or unlikely to align with all private style guides, hence they’re off by default.
A few settings which might be worth enabling include:
checkAlwaysTrueInstanceof– Flags uses of
instanceofwhich will always evaluate to
checkAlwaysTrueStrictComparison– Flags when an expression using
!==will always evaluate to
checkFunctionNameCase– Ensures the casing of function names matches their definition when called throughout the codebase.
polluteScopeWithLoopInitialAssignments– When set to
trueby default), variables declared in initial loop statements (e.g.
$for ($i = 1; $i < 10; $i++)) are prevented from being accessed outside the loop’s code block, avoiding possible pollution of the parent scope.
reportStaticMethodSignatures– Enforces full type checking for parameters and return types in static methods when overridden by a child class.
Complete details on these optional settings – and many more – can be found within PHPStan’s configuration reference.
That’s the end of our introduction to PHPStan. It helps you have confidence in your codebase and highlights possible issues before they become a problem in production.
PHPStan is so quick to setup that there’s really no reason not to use it, especially when working with a modern strongly-typed codebase. Don’t be tricked into thinking it can replace manual testing though. PHPStan may boast a large assortment of checks but it cannot identify logical issues and does not understand your project’s business rules. It’s merely another asset in your toolbox when assessing a codebase’s health, serving alongside trusted companions such as unit tests and end-to-end functionality tests.