paulund

PHPStan: Advanced Static Analysis for PHP

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 your code reaches production.

Table of Contents

  1. What is PHPStan?
  2. Why Use PHPStan?
  3. Installation
  4. Configuration Explained
  5. Analysis Levels
  6. Common Use Cases
  7. Running PHPStan
  8. Best Practices
  9. Advanced Configuration

What is PHPStan?

PHPStan is a static analysis tool that analyzes 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
  • Analyzes method calls and property access
  • Checks PHPDoc annotations for accuracy

PHPStan understands your code structure and can identify issues that traditional testing may miss.

Why Use PHPStan?

1. Early Bug Detection

PHPStan catches errors before they cause runtime failures:

  • Undefined variables and methods
  • Type mismatches and incompatibilities
  • Logic errors and unreachable code
  • Missing return statements

2. Type Safety Enforcement

PHPStan ensures your code respects type contracts:

  • Validates method signatures
  • Checks parameter and return types
  • Identifies nullable type violations
  • Enforces strict type declarations

3. Code Quality Improvement

PHPStan promotes better coding practices:

  • Identifies unused code and variables
  • Validates PHPDoc annotations
  • Ensures consistent API usage
  • Detects potential security issues

4. Refactoring Confidence

PHPStan provides a safety net during code changes:

  • Validates that changes don't break existing functionality
  • Identifies impact of API changes
  • Ensures backward compatibility
  • Catches regression bugs

Installation

Via Composer (Recommended)

# Install as dev dependency
composer require --dev phpstan/phpstan

# Install with extensions
composer require --dev phpstan/phpstan
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 detailed explanations:

parameters:
    level: max
    paths:
        - src

    reportUnmatchedIgnoredErrors: true

Configuration Breakdown

1. Analysis Level

level: max
  • Purpose: Sets the strictness level of analysis
  • Range: 0-9 (0 = basic, 9 = maximum strictness)
  • max: Always uses the highest available level
  • Effect: Enables all available checks and rules

2. Analysis Paths

paths:
    - src
  • Purpose: Defines which directories to analyze
  • Common paths: src/, app/, tests/
  • Effect: Focuses analysis on relevant code directories

3. Error Reporting

reportUnmatchedIgnoredErrors: true
  • Purpose: Reports when ignore patterns don't match any errors
  • Benefit: Prevents outdated ignore rules from hiding new issues
  • Effect: Ensures ignore patterns remain relevant

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

Level 1-3 (Intermediate)

  • Unknown methods called on objects
  • Unknown properties accessed on objects
  • Unknown variables in certain contexts

Level 4-6 (Advanced)

  • Return types declared in PHPDoc
  • Basic dead code detection
  • Unreachable statements after return/throw

Level 7-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
  • Includes all available rules, including experimental ones
  • A future-proof configuration option

Common Use Cases

1. Type Safety Validation

PHPStan detects:

// Type mismatch
function processUser(User $user): void
{
    echo $user->name; // Error if User::$name is private
}

// Nullable type violation
function getName(?string $name): string
{
    return $name; // Error: might return null
}

// Correct version
function getName(?string $name): string
{
    return $name ?? 'Unknown';
}

2. Method and Property Validation

PHPStan catches:

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

3. Array and Collection Analysis

PHPStan validates:

// 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 User instance
}

4. Return Type Validation

PHPStan enforces:

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

1. Basic Analysis

# Analyze src directory
./vendor/bin/phpstan analyse src

# Analyze multiple directories
./vendor/bin/phpstan analyse src tests

# Analyze specific file
./vendor/bin/phpstan analyse src/User.php

2. 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

3. 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

4. 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

1. Incremental Adoption

Start with lower levels and gradually increase:

# Start with level 0
./vendor/bin/phpstan analyse --level=0 src

# Gradually increase
./vendor/bin/phpstan analyse --level=5 src

# Eventually reach max
./vendor/bin/phpstan analyse --level=max src

2. Use Baseline for Legacy Code

Generate baseline to ignore existing errors:

# Generate baseline
./vendor/bin/phpstan analyse --generate-baseline

# Run analysis ignoring baseline errors
./vendor/bin/phpstan analyse

3. CI/CD Integration

Add PHPStan to your CI pipeline:

# .github/workflows/phpstan.yml
name: PHPStan
on: [push, pull_request]

jobs:
    phpstan:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v3
            - uses: shivammathur/setup-php@v2
              with:
                  php-version: "8.2"
            - run: composer install --optimize-autoloader
            - run: ./vendor/bin/phpstan analyse --error-format=github

4. IDE Integration

VS Code with PHPStan extension:

{
    "phpstan.enabled": true,
    "phpstan.path": "./vendor/bin/phpstan",
    "phpstan.configFile": "phpstan.neon"
}

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 Configuration:

includes:
    - ./vendor/nunomaduro/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 Optimization

parameters:
    level: max
    paths:
        - src

    # Memory optimization
    memoryLimitFile: .phpstan-memory-limit

    # Parallel processing
    parallel:
        jobSize: 20
        maximumNumberOfProcesses: 4

    # Cache optimization
    tmpDir: var/cache/phpstan

    # Exclude large 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)
composer require --dev nunomaduro/larastan

Integration with Other Tools

With Rector:

#!/bin/bash
# Quality check script
./vendor/bin/rector process --dry-run
./vendor/bin/phpstan analyse
./vendor/bin/pint --test

With PHPUnit:

#!/bin/bash
# Test and analyze
./vendor/bin/phpunit
./vendor/bin/phpstan analyse

Troubleshooting

Common Issues

  1. Memory Limit Exceeded
php -d memory_limit=1G ./vendor/bin/phpstan analyse src
  1. Autoloading Issues
parameters:
    bootstrapFiles:
        - vendor/autoload.php
        - bootstrap/app.php
  1. False Positives
parameters:
    ignoreErrors:
        - '#Specific error pattern#'
  1. Performance Issues
# Clear cache
./vendor/bin/phpstan clear-cache

# Reduce parallel processes
./vendor/bin/phpstan analyse --no-progress src

Debugging Tips

# Verbose output
./vendor/bin/phpstan analyse --debug src

# Analyse specific error
./vendor/bin/phpstan analyse --debug src/User.php

# Generate baseline for debugging
./vendor/bin/phpstan analyse --generate-baseline phpstan-baseline.neon

Conclusion

PHPStan is an essential tool for maintaining high-quality PHP codebases. It provides comprehensive static analysis that catches bugs early, enforces type safety, and improves code quality across your project.

Key Benefits:

  • Early bug detection prevents runtime errors
  • Type safety enforcement improves code reliability
  • Code quality insights guide better architecture decisions
  • Refactoring confidence through comprehensive analysis
  • CI/CD integration maintains quality standards

Getting Started:

  1. Install PHPStan via Composer
  2. Create basic configuration (phpstan.neon)
  3. Start with lower analysis levels
  4. Gradually increase strictness
  5. Integrate with development workflow

Best Practices:

  • Use baseline for legacy code
  • Integrate with CI/CD pipeline
  • Configure IDE for real-time feedback
  • Combine with other quality tools
  • Regular analysis during development

PHPStan becomes more valuable as your codebase grows and your team size increases. It is particularly beneficial for teams working on critical applications where code quality and reliability are paramount.


Related Resources