Practical guide
How to adopt PHPStan in an existing project
Stop adding new type errors immediately and reduce existing debt measurably in small steps.
In short
Zero errors in one day is not the first goal
PHPStan can analyze a legacy project incrementally. First fix broken bootstrap, unknown symbols, and errors in the area being changed, then lock any remainder in a baseline.
A baseline is not a trash bin. It lets CI block new problems at a strict rule level while the number of legacy exceptions deliberately decreases.
Prepare
Map the project’s real runtime
Analysis must know the same PHP, framework types, and generated code as the application.
- The supported PHP version and current composer.lock running locally and in CI.
- Directories containing your code and tests; vendor and cache are not analyzed directly.
- A framework extension or bootstrapFiles only where symbols are created dynamically.
- An owner for reviewing ignore rules, the baseline, and regular strictness increases.
Steps 1 to 3
Connect analysis without a big bang
Stabilize configuration and signal first, then address the volume of findings.
1. Install and configure one path
- Install the tool as a development dependency and commit its version through composer.lock.
- Set paths to your src and optionally tests, the PHP version, and an explicit rule level in phpstan.neon.dist.
- Run PHPStan in the same container or image as the app. Fix class-not-found, bootstrap, and extension configuration before code findings.
- Add a Composer script so developers and CI use the same command without hidden local arguments.
composer require --dev phpstan/phpstan Official PHPStan Getting Started guide 2. Fix types by value
- Start with findings that can alter runtime: wrong arguments, nullable values, missing methods, unreachable branches, and incomplete match expressions.
- Add native types to inputs, returns, and properties. Use PHPDoc for generics, array shapes, and types PHP cannot express.
- Validate dynamic request, JSON, or database data at the boundary and convert it to a DTO or value object.
- Do not silence a problem with broad mixed or blanket ignores. Use an extension, stub, or narrow identified ignore only for proven safe dynamics.
vendor/bin/phpstan analyse --memory-limit=1G Official PHPStan rule-level documentation 3. Lock legacy debt in a baseline
- Choose the highest practical level. If dozens to hundreds of legacy findings remain, generate a one-off baseline and review it manually.
- Commit and include the baseline, then reject new errors in CI. Do not regenerate the whole file in routine changes.
- Each repaired file removes its matching exception. Track baseline entries as a decreasing metric.
- Raise strictness by module or level; hold new code to the current standard without adding historical debt.
vendor/bin/phpstan analyse --level=7 --generate-baseline Official PHPStan baseline documentation Step 4
Prove that new errors cannot pass
The same command must be reproducible locally and on a clean CI runner.
-
Run analysis twice
Both runs have the same result and configuration does not depend on a locally generated file.
composer phpstan -
Insert an intentional type error
CI rejects it even in a file with baselined legacy findings. Then revert the change.
-
Fix one baseline entry
PHPStan reports an unmatched ignore or you update the baseline narrowly; the exception count falls.
Troubleshooting
Common problems
The first run reports thousands of unknown classes
Fix autoloading, framework extensions, generated stubs, and paths first. Do not baseline a broken configuration.
composer dump-autoload The baseline grows on every pull request
Generation is not a routine CI step. Commit a fixed baseline and require a separate justification for every new exception.
Developers get a different result from CI
Align the PHP image, extensions, composer.lock, configuration path, and working directory. Cache must not change results.
Analysis memory rises quickly
Do not analyze vendor or cache, update extensions, and inspect problematic files; a higher limit is the last step.
Done
Static analysis now prevents new debt.
Configuration is reproducible, CI catches new type errors, and a controlled baseline lets you remove legacy findings gradually.