PHPStan: Advanced Static Analysis for PHP
PHPStan is a static analysis tool for PHP that finds bugs in your code without running it. It performs deep analysis of your codebase to catch errors, enforce type safety, and improve code quality before anything reaches production.
The current major release is PHPStan 2.x, which shipped in late 2024. The analysis levels, configuration format, and core behaviour remain the same — though some parameter names changed and a few deprecated options were removed. If you're upgrading from 1.x, running ./vendor/bin/phpstan will surface any config issues immediately.
Table of Contents
- What is PHPStan?
- Why Use PHPStan?
- Installation
- Configuration Explained
- Analysis Levels
- Common Use Cases
- Running PHPStan
- Best Practices
- Advanced Configuration
What is PHPStan?
PHPStan analyses your PHP code without executing it. It:
- Detects bugs before they reach production
- Enforces type safety through comprehensive type checking
- Validates code logic and identifies unreachable code
- Analyses method calls and property access
- Checks PHPDoc annotations for accuracy
PHPStan understands your code structure and can identify issues that traditional testing often misses, particularly around type contracts and nullability.
Why Use PHPStan?
Early Bug Detection
PHPStan catches errors before they cause runtime failures: undefined variables and methods, type mismatches, logic errors with unreachable code, and missing return statements. Finding these at analysis time is far cheaper than tracking them down in a staging or production environment.
Type Safety Enforcement
PHPStan ensures your code respects type contracts. It validates method signatures, checks parameter and return types, identifies nullable type violations, and enforces strict type declarations. With more PHP codebases adopting strict types, this is increasingly important.
Refactoring Confidence
PHPStan provides a safety net during code changes. When you modify an API or refactor a service, PHPStan will flag every call site that breaks before you run a single test. That feedback loop is genuinely useful on larger codebases.
Installation
Via Composer (Recommended)
# Install as dev dependency
composer require --dev phpstan/phpstan
# Install with the extension installer (optional but convenient)
composer require --dev phpstan/extension-installer
Verify Installation
# Check PHPStan version
./vendor/bin/phpstan --version
# Run basic analysis
./vendor/bin/phpstan analyse src
Configuration Explained
Below is an example configuration file (phpstan.neon.dist) with explanations:
parameters:
level: max
paths:
- src
reportUnmatchedIgnoredErrors: true
Configuration Breakdown
Analysis Level
level: max
Sets the strictness of analysis. The range is 0 to 9, with max always using the highest available level and including experimental rules. Start lower and work up rather than jumping straight to max on an existing codebase.
Analysis Paths
paths:
- src
Defines which directories to analyse. Common choices are src/, app/, and tests/. Focus analysis on code you own, not generated files or vendor packages.
Error Reporting
reportUnmatchedIgnoredErrors: true
Reports when ignore patterns in your config don't match any errors. Without this, stale ignore rules quietly accumulate and can mask real problems introduced later.
Analysis Levels
PHPStan uses levels 0 to 9 to gradually increase analysis strictness.
Level 0 (Basic)
Basic syntax checking, undefined functions and classes, wrong number of arguments passed to methods.
Levels 1 to 3 (Intermediate)
Unknown methods called on objects, unknown properties accessed on objects, unknown variables in certain contexts.
Levels 4 to 6 (Advanced)
Return types declared in PHPDoc, basic dead code detection, unreachable statements after return or throw.
Levels 7 to 9 (Maximum)
Report partially wrong union types, report calling methods on nullable types, strict comparisons of incompatible types.
Level Max
Always uses the highest available level, including experimental rules. A useful future-proof setting for greenfield projects.
Common Use Cases
Type Safety Validation
// Nullable type violation — PHPStan catches this
function getName(?string $name): string
{
return $name; // Error: might return null
}
// Correct version
function getName(?string $name): string
{
return $name ?? 'Unknown';
}
Method and Property Validation
class User
{
private string $name;
public function getName(): string
{
return $this->name;
}
}
$user = new User();
echo $user->email; // Error: Property User::$email does not exist
$user->invalidMethod(); // Error: Method does not exist
Array and Collection Analysis
// Array key existence
$config = ['database' => ['host' => 'localhost']];
echo $config['cache']['driver']; // Error: Offset 'cache' does not exist
// Collection type checking
/** @var User[] $users */
$users = getUsers();
foreach ($users as $user) {
echo $user->name; // PHPStan knows $user is a User instance
}
Return Type Validation
function findUser(int $id): User
{
$user = User::find($id);
if (!$user) {
return null; // Error: Cannot return null, expects User
}
return $user;
}
// Correct version
function findUser(int $id): ?User
{
return User::find($id);
}
Running PHPStan
Basic Analysis
# Analyse src directory
./vendor/bin/phpstan analyse src
# Analyse multiple directories
./vendor/bin/phpstan analyse src tests
# Analyse specific file
./vendor/bin/phpstan analyse src/User.php
Configuration Options
# Use custom configuration
./vendor/bin/phpstan analyse -c phpstan.neon
# Set analysis level
./vendor/bin/phpstan analyse --level=5 src
# Memory limit for large projects
./vendor/bin/phpstan analyse --memory-limit=1G src
Output Formats
# Default output
./vendor/bin/phpstan analyse src
# JSON output for CI/CD
./vendor/bin/phpstan analyse --error-format=json src
# GitHub Actions format
./vendor/bin/phpstan analyse --error-format=github src
Advanced Options
# Generate baseline (ignore existing errors)
./vendor/bin/phpstan analyse --generate-baseline
# Clear cache
./vendor/bin/phpstan clear-cache
# Debug mode
./vendor/bin/phpstan analyse --debug src
Best Practices
Incremental Adoption
Start with lower levels and work up gradually. Jumping to max on an existing codebase will produce hundreds of errors and make the tool feel unusable. Start at level 0, fix errors, then increment.
./vendor/bin/phpstan analyse --level=0 src
./vendor/bin/phpstan analyse --level=5 src
./vendor/bin/phpstan analyse --level=max src
Use Baseline for Legacy Code
If you're adding PHPStan to an existing project, generate a baseline first. This ignores pre-existing errors so you can enforce the tool going forward without being buried immediately.
# Generate baseline
./vendor/bin/phpstan analyse --generate-baseline
# Subsequent runs ignore baseline errors
./vendor/bin/phpstan analyse
CI/CD Integration
Add PHPStan to your CI pipeline so analysis runs on every pull request. Use the github error format so errors appear inline in the PR diff.
# .github/workflows/phpstan.yml
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.3"
- run: composer install --optimize-autoloader
- run: ./vendor/bin/phpstan analyse --error-format=github
IDE Integration
VS Code with PHPStan extension:
{
"phpstan.enabled": true,
"phpstan.path": "./vendor/bin/phpstan",
"phpstan.configFile": "phpstan.neon"
}
Real-time feedback in the editor catches issues before you even commit. Worth setting up if you spend time in VS Code.
Advanced Configuration
Complete Configuration Example
parameters:
level: max
paths:
- src
- tests
# Exclude specific directories
excludePaths:
- src/Legacy/*
- tests/fixtures/*
# Custom error reporting
reportUnmatchedIgnoredErrors: true
checkMissingIterableValueType: true
checkGenericClassInNonGenericObjectType: true
# Ignore specific errors
ignoreErrors:
- '#Call to an undefined method App\\User::invalidMethod\(\)#'
-
message: '#Access to an undefined property#'
path: src/Legacy/OldClass.php
# Additional rules
checkAlwaysTrueCheckTypeFunctionCall: true
checkAlwaysTrueInstanceof: true
checkAlwaysTrueStrictComparison: true
checkExplicitMixedMissingReturn: true
checkFunctionNameCase: true
checkInternalClassCaseSensitivity: true
# Bootstrap files
bootstrapFiles:
- tests/bootstrap.php
# Autoload directories
scanDirectories:
- src/helpers
# Stub files for missing extensions
stubFiles:
- stubs/custom.stub
Framework-Specific Configuration
Laravel with Larastan v3:
Larastan v3 targets PHPStan 2.x. Install it as a dev dependency and include the extension in your config.
composer require --dev larastan/larastan
includes:
- ./vendor/larastan/larastan/extension.neon
parameters:
level: max
paths:
- app
- database
- routes
- tests
# Laravel-specific ignores
ignoreErrors:
- '#Unsafe usage of new static\(\)#'
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder#'
# Laravel features
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
Symfony Configuration:
includes:
- ./vendor/phpstan/phpstan-symfony/extension.neon
parameters:
level: max
paths:
- src
- tests
symfony:
container_xml_path: var/cache/dev/App_KernelDevDebugContainer.xml
Custom Rules and Extensions
parameters:
level: max
paths:
- src
# Custom rules
rules:
- App\PHPStan\Rules\NoEntityManagerInControllerRule
# Service extensions
services:
-
class: App\PHPStan\Extension\CustomExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
# Type extensions
typeAliases:
UserId: 'int<1, max>'
EmailAddress: 'string'
Performance Optimisation
parameters:
level: max
paths:
- src
# Parallel processing
parallel:
jobSize: 20
maximumNumberOfProcesses: 4
# Cache location
tmpDir: var/cache/phpstan
# Exclude large generated files
excludePaths:
- src/Generated/*
- '*/large-file.php'
Extensions and Integrations
Popular Extensions
# Doctrine extension
composer require --dev phpstan/phpstan-doctrine
# Symfony extension
composer require --dev phpstan/phpstan-symfony
# PHPUnit extension
composer require --dev phpstan/phpstan-phpunit
# Laravel extension (Larastan v3, requires PHPStan 2.x)
composer require --dev larastan/larastan
Integration with Other Tools
With Rector:
#!/bin/bash
./vendor/bin/rector process --dry-run
./vendor/bin/phpstan analyse
./vendor/bin/pint --test
With PHPUnit:
#!/bin/bash
./vendor/bin/phpunit
./vendor/bin/phpstan analyse
Troubleshooting
Common Issues
Memory Limit Exceeded
php -d memory_limit=1G ./vendor/bin/phpstan analyse src
Autoloading Issues
parameters:
bootstrapFiles:
- vendor/autoload.php
- bootstrap/app.php
False Positives
parameters:
ignoreErrors:
- '#Specific error pattern#'
Performance Issues
# Clear cache
./vendor/bin/phpstan clear-cache
# Suppress progress bar for cleaner output
./vendor/bin/phpstan analyse --no-progress src
Debugging Tips
# Verbose output
./vendor/bin/phpstan analyse --debug src
# Analyse a specific file
./vendor/bin/phpstan analyse --debug src/User.php
# Generate a named baseline for debugging
./vendor/bin/phpstan analyse --generate-baseline phpstan-baseline.neon
Conclusion
PHPStan is worth adding to any PHP project that's going to be maintained for more than a few months. The upfront cost of fixing errors is usually a few hours; the ongoing benefit is catching bugs before they ship.
If you're on an existing codebase, start with a baseline and level 0. Pick a level you can keep clean, fix the errors at that level, then increment. The baseline approach means you don't have to fix everything at once, and it prevents the tool from feeling like a burden.
For greenfield projects, set level: max from day one. It's much easier to maintain than to retrofit later.