Tools Overview
| Tool | Purpose |
|---|---|
| Laravel Pint | Code formatting (PHP CS Fixer) |
| PHPStan + Larastan | Static analysis |
| Rector | Automated refactoring |
Laravel Pint
Configuration
// pint.json
{
"preset": "laravel",
"rules": {
"align_multiline_comment": true,
"array_indentation": true,
"array_syntax": true,
"binary_operator_spaces": {
"operators": {
"=>": "align_single_space_minimal"
}
},
"blank_line_before_statement": {
"statements": ["continue", "return", "throw", "try"]
},
"class_attributes_separation": {
"elements": {
"method": "one",
"trait_import": "none"
}
},
"concat_space": {
"spacing": "one"
},
"date_time_immutable": true,
"declare_strict_types": true,
"final_class": true,
"final_public_method_for_abstract_class": true,
"fully_qualified_strict_types": true,
"global_namespace_import": {
"import_classes": true,
"import_constants": true,
"import_functions": true
},
"multiline_whitespace_before_semicolons": {
"strategy": "no_multi_line"
},
"new_with_parentheses": {
"anonymous_class": false,
"named_class": false
},
"no_unused_imports": true,
"nullable_type_declaration_for_default_null_value": true,
"ordered_class_elements": {
"order": [
"use_trait",
"case",
"constant_public",
"constant_protected",
"constant_private",
"property_public",
"property_protected",
"property_private",
"construct",
"destruct",
"magic",
"phpunit",
"method_public",
"method_protected",
"method_private"
]
},
"ordered_imports": {
"imports_order": ["class", "function", "const"],
"sort_algorithm": "alpha"
},
"ordered_interfaces": true,
"ordered_traits": true,
"phpdoc_order": true,
"phpdoc_separation": true,
"phpdoc_summary": true,
"self_static_accessor": true,
"simplified_null_return": true,
"single_line_empty_body": true,
"strict_comparison": true,
"trailing_comma_in_multiline": true,
"visibility_required": {
"elements": ["method", "property"]
},
"void_return": true,
"yoda_style": {
"equal": false,
"identical": false,
"less_and_greater": false
}
}
}
Running Pint
# Format all files
./vendor/bin/pint
# Check without fixing (CI)
./vendor/bin/pint --test
# Format specific path
./vendor/bin/pint app/Models
# Verbose output
./vendor/bin/pint -v
Key Rules Explained
| Rule | Effect |
|---|---|
declare_strict_types |
Adds declare(strict_types=1); |
final_class |
Makes classes final |
strict_comparison |
Uses === instead of == |
void_return |
Adds void return types |
new_with_parentheses: false |
new User instead of new User() |
trailing_comma_in_multiline |
Trailing commas in arrays/params |
ordered_imports |
Alphabetical imports |
PHPStan
Configuration
# phpstan.neon
includes:
- vendor/larastan/larastan/extension.neon
- vendor/nesbot/carbon/extension.neon
parameters:
paths:
- app
- database
- routes
- tests
level: 5
checkModelProperties: true
ignoreErrors:
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::ordered\(\)#'
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::attachTags\(\)#'
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::syncTags\(\)#'
excludePaths:
- vendor
- storage
- bootstrap/cache
Levels
| Level | Checks |
|---|---|
| 0 | Basic checks, unknown classes |
| 1 | Possibly undefined variables |
| 2 | Unknown methods on $this |
| 3 | Return types, types of properties |
| 4 | Basic dead code checking |
| 5 | Argument types (recommended) |
| 6 | Report missing typehints |
| 7 | Union types |
| 8 | Nullable types |
| 9 | Mixed type |
Running PHPStan
# Run analysis
./vendor/bin/phpstan analyse
# With memory limit
./vendor/bin/phpstan analyse --memory-limit=512M
# Generate baseline (ignore existing errors)
./vendor/bin/phpstan analyse --generate-baseline
# Specific path
./vendor/bin/phpstan analyse app/Models
Common Fixes
// Error: Property has no type
// Fix: Add type declaration
private int $count;
// Error: Method returns null but return type doesn't allow it
// Fix: Make return type nullable
public function find(int $id): ?User
// Error: Parameter $data has no type
// Fix: Add parameter type
public function process(array $data): void
Rector
Configuration
// rector.php
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;
use RectorLaravel\Set\LaravelSetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/app',
__DIR__ . '/database',
__DIR__ . '/routes',
__DIR__ . '/tests',
])
->withSkip([
__DIR__ . '/bootstrap/cache',
__DIR__ . '/storage',
__DIR__ . '/vendor',
])
->withSets([
SetList::DEAD_CODE,
SetList::CODE_QUALITY,
SetList::TYPE_DECLARATION,
SetList::PRIVATIZATION,
SetList::EARLY_RETURN,
SetList::STRICT_BOOLEANS,
LaravelSetList::LARAVEL_110,
])
->withPreparedSets(
deadCode: true,
codeQuality: true,
typeDeclarations: true,
privatization: true,
earlyReturn: true,
strictBooleans: true,
);
Running Rector
# Preview changes (dry run)
./vendor/bin/rector process --dry-run
# Apply changes
./vendor/bin/rector process
# Specific path
./vendor/bin/rector process app/Models
# With specific rule
./vendor/bin/rector process --only="Rector\\DeadCode\\Rector\\ClassMethod\\RemoveUnusedPrivateMethodRector"
Common Transformations
// BEFORE: Dead code removal
private function unusedMethod() { }
// AFTER: Method removed
// BEFORE: Early return
public function check($value)
{
if ($value) {
return true;
} else {
return false;
}
}
// AFTER: Simplified
public function check($value): bool
{
return (bool) $value;
}
// BEFORE: Missing return type
public function getName()
{
return $this->name;
}
// AFTER: Type added
public function getName(): string
{
return $this->name;
}
Composer Scripts
Add to composer.json:
{
"scripts": {
"lint": "./vendor/bin/pint",
"lint:check": "./vendor/bin/pint --test",
"analyse": "./vendor/bin/phpstan analyse",
"refactor": "./vendor/bin/rector process --dry-run",
"refactor:apply": "./vendor/bin/rector process",
"test": "./vendor/bin/pest",
"quality": [
"@lint:check",
"@analyse",
"@test"
]
}
}
Usage:
composer lint # Format code
composer analyse # Static analysis
composer test # Run tests
composer quality # All checks
CI/CD Integration
GitHub Actions Example
# .github/workflows/quality.yml
name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
- name: Install Dependencies
run: composer install --no-progress --prefer-dist
- name: Run Pint
run: ./vendor/bin/pint --test
- name: Run PHPStan
run: ./vendor/bin/phpstan analyse
- name: Run Tests
run: ./vendor/bin/pest
Editor Integration
VS Code
// .vscode/settings.json
{
"editor.formatOnSave": true,
"[php]": {
"editor.defaultFormatter": "open-southeners.laravel-pint"
}
}
PHPStorm
- Settings → PHP → Quality Tools → Laravel Pint
- Enable "Run on save"
- Point to
./vendor/bin/pint
Pre-Commit Hooks
Using Husky or similar:
#!/bin/sh
# .husky/pre-commit
./vendor/bin/pint --test
./vendor/bin/phpstan analyse
./vendor/bin/pest --parallel