PHPStan: Static Analysis for PHP – Catch Bugs Before Runtime
Learn how PHPStan catches bugs without running code.
20+ years shipping production PHP systems at scale. Everything here is grounded in real deployments.
- ✓Basic knowledge of PHP syntax and types
- ✓Familiarity with Composer
- ✓A PHP project to analyse (optional but recommended)
- PHPStan analyzes your PHP code without executing it, detecting type mismatches, missing return types, and undefined variables.
- It integrates with your CI pipeline to prevent bugs from reaching production.
- Levels 0-9 allow gradual adoption; start with level 0 and increase as your codebase improves.
- Use phpstan.neon config to customize rules, exclude paths, and define stub files.
- Common mistakes: ignoring baseline, not updating after refactoring, and misconfiguring autoload.
Imagine you're building a house. PHPStan is like a building inspector who checks the blueprints before any construction starts. They spot missing support beams (missing return types) or wrong materials (type mismatches) so you fix them on paper, not after the walls are up. This saves time, money, and prevents collapses in production.
You've just deployed a new feature. Hours later, your monitoring alerts spike with a 500 error. The culprit? A function expected an array but got null. This is the classic 'undefined array key' or 'call to a member function on null' that haunts PHP developers. While PHP's dynamic nature is flexible, it's also a breeding ground for runtime surprises.
Enter PHPStan: a static analysis tool that inspects your code without executing it. It's like having a senior developer review every line of your codebase, catching type errors, missing return types, and dead code before you even run a test. PHPStan operates on levels 0 to 9, where 0 is the most lenient and 9 is the strictest. You can start at a low level and gradually increase as your code improves.
In this tutorial, you'll learn how to install PHPStan, configure it for your project, and integrate it into your development workflow. We'll cover real-world scenarios like handling legacy code with baselines, custom rules, and debugging production incidents. By the end, you'll have a robust static analysis setup that prevents bugs from reaching users.
PHPStan is not just a linter; it's a powerful tool that understands PHP's type system, including generics (via PHPDoc), union types, and even frameworks like Laravel. It can detect issues like accessing undefined array keys, calling methods on nullable objects, and mismatched type hints. This proactive approach saves countless hours of debugging and improves code quality across your team.
Installing PHPStan
PHPStan is installed via Composer. You can install it globally or per-project. For most projects, per-project installation is recommended to avoid version conflicts.
Run the following command in your project root:
``bash composer require --dev phpstan/phpstan ``
This installs the latest stable version. If you need bleeding-edge features, you can use phpstan/phpstan:dev-master, but be cautious.
After installation, verify it works:
``bash vendor/bin/phpstan --version ``
You should see output like PHPStan - PHP Static Analysis Tool 1.12.x. Now you're ready to analyse your code.
Running Your First Analysis
Let's run PHPStan on a simple PHP file. Create a file test.php with a deliberate type error:
```php <?php
function add(int $a, int $b): int { return $a + $b; }
echo add('hello', 5); // Error: string passed instead of int ```
Now run PHPStan:
``bash vendor/bin/phpstan analyse test.php --level=0 ``
You'll see an error like:
`` ------ ------------------------------------------------------- Line test.php ------ ------------------------------------------------------- 7 Parameter #1 $a of function add expects int, string given. ------ ------------------------------------------------------- ``
PHPStan caught the type mismatch without executing the code. The --level flag sets the strictness. Level 0 is the most basic; level 9 is the strictest. Start low and increase gradually.
--level to set strictness. Start at level 0 and increase as your code improves.Configuring PHPStan with phpstan.neon
For real projects, you'll want a configuration file. Create phpstan.neon in your project root:
``neon parameters: level: 6 paths: - src excludePaths: - src/Legacy checkMissingIterableValueType: true checkGenericClassInNonGenericObjectType: true ``
Then run:
``bash vendor/bin/phpstan analyse ``
PHPStan automatically reads phpstan.neon. You can also specify a custom config with -c.
level: analysis strictness (0-9)paths: directories to analyseexcludePaths: directories to skip (e.g., legacy code, vendor)checkMissingIterableValueType: ensures arrays have type hintscheckGenericClassInNonGenericObjectType: checks for proper generic usage
You can also define stub files for classes that PHPStan can't autoload (e.g., C extensions).
Working with Baselines
When introducing PHPStan to an existing codebase, you'll likely have hundreds of errors. Instead of fixing them all at once, generate a baseline:
``bash vendor/bin/phpstan analyse --generate-baseline ``
This creates a phpstan-baseline.neon file listing all current errors. PHPStan will ignore them in future runs, so you can focus on new errors. Over time, fix baseline errors and regenerate the baseline to keep it clean.
To use the baseline, include it in your config:
``neon includes: - phpstan-baseline.neon ``
Now, when you run PHPStan, only new errors will be reported. This is essential for gradual adoption.
--generate-baseline only on purpose.Custom Rules and Extensions
PHPStan supports custom rules via extensions. You can create your own rule by implementing the PHPStan\Rules\Rule interface. For example, a rule that forbids using var_dump in production code:
```php <?php
use PhpParser\Node; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder;
class NoVarDumpRule implements Rule { public function getNodeType(): string { return Node\Expr\FuncCall::class; }
public function processNode(Node $node, Scope $scope): array { if ($node->name instanceof Node\Name && $node->name->toString() === 'var_dump') { return [ RuleErrorBuilder::message('Do not use var_dump in production code.') ->line($node->getLine()) ->build(), ]; } return []; } } ```
Register the rule in your config:
``neon services: - class: NoVarDumpRule tags: - phpstan.rules.rule ``
Now PHPStan will flag any var_dump calls.
Integrating PHPStan into CI/CD
To prevent bugs from reaching production, run PHPStan in your CI pipeline. Here's an example for GitHub Actions:
```yaml name: PHPStan
on: [push, pull_request]
jobs: phpstan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: '8.2' tools: composer - run: composer install --no-progress - run: vendor/bin/phpstan analyse --no-progress --error-format=github ```
This will fail the build if PHPStan finds errors. Use --error-format=github for annotations in pull requests.
For GitLab CI:
``yaml phpstan: stage: test script: - composer install - vendor/bin/phpstan analyse --no-progress ``
Always use --no-progress in CI to avoid log clutter.
--error-format=github for PR annotations.The Null That Broke the Checkout
$response->isSuccess() without checking for null.- Always check for null when calling external services, even if documentation says otherwise.
- Use PHPStan's strict rules to flag potentially null values before runtime.
- Implement a baseline for legacy code and gradually fix issues.
- Integrate PHPStan into CI to catch regressions automatically.
- Treat static analysis warnings as potential production bugs.
use statements and verify autoloading. Add a stub file if the method is from a dynamic source.var_dump or gettype to confirm. The issue might be a type mismatch from a previous function.assert() or instanceof to narrow the type. Alternatively, use PHPDoc @var to specify the expected type.phpstan analyse --level=6 src/phpstan analyse --generate-baseline| File | Command / Code | Purpose |
|---|---|---|
| install.php | echo "PHPStan installed successfully!"; | Installing PHPStan |
| test.php | function add(int $a, int $b): int { | Running Your First Analysis |
| phpstan.neon | parameters: | Configuring PHPStan with phpstan.neon |
| phpstan-baseline.neon | parameters: | Working with Baselines |
| NoVarDumpRule.php | use PhpParser\Node; | Custom Rules and Extensions |
| .github | name: PHPStan | Integrating PHPStan into CI/CD |
Key takeaways
Common mistakes to avoid
3 patternsNot using a baseline when adopting PHPStan on a large codebase.
Setting the level too high too early.
Ignoring PHPStan warnings because 'it works in production'.
Interview Questions on This Topic
What is static analysis and how does PHPStan help?
Frequently Asked Questions
20+ years shipping production PHP systems at scale. Everything here is grounded in real deployments.
That's Advanced PHP. Mark it forged?
3 min read · try the examples if you haven't