Appearance
🔄 Migration Guide
Comprehensive guide for gradually adopting these coding standards in existing Laravel projects without disrupting development.
📋 Overview
Migrating an existing project to new coding standards is a journey, not a sprint. This guide provides a pragmatic, step-by-step approach to improve your codebase incrementally.
🎯 Migration Strategy
The Boy Scout Rule
"Always leave the code better than you found it."
Apply standards to code you touch rather than attempting massive refactoring.
📊 Phase 1: Assessment (Week 1)
1.1 Audit Current Codebase
bash
# Check PSR-12 compliance
./vendor/bin/phpcs --standard=PSR12 app/
# Run static analysis
./vendor/bin/phpstan analyse app/ --level=5
# Check for N+1 queries
# Use Laravel Telescope to monitor queries
# Generate code metrics
./vendor/bin/phpmetrics --report-html=metrics app/1.2 Document Current State
Create a baseline document:
markdown
# Codebase Audit - [Date]
## Metrics
- Total Files: 450
- Lines of Code: 85,000
- PSR-12 Compliance: 35%
- Test Coverage: 42%
- Average Cyclomatic Complexity: 8.5
## Major Issues
- [ ] Fat controllers (45% of controllers > 200 lines)
- [ ] N+1 queries in 12 endpoints
- [ ] No type declarations in 60% of methods
- [ ] Missing input validation in 30% of endpoints
- [ ] Direct model access in 80% of controllers1.3 Prioritize Improvements
| Priority | Issue | Impact | Effort |
|---|---|---|---|
| Critical | N+1 Queries | High | Medium |
| Critical | Missing Validation | High | Low |
| High | Type Declarations | Medium | Low |
| High | Fat Controllers | Medium | High |
| Medium | PSR-12 Compliance | Low | Low |
🔧 Phase 2: Foundation Setup (Week 2)
2.1 Install Code Quality Tools
bash
# Install PHP CS Fixer
composer require friendsofphp/php-cs-fixer --dev
# Install PHPStan
composer require phpstan/phpstan --dev
# Install Laravel Pint
composer require laravel/pint --dev
# Install Telescope (development)
composer require laravel/telescope --dev
php artisan telescope:install2.2 Configure Tools
php
// .php-cs-fixer.php
<?php
$finder = PhpCsFixer\Finder::create()
->in(__DIR__)
->exclude(['bootstrap', 'storage', 'vendor'])
->name('*.php')
->notName('*.blade.php');
return (new PhpCsFixer\Config())
->setRules([
'@PSR12' => true,
'strict_param' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'no_unused_imports' => true,
])
->setFinder($finder);neon
# phpstan.neon
parameters:
level: 5
paths:
- app
excludePaths:
- app/Console/Kernel.php
checkMissingIterableValueType: false2.3 Setup CI/CD Checks
yaml
# .github/workflows/code-quality.yml
name: Code Quality
on: [push, pull_request]
jobs:
code-quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Install Dependencies
run: composer install --prefer-dist --no-progress
- name: Run PHP CS Fixer
run: ./vendor/bin/php-cs-fixer fix --dry-run --diff
- name: Run PHPStan
run: ./vendor/bin/phpstan analyse
- name: Run Tests
run: php artisan test --coverage🚀 Phase 3: Incremental Migration (Weeks 3-12)
3.1 Start with New Code
Rule: All new code must follow the standards completely.
php
<?php
// New controller - follows all standards
declare(strict_types=1);
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreProductRequest;
use App\Http\Resources\ProductResource;
use App\Services\ProductService;
use Illuminate\Http\JsonResponse;
class ProductController extends Controller
{
public function __construct(
private ProductService $productService
) {}
public function store(StoreProductRequest $request): JsonResponse
{
$product = $this->productService->createProduct($request->validated());
return response()->json([
'data' => new ProductResource($product),
'message' => 'Product created successfully',
], 201);
}
}3.2 Refactor Critical Paths
Focus on frequently used and business-critical code first.
Example: Refactor Order Processing
Before: Fat Controller
php
<?php
// OLD CODE - Don't modify all at once
class OrderController extends Controller
{
public function store(Request $request)
{
// 200+ lines of business logic
}
}Migration Steps:
Step 1: Extract validation
php
<?php
// Create Form Request
class StoreOrderRequest extends FormRequest
{
public function rules(): array
{
return [
'items' => ['required', 'array', 'min:1'],
'items.*.product_id' => ['required', 'exists:products,id'],
'items.*.quantity' => ['required', 'integer', 'min:1'],
];
}
}
// Update controller
class OrderController extends Controller
{
public function store(StoreOrderRequest $request) // Now using Form Request
{
// Business logic
}
}Step 2: Create service layer
php
<?php
// Create Service
class OrderService
{
public function createOrder(User $user, array $data): Order
{
// Move business logic here
}
}
// Update controller
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService
) {}
public function store(StoreOrderRequest $request)
{
$order = $this->orderService->createOrder(
auth()->user(),
$request->validated()
);
return response()->json($order, 201);
}
}Step 3: Create repository
php
<?php
// Create Repository
class OrderRepository implements OrderRepositoryInterface
{
public function create(array $data): Order
{
return Order::create($data);
}
}
// Update Service
class OrderService
{
public function __construct(
private OrderRepositoryInterface $orderRepository
) {}
public function createOrder(User $user, array $data): Order
{
return $this->orderRepository->create([
'user_id' => $user->id,
// ... other data
]);
}
}3.3 Fix N+1 Queries
php
<?php
// BEFORE: Identify N+1 with Telescope
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count(); // N+1!
}
// AFTER: Fix immediately
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo $user->posts_count;
}3.4 Add Type Declarations
php
<?php
// Use IDE or tools to add types incrementally
// BEFORE
public function getUser($id) {
return User::find($id);
}
// AFTER
public function getUser(int $id): ?User {
return User::find($id);
}📝 Phase 4: Documentation & Training (Ongoing)
4.1 Team Training
markdown
# Training Schedule
## Week 1: Fundamentals
- PSR-12 and PHP Standards
- Type Declarations
- Naming Conventions
## Week 2: Architecture
- SOLID Principles
- Repository Pattern
- Service Layer
## Week 3: Practical
- Form Requests
- API Resources
- Testing Strategies
## Week 4: Advanced
- Performance Optimization
- Security Best Practices
- Code Review Process4.2 Code Review Guidelines
Update your PR template:
markdown
## Code Quality Checklist
- [ ] Follows PSR-12 standards
- [ ] All methods have type declarations
- [ ] Uses Form Requests for validation
- [ ] Controllers are thin (< 50 lines)
- [ ] Business logic in Services
- [ ] Data access via Repositories
- [ ] No N+1 queries
- [ ] Tests included
- [ ] Documentation updated
## Migration Notes
If this touches legacy code:
- [ ] Applied standards to touched code
- [ ] Added tests for modified functionality
- [ ] Documented any technical debt left behind🎯 Phase 5: Consolidation (Weeks 13+)
5.1 Establish Coding Dojo
Regular sessions to refactor legacy code together:
markdown
# Weekly Coding Dojo
## Format
- 2 hours every Friday afternoon
- Team selects one legacy file
- Refactor together following standards
- Discuss tradeoffs and decisions
## Benefits
- Team learning
- Shared ownership
- Consistent patterns
- Knowledge transfer5.2 Track Progress
bash
# Monthly metrics
./vendor/bin/phpstan analyse --level=6 # Increase level gradually
./vendor/bin/phpmetrics --report-html=metrics app/
# Compare with baseline
echo "PSR-12 Compliance: Month 1: 35%, Month 3: 65%, Month 6: 85%"
echo "Test Coverage: Month 1: 42%, Month 3: 58%, Month 6: 75%"⚠️ Migration Gotchas
Don't Do This
❌ Big Bang Refactoring
bash
# DON'T: Refactor everything at once
# This breaks everything and blocks development
git checkout -b refactor-everything
# ... refactor 100 files ...
# Merge conflicts nightmare!❌ Mixing Features and Refactoring
# DON'T: Mix new features with refactoring in one PR
- Added user authentication (new feature)
- Refactored 50 controllers (refactoring)
- Added payment gateway (new feature)
- Restructured entire app (refactoring)❌ Ignoring Tests
php
// DON'T: Refactor without tests
// How do you know it still works?Do This Instead
✅ Incremental Improvements
PR #1: Extract validation to Form Request for OrderController
PR #2: Add repository for Order operations
PR #3: Create OrderService with business logic
PR #4: Add tests for Order creation flow✅ Refactor Then Feature
PR #1: Refactor OrderController (preparation)
PR #2: Add new discount feature (new feature on clean base)✅ Test First
php
// Add tests for existing behavior
// Then refactor with confidence
// Tests prove nothing broke📊 Success Metrics
Track these metrics monthly:
| Metric | Baseline | Month 3 | Month 6 | Target |
|---|---|---|---|---|
| PSR-12 Compliance | 35% | 60% | 85% | 95% |
| Test Coverage | 42% | 60% | 75% | 80% |
| Avg Cyclomatic Complexity | 8.5 | 7.0 | 5.5 | < 5 |
| Controllers > 100 lines | 45% | 30% | 15% | < 10% |
| N+1 Queries | 12 | 5 | 1 | 0 |
🎓 Learning Resources
For the Team
- Documentation - This standards guide
- Code Examples - Reference implementations
- Pair Programming - Learn by doing
- Code Reviews - Feedback and learning
- Coding Dojos - Group refactoring sessions
External Resources
✅ Migration Checklist
Preparation
- [ ] Audit codebase and document current state
- [ ] Install code quality tools
- [ ] Configure CI/CD checks
- [ ] Create migration plan with timeline
Execution
- [ ] Apply standards to all new code
- [ ] Fix critical issues (N+1, security)
- [ ] Refactor high-traffic endpoints
- [ ] Add tests incrementally
- [ ] Update documentation
Consolidation
- [ ] Establish code review process
- [ ] Schedule regular refactoring sessions
- [ ] Track metrics monthly
- [ ] Celebrate improvements
- [ ] Share learnings
🔄 Remember: Migration is a marathon, not a sprint. Focus on sustainable progress and team learning rather than perfection overnight.