PHPStan - PHP static analysis tool
The PHPStan project describes the tool as follows:
PHPStan focuses on finding errors in your code without actually running it. It catches whole classes of bugs even before you write tests for the code. It moves PHP closer to compiled languages in the sense that the correctness of each line of the code can be checked before you run the actual line.
PHPStan checks every code path, including the paths your tests never reach. It reports problems such as a call to a method that doesn't exist, an argument of the wrong type or a missing return type. Vortex comes with a pre-configured PHPStan ruleset for Drupal projects, so the analysis runs on every build without setup.
This page is for developers who write PHP in a Vortex project. It shows how to run PHPStan, what the shipped configuration analyzes, how to ignore an error you can't fix and how to onboard an existing codebase with a baseline. For PHPStan itself, see the PHPStan user guide.
Usage
The Ahoy command runs PHPCS, PHPStan and Rector in that order. The Docker Compose command runs PHPStan alone. To analyze the code, run 1 of the following commands:
- Ahoy
- Docker Compose
ahoy lint-be
docker compose exec cli vendor/bin/phpstan
Confirm that the analysis passed: PHPStan prints [OK] No errors.
PHPStan only reports errors and leaves your code unchanged. Rector fixes some of the same problems automatically, such as missing type declarations.
➡️ See Rector
Configuration
See the PHPStan configuration reference.
All global configuration lives in the
phpstan.neon
file.
The analysis runs at level 7,
with the Drupal-specific rules from
mglaman/phpstan-drupal, which
phpstan/extension-installer loads automatically.
The phpVersion value in phpstan.neon, set to 80427, makes PHPStan
treat the code as PHP 8.4.27, the version the composer.json key
config.platform.php pins. PHPStan doesn't read that key, so update both
when you move to a new PHP version.
treatPhpDocTypesAsCertain is set to false because the types in doc
comments go out of date, so PHPStan relies on native type declarations.
The ignoreErrors list silences 2 errors that Drupal code can't avoid:
- Missing iterable value types in custom modules, custom themes and
tests/phpunit - Possibly undefined variables in the included settings files
PHPStan analyzes the following paths, which the paths list in
phpstan.neon sets:
web/modules/customweb/themes/customweb/sites/default/settings.phpweb/sites/default/includestestsscripts
To add or remove targets, edit the paths and excludePaths lists in
phpstan.neon:
parameters:
paths:
- path/to/dir_or_file
excludePaths:
- path/to/exclude/all_dir_files/*
- path/to/exclude/a_file.php
Ignore violations
To ignore an error across the project, add it to ignoreErrors in the
phpstan.neon
file:
parameters:
ignoreErrors:
- # Comment about why this rule is excluded.
# 'message' is a regular expression with `#` as begin and end delimiters.
message: '#.*no value type specified in iterable type array.#'
paths:
- path/to/exclude/all_dir_files/*
- path/to/exclude/a_file.php
PHPStan's inline comments work 1 line at a time. To ignore errors in whole files, use the configuration:
- To leave files out of the analysis, add them to
excludePaths. - To silence 1 error in some files only, add
pathsto itsignoreErrorsentry, as in the preceding example.
The PHPStan issue Ignoring code blocks using PHPDocs explains why PHPStan has no block-level ignore.
To ignore 1 error on the next line, add a @phpstan-ignore comment with
the error identifier that PHPStan prints with the error:
// @phpstan-ignore variable.undefined
echo $title;
To ignore an error on the same line instead, add the comment at the end of that line.
To ignore every error on the next line, whatever its identifier, add this comment on the line before it:
// @phpstan-ignore-next-line
$a = 1;
Baseline for an existing codebase
When you install Vortex into an existing codebase, the first analysis
reports every error that the code already has. A baseline records those
errors in phpstan-baseline.neon, and PHPStan then reports only the errors
that the baseline doesn't record. Every new error fails the analysis, and
you fix the recorded errors over time.
Use the baseline only to onboard existing code. To ignore a new error, see Ignore violations.
phpstan.neon ships with the include of phpstan-baseline.neon commented
out, so PHPStan uses the baseline only after you uncomment it.
Generate the baseline
To record the current errors in phpstan-baseline.neon, run 1 of the
following commands:
- Ahoy
- Docker Compose
ahoy lint-be-baseline
docker compose exec cli vendor/bin/phpstan \
--generate-baseline=phpstan-baseline.neon \
--allow-empty-baseline
PHPStan prints [OK] Baseline generated with and the number of errors it
recorded.
To load the baseline, uncomment the includes lines in phpstan.neon:
includes:
- phpstan-baseline.neon
Commit phpstan-baseline.neon and phpstan.neon. CI reads the committed
baseline and never generates one, so a new error can't enter the baseline
unnoticed.
Run ahoy cli vendor/bin/phpstan and confirm that PHPStan prints
[OK] No errors. ahoy lint-be runs PHPCS first and stops on its errors,
so it can end before PHPStan runs.
Shrink the baseline
After you fix a recorded error, PHPStan fails with Ignored error pattern
and was not matched in reported errors, because the baseline still
records the error. To remove the fixed errors from the baseline, run
ahoy lint-be-baseline again and commit the updated file.
Regenerate the baseline only after you fix errors. A regeneration records every error in the code, including a new one you meant to fix.
When the baseline records no errors, delete phpstan-baseline.neon and
comment out the includes lines in phpstan.neon again. PHPStan stops
with an error when an included file is missing.
Continuous integration
PHPStan runs in the lint job of the continuous integration
pipeline and fails the build on
violations.
Ignore failures
To let the build pass anyway, set the VORTEX_CI_PHPSTAN_IGNORE_FAILURE CI
variable to 1. PHPStan still runs and prints every error in the job log,
so you can see what you're letting through.