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
- Static Analyzers – PHPStan, Psalm, Larastan
- Code Formatters – Laravel Pint, PHP CS Fixer
- Complexity Analyzers – PHP Insights, PHPMD
- 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: