PHPStan and Psalm
| Install | |
| Simple example | |
| Complex usage | |
| What it found in src/ |
Install
Nothing to install by hand. The first run downloads phpstan.phar and psalm.phar into ~/.cache/aredel-tools/ and executes them under the php:8.2-cli image. Psalm gets a minimal config generated per run — no psalm.xml is added to the repo. Versions on this site: PHPStan 2.2.14, Psalm 6.18.0:
./scripts/lint-static.sh --tool phpstan --files src/dev/php/linters/phpstan_psalm.php ./scripts/lint-static.sh --tool psalm --files src/dev/php/linters/phpstan_psalm.php
Simple example
PHPStan is the default tool — analyse one file for unknown symbols, bad types and dead code:
./scripts/lint-static.sh --files src/ru/qa/.php/BasePage.php
Second opinion from Psalm (missing types, unused code):
./scripts/lint-static.sh --tool psalm --files src/ru/qa/.php/BasePage.php
Complex usage
Both tools at once, strictness ladder, staged-only checks, and a saved report:
./scripts/lint-static.sh --tool phpstan,psalm,phpcs --files src/ru/qa/.php/ ./scripts/lint-static.sh --tool phpstan --level 2 --files src/ru/qa/.php/ ./scripts/lint-static.sh --tool psalm --staged ./scripts/lint-static.sh --tool phpstan --files src/ru/qa/.php/ --report reports/phpstan-qa
Practical loop on legacy code: narrow the scope (--files / --staged), fix new code first, raise --level 0-9 only when the current level is quiet. Reports append .txt when missing and start with a UTC timestamp; the exit code still reflects the tools, not the report.
What it found in src/
Expect findings on legacy files — that is normal, not a reason to fix everything at once:
- PHPStan level 1 flags dynamic properties (Access to an undefined property): page classes assign $this->... without declaring them, which PHP 8.2 deprecates. The new BasePage and ArticlePage classes already declare every property for exactly this reason.
- Cross-file unknowns when files are analysed standalone — pass a whole directory so symbols resolve.
- Psalm additionally wants return types everywhere (MissingReturnType) and reports unused code.
Unlike PHPCS, neither tool auto-fixes: every finding is a human decision. That is why style went first in the src/ cleanup (3533 down to 695 PHPCS violations) and bug-hunting with these two is next.
Article author: Andrei Olegovich
| Development with PHP | |
| PHP linters we use | |
| php -l and lint-php.sh | |
| PHPCS: PHP CodeSniffer | |
| phpcs.xml ruleset file | |
| PHPCBF: fix style automatically | |
| wrap-long-lines.sh | |
| PHPStan and Psalm |