X
Popular Searches

How to Statically Analyze PHP Projects with PHPStan

PHPStan logo

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 analyzing source files targeting older versions of PHP.

It’s recommended you use Composer to add PHPStan as a dependency:

composer require --dev phpstan/phpstan
Advertisement

The PHPStan binary will be added to your project at vendor/bin/phpstan. You can now use it to analyze 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.

A successful PHPStan run

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/possibly 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 return statement within functions).

Rules are sorted into 9 different “levels”, labeled 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.

A failed PHPStan run

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.

Advertisement

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.

Configuring PHPStan

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:

vendor/bin/phpstan analyse

To override the configuration file which is used, pass the --configuration flag:

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
Advertisement

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 parameters section.

Ignoring Errors

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.

Optional Rules

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 instanceof which will always evaluate to true.
  • checkAlwaysTrueStrictComparison – Flags when an expression using === or !== will always evaluate to true.
  • checkFunctionNameCase – Ensures the casing of function names matches their definition when called throughout the codebase.
  • polluteScopeWithLoopInitialAssignments – When set to false (it’s true by default), variables declared in initial loop statements (e.g. $i in $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.
Advertisement

Complete details on these optional settings – and many more – can be found within PHPStan’s configuration reference.

Conclusion

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.

James Walker James Walker
James Walker is a CloudSavvy IT contributor. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows with DevOps, CI/CD, Docker, and Kubernetes. Read Full Bio »

The above article may contain affiliate links, which help support CloudSavvy IT.