Claude
Skills
Sign in
Back

laravel-systematic-debugging

Included with Lifetime
$97 forever

Systematic debugging process for Laravel applications - ensures root cause investigation before attempting fixes. Use for any Laravel issue (test failures, bugs, unexpected behavior, performance problems).

Code Review

What this skill does


# Systematic Debugging for Laravel

## Overview

Random fixes waste time and create new bugs in Laravel applications. Quick patches mask underlying issues.

**Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure.

**Violating the letter of this process is violating the spirit of debugging.**

## The Iron Law

```
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
```

If you haven't completed Phase 1, you cannot propose fixes.

## When to Use

Use for ANY Laravel technical issue:
- Test failures
- Eloquent query issues
- Authentication/authorization bugs
- Validation failures
- Queue job failures
- Route errors
- Migration issues
- N+1 query problems
- Performance issues

**Use this ESPECIALLY when:**
- Under time pressure
- "Just one quick fix" seems obvious
- You've already tried multiple fixes
- Previous fix didn't work
- You don't fully understand the issue

## The Four Phases

You MUST complete each phase before proceeding to the next.

### Phase 1: Root Cause Investigation

**BEFORE attempting ANY fix:**

1. **Read Error Messages Carefully**
   ```
   SQLSTATE[23000]: Integrity constraint violation
   → Check foreign key constraints, not a code bug
   
   Class 'App\Models\Post' not found
   → Check namespace, run composer dump-autoload
   
   Method Illuminate\Database\Eloquent\Collection::save does not exist
   → get() returns Collection, not Model. Use first() or update()
   ```

2. **Check Laravel Logs**
   ```bash
   # Main Laravel log
   tail -f storage/logs/laravel.log
   
   # Check for specific errors
   grep "SQLSTATE" storage/logs/laravel.log
   
   # Clear logs if too large
   > storage/logs/laravel.log
   ```

3. **Enable Debug Mode (Local Only)**
   ```env
   APP_DEBUG=true
   APP_ENV=local
   ```

4. **Use Laravel Telescope**
   ```bash
   composer require laravel/telescope --dev
   php artisan telescope:install
   php artisan migrate
   
   # Access at /telescope
   # View: Requests, Queries, Jobs, Events, Exceptions
   ```

5. **Check Recent Changes**
   ```bash
   # What changed that could cause this?
   git log --oneline -10
   git diff HEAD~5
   
   # Check if migrations ran
   php artisan migrate:status
   
   # Check if config cached
   php artisan config:show
   ```

6. **Reproduce Consistently**
   ```bash
   # Can you trigger it every time?
   php artisan tinker
   >>> App\Models\Post::first();
   
   # Try in different environments
   APP_ENV=testing php artisan test
   ```

7. **Trace Data Flow for Eloquent Issues**
   ```php
   // Enable query logging
   DB::listen(function ($query) {
       Log::debug('Query executed', [
           'sql' => $query->sql,
           'bindings' => $query->bindings,
           'time' => $query->time,
       ]);
   });
   
   // Or in specific code
   DB::enableQueryLog();
   $posts = Post::with('user')->get();
   dd(DB::getQueryLog());
   ```

### Phase 2: Pattern Analysis

**Find the pattern before fixing:**

1. **Find Working Examples in Laravel**
   ```bash
   # Search for similar working code
   grep -r "belongsTo" app/Models/
   grep -r "middleware" app/Http/
   
   # Check Laravel docs for the pattern
   # Check other models that work correctly
   ```

2. **Compare Against Laravel Conventions**
   ```php
   // ❌ What you have
   class Post extends Model {
       public function author() {
           return $this->hasOne(User::class, 'id', 'user_id');
       }
   }
   
   // ✅ Laravel convention
   class Post extends Model {
       public function user(): BelongsTo {
           return $this->belongsTo(User::class);
       }
   }
   ```

3. **Check Laravel Documentation**
   - Read the COMPLETE section, don't skim
   - Follow examples exactly first
   - Customize only after understanding

4. **Identify Differences**
   ```php
   // Working model
   class User extends Model {
       protected $fillable = ['name', 'email'];
   }
   
   // Broken model - difference: missing mass assignment protection
   class Post extends Model {
       // No $fillable or $guarded defined
   }
   ```

### Phase 3: Hypothesis and Testing

**Scientific method:**

1. **Form Single Hypothesis**
   ```
   Hypothesis: "Posts aren't saving because mass assignment 
   protection is blocking the 'user_id' field"
   
   Expected: Adding 'user_id' to $fillable will fix it
   ```

2. **Test Minimally**
   ```php
   // Before (broken)
   protected $fillable = ['title', 'content'];
   
   // Test change (ONE variable)
   protected $fillable = ['title', 'content', 'user_id'];
   
   // Don't change multiple things at once
   ```

3. **Verify in Tinker**
   ```bash
   php artisan tinker
   >>> $post = Post::create(['title' => 'Test', 'content' => 'Test', 'user_id' => 1]);
   >>> $post->user_id; // Should be 1
   ```

4. **When You Don't Know**
   - Say "I don't understand why X is happening"
   - Check Laravel GitHub issues for similar problems
   - Ask in Laravel Discord/Forums with specifics
   - Don't pretend to know

### Phase 4: Implementation

**Fix the root cause, not the symptom:**

1. **Create Failing Test Case**
   ```php
   use Illuminate\Foundation\Testing\RefreshDatabase;
   
   test('user can create post', function () {
       $user = User::factory()->create();
       
       $response = $this->actingAs($user)
           ->post('/posts', [
               'title' => 'Test Post',
               'content' => 'Test content',
           ]);
       
       $response->assertRedirect();
       
       expect(Post::where('title', 'Test Post')->exists())->toBeTrue();
       expect(Post::first()->user_id)->toBe($user->id);
   });
   
   // Run and watch it FAIL first
   php artisan test --filter=user_can_create_post
   ```

2. **Implement Single Fix**
   ```php
   // ONE fix for the root cause
   protected $fillable = ['title', 'content', 'user_id'];
   
   // NO bundled improvements like:
   // - Adding casts
   // - Refactoring methods
   // - Changing other code
   ```

3. **Verify Fix**
   ```bash
   # Test passes now
   php artisan test --filter=user_can_create_post
   
   # All tests still pass
   php artisan test
   
   # Manual verification
   php artisan tinker
   >>> $post = Post::create([...]);
   ```

4. **If Fix Doesn't Work**
   - STOP
   - Count: How many fixes have you tried?
   - If < 3: Return to Phase 1 with new information
   - **If ≥ 3: STOP and question the approach**

5. **If 3+ Fixes Failed: Question Architecture**
   ```
   Pattern indicating architectural problem:
   - Each fix reveals new shared state/coupling
   - Fixes require "massive refactoring"
   - Each fix creates new symptoms elsewhere
   
   STOP and question fundamentals:
   - Is this Laravel pattern correct?
   - Should we use a different approach (repository/service)?
   - Are we fighting the framework?
   
   Discuss with team before attempting more fixes.
   ```

## Laravel-Specific Debug Techniques

### Eloquent Debugging
```php
// See actual SQL
$posts = Post::where('status', 'published');
dd($posts->toSql(), $posts->getBindings());

// Check relationship loading
$post = Post::first();
$post->relationLoaded('user'); // false
$post->load('user');
$post->relationLoaded('user'); // true

// Prevent lazy loading (catch N+1)
Model::preventLazyLoading(!app()->isProduction());
```

### Route Debugging
```bash
# List all routes
php artisan route:list

# Find specific route
php artisan route:list --name=posts

# Check route exists
php artisan tinker
>>> route('posts.show', 1);
```

### Queue Debugging
```bash
# See failed jobs
php artisan queue:failed

# Retry failed job
php artisan queue:retry <id>

# Work queue with verbose output
php artisan queue:work --verbose

# Check job payload
php artisan tinker
>>> DB::table('jobs')->first();
```

### Validation Debugging
```php
// See exact validation errors
protected function failedValidation(Validator $validator)
{
    Log::debug('Validation failed', [
        'errors' => $validator->errors()->toArray(),
        'input' => $this->all(),
    ]);
 

Related in Code Review