Code Quality & Best Practices

Improving Laravel Code Quality with Static Analysis and Linting Tools

Improve Laravel code quality with static analysis and linting tools. Learn PHPStan, Larastan, PHP CS Fixer, Pint, and best practices for maintaining clean, bug-free code.

Muhammad Waqas

Muhammad Waqas

CEO at CentoSquare

|
03-Nov-2025
4 min read
Improving Laravel Code Quality with Static Analysis and Linting Tools

Introduction

Code quality directly impacts maintainability, bug frequency, and development velocity. While tests verify runtime behavior, static analysis and linting tools catch errors before code execution, enforce coding standards, and identify potential bugs at compile time.

Modern Laravel development requires more than just functional code—it demands consistent formatting, type safety, adherence to standards, and early bug detection. Static analysis tools examine code without running it, finding type errors, undefined variables, and potential issues that tests might miss.

In this comprehensive guide, we'll explore essential static analysis and linting tools for Laravel 12, including PHPStan/Larastan, Laravel Pint, PHP CS Fixer, and integration strategies that will elevate your code quality to production standards.


Why Static Analysis Matters

Benefits of Static Analysis

  • Early Bug Detection – Find errors before runtime
  • Type Safety – Catch type-related bugs
  • Code Consistency – Enforce team coding standards
  • Documentation – Type hints serve as documentation
  • Refactoring Confidence – Safely modify code
  • Reduced Technical Debt – Prevent quality degradation
  • CI/CD Integration – Automated quality gates

Types of Code Quality Tools

  1. Static Analyzers – PHPStan, Psalm, Larastan
  2. Code Formatters – Laravel Pint, PHP CS Fixer
  3. Complexity Analyzers – PHP Insights, PHPMD
  4. Code Review Tools – SonarQube, Scrutinizer

PHPStan and Larastan

1. Installing Larastan

Larastan is PHPStan specifically configured for Laravel:

composer require nunomaduro/larastan --dev

Create phpstan.neon configuration:

includes:
    - vendor/nunomaduro/larastan/extension.neon

parameters:
    paths:
        - app
        - config
        - database
        - routes
    
    level: 5
    
    ignoreErrors:
        - '#PHPDoc tag @var#'
    
    excludePaths:
        - ./*/*/FileToBeExcluded.php
    
    checkMissingIterableValueType: false

2. Understanding PHPStan Levels

PHPStan has 10 strictness levels (0-9):

  • Level 0 – Basic checks, undefined variables
  • Level 3 – Unknown method calls, property access
  • Level 5 – Type hints verification (recommended starting point)
  • Level 8 – Strict type checking
  • Level 9 – Maximum strictness (mixed types not allowed)

Start at level 5 and gradually increase:

# Run analysis
vendor/bin/phpstan analyse

# Specific level
vendor/bin/phpstan analyse --level=5

# Generate baseline (ignore existing errors)
vendor/bin/phpstan analyse --generate-baseline

3. Common PHPStan Checks

Type Errors:

// ❌ PHPStan will catch this
class UserService
{
    public function getUser(int $id): User
    {
        return User::find($id); // Returns User|null, not User
    }
}

// ✅ Correct implementation
class UserService
{
    public function getUser(int $id): ?User
    {
        return User::find($id);
    }
    
    public function getUserOrFail(int $id): User
    {
        return User::findOrFail($id);
    }
}

Undefined Methods:

// ❌ PHPStan detects undefined method
$user = User::find(1);
$user->undefinedMethod(); // Error: Method undefinedMethod() not found

// ✅ Using defined methods
$user = User::find(1);
$user->posts()->count();

Property Access:

// ❌ PHPStan catches undefined properties
class Post extends Model
{
    // No $title property defined
}

$post = new Post();
echo $post->title; // Error: Access to undefined property

// ✅ Define properties or use @property annotation
/**
 * @property string $title
 * @property string $content
 */
class Post extends Model
{
    protected $fillable = ['title', 'content'];
}

4. Adding Type Hints

Improve type safety across your codebase:

// ❌ No type hints
class OrderService
{
    private $repository;
    
    public function __construct($repository)
    {
        $this->repository = $repository;
    }
    
    public function getOrder($id)
    {
        return $this->repository->find($id);
    }
}

// ✅ Full type hints
class OrderService
{
    public function __construct(
        private OrderRepository $repository
    ) {}
    
    public function getOrder(int $id): ?Order
    {
        return $this->repository->find($id);
    }
    
    public function createOrder(array $data): Order
    {
        return $this->repository->create($data);
    }
    
    /**
     * @return Collection<int, Order>
     */
    public function getActiveOrders(): Collection
    {
        return $this->repository->getActive();
    }
}

Laravel Pint

Laravel Pint is an opinionated PHP code formatter built on PHP CS Fixer.

1. Installing Laravel Pint

composer require laravel/pint --dev

2. Running Pint

# Format all files
vendor/bin/pint

# Dry run (preview changes)
vendor/bin/pint --test

# Format specific directory
vendor/bin/pint app/Services

# Show detailed diff
vendor/bin/pint -v

3. Configuring Pint

Create pint.json in your project root:

{
    "preset": "laravel",
    "rules": {
        "simplified_null_return": true,
        "braces": false,
        "new_with_braces": {
            "anonymous_class": false,
            "named_class": false
        },
        "no_unused_imports": true,
        "ordered_imports": {
            "sort_algorithm": "alpha"
        },
        "single_quote": true,
        "trailing_comma_in_multiline": {
            "elements": ["arrays"]
        }
    },
    "exclude": [
        "vendor",
        "storage",
        "bootstrap/cache"
    ]
}

4. Available Presets

{
    "preset": "laravel"  // Laravel conventions (default)
}

{
    "preset": "psr12"    // PSR-12 standards
}

{
    "preset": "symfony"  // Symfony conventions
}

5. Before and After Examples

Before Pint:

<?php

namespace App\Services;

use App\Models\User;
use Illuminate\Support\Collection;
use App\Models\Post;

class PostService {
    public function __construct(private PostRepository $repository) {
    }

    public function getPostsByUser( int $userId ) : Collection
    {
        $user = User::find($userId);
        if( $user === null ) {
            return collect([]);
        }
        return $user->posts;
    }
}

After Pint:

<?php

namespace App\Services;

use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Collection;

class PostService
{
    public function __construct(private PostRepository $repository)
    {
    }

    public function getPostsByUser(int $userId): Collection
    {
        $user = User::find($userId);
        
        if ($user === null) {
            return collect([]);
        }
        
        return $user->posts;
    }
}

PHP CS Fixer

More configurable alternative to Pint.

1. Installing PHP CS Fixer

composer require friendsofphp/php-cs-fixer --dev

2. Configuration

Create .php-cs-fixer.php:

<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$finder = Finder::create()
    ->in([
        __DIR__ . '/app',
        __DIR__ . '/config',
        __DIR__ . '/database',
        __DIR__ . '/routes',
        __DIR__ . '/tests',
    ])
    ->name('*.php')
    ->notName('*.blade.php')
    ->ignoreDotFiles(true)
    ->ignoreVCS(true);

return (new Config())
    ->setRules([
        '@PSR12' => true,
        'array_syntax' => ['syntax' => 'short'],
        'ordered_imports' => ['sort_algorithm' => 'alpha'],
        'no_unused_imports' => true,
        'not_operator_with_successor_space' => true,
        'trailing_comma_in_multiline' => true,
        'phpdoc_scalar' => true,
        'unary_operator_spaces' => true,
        'binary_operator_spaces' => true,
        'blank_line_before_statement' => [
            'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
        ],
        'phpdoc_single_line_var_spacing' => true,
        'phpdoc_var_without_name' => true,
        'class_attributes_separation' => [
            'elements' => [
                'method' => 'one',
            ],
        ],
        'method_argument_space' => [
            'on_multiline' => 'ensure_fully_multiline',
            'keep_multiple_spaces_after_comma' => true,
        ],
        'single_trait_insert_per_statement' => true,
    ])
    ->setFinder($finder);

3. Running PHP CS Fixer

# Fix code
vendor/bin/php-cs-fixer fix

# Dry run
vendor/bin/php-cs-fixer fix --dry-run --diff

# Fix specific path
vendor/bin/php-cs-fixer fix app/Services

PHP Insights

Comprehensive code quality and architecture analysis.

1. Installing PHP Insights

composer require nunomaduro/phpinsights --dev
php artisan vendor:publish --provider="NunoMaduro\PhpInsights\Application\Adapters\Laravel\InsightsServiceProvider"

2. Running PHP Insights

# Analyze code
php artisan insights

# With verbose output
php artisan insights -v

# Analyze specific directory
php artisan insights app/Services

# Generate report
php artisan insights --format=json > insights.json

3. Configuration

Edit config/insights.php:

<?php

return [
    'preset' => 'laravel',
    
    'ide' => 'phpstorm',
    
    'exclude' => [
        'vendor',
        'storage',
        'bootstrap/cache',
    ],
    
    'add' => [
        // Additional checks
    ],
    
    'remove' => [
        // Remove specific checks
        \NunoMaduro\PhpInsights\Domain\Insights\ForbiddenNormalClasses::class,
    ],
    
    'config' => [
        \PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff::class => [
            'lineLimit' => 120,
            'absoluteLineLimit' => 160,
        ],
    ],
];

4. Understanding Metrics

PHP Insights reports on four categories:

  • Code – Overall code quality score
  • Complexity – Cyclomatic complexity
  • Architecture – Design patterns adherence
  • Style – Coding standards compliance
# Example output
Code .......................... 85.2 %
Complexity .................... 78.5 %
Architecture .................. 92.1 %
Style ......................... 95.8 %

Integrating Tools into Workflow

1. Pre-commit Hooks

Install Git hooks with Husky or manually:

# .git/hooks/pre-commit
#!/bin/bash

echo "Running static analysis..."

# Run Pint
vendor/bin/pint --test

if [ $? -ne 0 ]; then
    echo "❌ Code formatting issues found. Run 'vendor/bin/pint' to fix."
    exit 1
fi

# Run PHPStan
vendor/bin/phpstan analyse --error-format=table

if [ $? -ne 0 ]; then
    echo "❌ Static analysis failed. Fix errors before committing."
    exit 1
fi

echo "✅ All checks passed!"
exit 0

Make executable:

chmod +x .git/hooks/pre-commit

2. GitHub Actions CI/CD

# .github/workflows/code-quality.yml
name: Code Quality

on: [push, pull_request]

jobs:
  code-quality:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          coverage: none
          
      - name: Install Dependencies
        run: composer install --no-interaction --prefer-dist --optimize-autoloader
        
      - name: Run Pint
        run: vendor/bin/pint --test
        
      - name: Run PHPStan
        run: vendor/bin/phpstan analyse
        
      - name: Run PHP Insights
        run: php artisan insights --no-interaction --min-quality=80

3. Composer Scripts

Add shortcuts to composer.json:

{
    "scripts": {
        "format": "vendor/bin/pint",
        "format:test": "vendor/bin/pint --test",
        "analyse": "vendor/bin/phpstan analyse",
        "insights": "php artisan insights",
        "quality": [
            "@format:test",
            "@analyse"
        ]
    }
}

Usage:

composer format        # Format code
composer analyse       # Run static analysis
composer quality       # Run all quality checks

Best Practices

1. Gradual Adoption

Start with low strictness and increase gradually:

# Week 1: Level 3
phpstan.neon: level: 3

# Week 2: Fix issues, increase to 4
phpstan.neon: level: 4

# Continue until reaching level 8

2. Team Standards

Document your team's standards:

# Code Quality Standards

## Required Tools
- Laravel Pint (formatting)
- PHPStan Level 6 (static analysis)
- PHP Insights (architecture)

## Pre-commit Checklist
- [ ] Run `composer format`
- [ ] Run `composer analyse`
- [ ] All tests pass
- [ ] No PHPStan errors

## Code Review Focus
- Type safety
- Naming conventions
- SOLID principles
- Test coverage

3. Continuous Improvement

// Before: No type hints
public function getUser($id)
{
    return User::find($id);
}

// After Sprint 1: Basic types
public function getUser(int $id)
{
    return User::find($id);
}

// After Sprint 2: Complete types
public function getUser(int $id): ?User
{
    return User::find($id);
}

// After Sprint 3: Docblock for collections
/**
 * @return Collection<int, User>
 */
public function getActiveUsers(): Collection
{
    return User::where('active', true)->get();
}

4. Baseline Strategy

Use baselines to adopt tools in legacy projects:

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

# Fix new code only
# Old errors in phpstan-baseline.neon are ignored

Advanced Configurations

1. Custom PHPStan Rules

Create custom rules for project-specific checks:

// app/StaticAnalysis/NoEloquentInControllersRule.php
namespace App\StaticAnalysis;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;

class NoEloquentInControllersRule implements Rule
{
    public function getNodeType(): string
    {
        return Node\Expr\StaticCall::class;
    }
    
    public function processNode(Node $node, Scope $scope): array
    {
        if (!$scope->isInClass()) {
            return [];
        }
        
        $class = $scope->getClassReflection();
        
        if (!str_ends_with($class->getName(), 'Controller')) {
            return [];
        }
        
        // Check if calling Eloquent methods
        if ($node->class instanceof Node\Name && 
            str_contains($node->class->toString(), 'Model')) {
            return ['Controllers should not use Eloquent directly. Use repositories or services.'];
        }
        
        return [];
    }
}

2. IDE Integration

PhpStorm:

  • Settings → PHP → Quality Tools → PHPStan
  • Enable "Run on save"
  • Configure path to PHPStan

VS Code:

  • Install "PHPStan" extension
  • Install "PHP Sniffer & Beautifier" extension
  • Configure in settings.json:
{
    "phpstan.path": "vendor/bin/phpstan",
    "phpstan.level": "6",
    "php-cs-fixer.executablePath": "vendor/bin/php-cs-fixer"
}

Monitoring Code Quality Metrics

1. Track Improvements

# Run and save results
vendor/bin/phpstan analyse --error-format=json > phpstan-results.json

# Compare over time
# Measure:
# - Total errors
# - Errors by severity
# - Code coverage
# - Complexity scores

2. Set Quality Gates

# In CI/CD
- name: Quality Gate
  run: |
    ERRORS=$(vendor/bin/phpstan analyse --error-format=json | jq '.totals.file_errors')
    if [ $ERRORS -gt 0 ]; then
      echo "Quality gate failed: $ERRORS PHPStan errors"
      exit 1
    fi

Conclusion

Static analysis and linting tools are essential for maintaining high-quality Laravel codebases. By implementing PHPStan/Larastan, Laravel Pint, and PHP Insights, you catch bugs early, enforce consistent standards, and build more maintainable applications. Integrate these tools into your workflow, CI/CD pipeline, and development process for continuous code quality improvement.

Key takeaways:

  • Install Larastan for Laravel-specific static analysis
  • Use Laravel Pint for consistent code formatting
  • Integrate tools into pre-commit hooks and CI/CD
  • Start with lower strictness levels and increase gradually
  • Add comprehensive type hints throughout codebase
  • Monitor code quality metrics over time
  • Make quality checks part of code review process

Need help improving your Laravel code quality? NeedLaravelSite specializes in code audits, refactoring, and quality improvement. Contact us for expert Laravel development services.


Related Resources:


Article Tags

Laravel code quality Laravel static analysis Laravel linting PHPStan Laravel Larastan Laravel Pint PHP CS Fixer Laravel code standards Laravel type checking Laravel code inspection PHP static analysis Laravel coding standards Laravel code formatter

About the Author

Muhammad Waqas

Muhammad Waqas

CEO at CentoSquare

Founder & CEO at CentoSquare | Creator of NeedLaravelSite | Helping Businesses Grow with Cutting-Edge Web, Mobile & Marketing Solutions | Building Innovative Products