systematic-debugging-laravel

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Systematic Debugging for Laravel

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.
在Laravel应用中,随意修复不仅浪费时间还会产生新的bug。快速补丁会掩盖潜在问题。
核心原则: 始终在尝试修复前找到根本原因。仅修复症状等同于失败。
违反此流程的任何环节,都是违背调试的本质。

The Iron Law

铁律

NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
If you haven't completed Phase 1, you cannot propose fixes.
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
如果尚未完成第一阶段,不得提出修复方案。

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
适用于所有Laravel技术问题:
  • 测试失败
  • Eloquent查询问题
  • 认证/授权bug
  • 验证失败
  • 队列任务失败
  • 路由错误
  • 迁移问题
  • N+1查询问题
  • 性能问题
尤其在以下场景必须使用:
  • 处于时间压力下
  • “只需快速修复一下”看似可行
  • 已经尝试过多种修复方案
  • 之前的修复无效
  • 并未完全理解问题

The Four Phases

四个阶段

You MUST complete each phase before proceeding to the next.
必须完成上一阶段后才能进入下一阶段。

Phase 1: Root Cause Investigation

阶段1:根本原因调查

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());
在尝试任何修复之前:
  1. 仔细阅读错误信息
    SQLSTATE[23000]: Integrity constraint violation
    → 检查外键约束,而非代码bug
    
    Class 'App\Models\Post' not found
    → 检查命名空间,运行composer dump-autoload
    
    Method Illuminate\Database\Eloquent\Collection::save does not exist
    → get()返回Collection,而非Model。使用first()或update()
  2. 查看Laravel日志
    bash
    # 主Laravel日志
    tail -f storage/logs/laravel.log
    
    # 查找特定错误
    grep "SQLSTATE" storage/logs/laravel.log
    
    # 日志过大时清空
    > storage/logs/laravel.log
  3. 启用调试模式(仅本地环境)
    env
    APP_DEBUG=true
    APP_ENV=local
  4. 使用Laravel Telescope
    bash
    composer require laravel/telescope --dev
    php artisan telescope:install
    php artisan migrate
    
    # 访问地址 /telescope
    # 查看:请求、查询、任务、事件、异常
  5. 检查最近的变更
    bash
    # 哪些变更可能导致此问题?
    git log --oneline -10
    git diff HEAD~5
    
    # 检查迁移是否已执行
    php artisan migrate:status
    
    # 检查配置是否已缓存
    php artisan config:show
  6. 稳定复现问题
    bash
    # 能否每次都触发问题?
    php artisan tinker
    >>> App\Models\Post::first();
    
    # 在不同环境中尝试
    APP_ENV=testing php artisan test
  7. 追踪Eloquent问题的数据流
    php
    // 启用查询日志
    DB::listen(function ($query) {
        Log::debug('Query executed', [
            'sql' => $query->sql,
            'bindings' => $query->bindings,
            'time' => $query->time,
        ]);
    });
    
    // 或在特定代码中启用
    DB::enableQueryLog();
    $posts = Post::with('user')->get();
    dd(DB::getQueryLog());

Phase 2: Pattern Analysis

阶段2:模式分析

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
    }
修复前先找到模式:
  1. 在Laravel中查找可行示例
    bash
    # 搜索类似的可行代码
    grep -r "belongsTo" app/Models/
    grep -r "middleware" app/Http/
    
    # 查看Laravel文档中的模式
    # 检查其他可正常工作的模型
  2. 与Laravel约定进行对比
    php
    // ❌ 当前代码
    class Post extends Model {
        public function author() {
            return $this->hasOne(User::class, 'id', 'user_id');
        }
    }
    
    // ✅ Laravel约定写法
    class Post extends Model {
        public function user(): BelongsTo {
            return $this->belongsTo(User::class);
        }
    }
  3. 查看Laravel文档
    • 完整阅读相关章节,不要略读
    • 先完全遵循示例
    • 理解后再进行定制
  4. 找出差异
    php
    // 可正常工作的模型
    class User extends Model {
        protected $fillable = ['name', 'email'];
    }
    
    // 有问题的模型 - 差异:缺少批量赋值保护
    class Post extends Model {
        // 未定义$fillable或$guarded
    }

Phase 3: Hypothesis and Testing

阶段3:假设与测试

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
遵循科学方法:
  1. 形成单一假设
    假设:“文章无法保存是因为批量赋值保护阻止了'user_id'字段”
    
    预期结果:将'user_id'添加到$fillable中可解决问题
  2. 最小化测试
    php
    // 之前的代码(有问题)
    protected $fillable = ['title', 'content'];
    
    // 测试变更(仅修改一个变量)
    protected $fillable = ['title', 'content', 'user_id'];
    
    // 不要同时修改多个内容
  3. 在Tinker中验证
    bash
    php artisan tinker
    >>> $post = Post::create(['title' => 'Test', 'content' => 'Test', 'user_id' => 1]);
    >>> $post->user_id; // 应返回1
  4. 当你不确定时
    • 直接说“我不理解为什么X会发生”
    • 在Laravel GitHub issues中查找类似问题
    • 在Laravel Discord/论坛中详细提问
    • 不要不懂装懂

Phase 4: Implementation

阶段4:实施修复

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.
修复根本原因,而非症状:
  1. 编写失败的测试用例
    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);
    });
    
    // 运行测试,先确认它会失败
    php artisan test --filter=user_can_create_post
  2. 实施单一修复
    php
    // 针对根本原因的单一修复
    protected $fillable = ['title', 'content', 'user_id'];
    
    // 不要附带其他改进,比如:
    // - 添加类型转换
    // - 重构方法
    // - 修改其他代码
  3. 验证修复效果
    bash
    # 测试现在应通过
    php artisan test --filter=user_can_create_post
    
    # 所有测试仍需通过
    php artisan test
    
    # 手动验证
    php artisan tinker
    >>> $post = Post::create([...]);
  4. 如果修复无效
    • 立即停止
    • 统计:已经尝试了多少次修复?
    • 如果少于3次:结合新信息返回阶段1
    • 如果≥3次:停止并质疑当前方法
  5. 如果3次以上修复失败:质疑架构
    表明架构存在问题的模式:
    - 每次修复都会暴露出新的共享状态/耦合
    - 修复需要“大规模重构”
    - 每次修复都会在其他地方产生新的症状
    
    停止并质疑基础问题:
    - 这个Laravel模式是否正确?
    - 我们是否应该使用不同的方法(仓库/服务模式)?
    - 我们是否在与框架作对?
    
    在尝试更多修复前与团队讨论。

Laravel-Specific Debug Techniques

Laravel特定调试技巧

Eloquent Debugging

Eloquent调试

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());
php
// 查看实际执行的SQL
$posts = Post::where('status', 'published');
dd($posts->toSql(), $posts->getBindings());

// 检查关系是否已加载
$post = Post::first();
$post->relationLoaded('user'); // false
$post->load('user');
$post->relationLoaded('user'); // true

// 防止懒加载(捕获N+1问题)
Model::preventLazyLoading(!app()->isProduction());

Route Debugging

路由调试

bash
undefined
bash
undefined

List all routes

列出所有路由

php artisan route:list
php artisan route:list

Find specific route

查找特定路由

php artisan route:list --name=posts
php artisan route:list --name=posts

Check route exists

检查路由是否存在

php artisan tinker
route('posts.show', 1);
undefined
php artisan tinker
route('posts.show', 1);
undefined

Queue Debugging

队列调试

bash
undefined
bash
undefined

See failed jobs

查看失败的任务

php artisan queue:failed
php artisan queue:failed

Retry failed job

重试失败的任务

php artisan queue:retry <id>
php artisan queue:retry <id>

Work queue with verbose output

以详细输出运行队列

php artisan queue:work --verbose
php artisan queue:work --verbose

Check job payload

检查任务负载

php artisan tinker
DB::table('jobs')->first();
undefined
php artisan tinker
DB::table('jobs')->first();
undefined

Validation Debugging

验证调试

php
// See exact validation errors
protected function failedValidation(Validator $validator)
{
    Log::debug('Validation failed', [
        'errors' => $validator->errors()->toArray(),
        'input' => $this->all(),
    ]);
    
    parent::failedValidation($validator);
}
php
// 查看具体的验证错误
protected function failedValidation(Validator $validator)
{
    Log::debug('Validation failed', [
        'errors' => $validator->errors()->toArray(),
        'input' => $this->all(),
    ]);
    
    parent::failedValidation($validator);
}

Red Flags - STOP and Follow Process

危险信号——停止并遵循流程

If you catch yourself thinking:
  • "Quick fix for now, investigate later"
  • "Just try changing X and see if it works"
  • "Add protected $guarded = [] to see if that helps"
  • "Skip the test, I'll manually verify"
  • "It's probably the relationship definition"
  • "I don't fully understand Eloquent but this might work"
  • "The docs say X but I'll adapt it differently"
  • "One more fix attempt" (when already tried 2+)
ALL of these mean: STOP. Return to Phase 1.
如果你发现自己有以下想法:
  • “先快速修复,之后再调查”
  • “试试修改X看看能不能行”
  • “添加protected $guarded = []看看是否有用”
  • “跳过测试,我手动验证就行”
  • “可能是关系定义的问题”
  • “我不完全理解Eloquent,但这样可能有用”
  • “文档说X,但我要换个方式”
  • “再试一次修复”(已经尝试过2次以上)
所有这些想法都意味着:停止操作。回到阶段1。

Common Laravel Debugging Scenarios

Laravel常见调试场景

Scenario 1: N+1 Query Problem

场景1:N+1查询问题

Phase 1: Detect it
- Enable Model::preventLazyLoading()
- Exception thrown showing the problem

Phase 2: Find the pattern
- Check working code that uses with()
- Identify which relationship is lazy loading

Phase 3: Hypothesis
- "Adding with('user') will prevent the N+1"

Phase 4: Fix
- Add test that counts queries
- Add with('user') to the query
- Verify query count reduced
阶段1:检测问题
- 启用Model::preventLazyLoading()
- 抛出异常显示问题所在

阶段2:找到模式
- 查看使用with()的可行代码
- 确定哪个关系在懒加载

阶段3:提出假设
- “添加with('user')可以解决N+1问题”

阶段4:修复
- 编写统计查询次数的测试
- 在查询中添加with('user')
- 验证查询次数已减少

Scenario 2: Route Model Binding Not Working

场景2:路由模型绑定无效

Phase 1: Investigate
- Check route definition: /posts/{post}
- Check controller parameter: Post $post
- Check if using custom key

Phase 2: Pattern
- Compare with working route binding
- Check Post model for getRouteKeyName()

Phase 3: Hypothesis
- "Parameter name doesn't match or model not found"

Phase 4: Fix
- Ensure route parameter matches method parameter
- Or customize: public function getRouteKeyName() { return 'slug'; }
阶段1:调查
- 检查路由定义:/posts/{post}
- 检查控制器参数:Post $post
- 检查是否使用了自定义键

阶段2:模式对比
- 与可行的路由绑定对比
- 检查Post模型的getRouteKeyName()方法

阶段3:提出假设
- “参数名称不匹配或模型未找到”

阶段4:修复
- 确保路由参数与方法参数匹配
- 或自定义:public function getRouteKeyName() { return 'slug'; }

Scenario 3: Mass Assignment Exception

场景3:批量赋值异常

Phase 1: Error says "Add [field] to fillable property"
Phase 2: Check other models' $fillable arrays
Phase 3: Hypothesis: "Field not in $fillable"
Phase 4: Add field to $fillable, test
阶段1:错误提示“将[字段]添加到fillable属性中”
阶段2:检查其他模型的$fillable数组
阶段3:假设:“字段未在$fillable中”
阶段4:将字段添加到$fillable,测试

Integration with Laravel Agents

与Laravel工具集成

  • Use laravel-debugger for Laravel-specific debugging help
  • Use laravel-testing-expert for creating failing tests (Phase 4)
  • Use eloquent-specialist for relationship debugging
  • Use laravel-performance-optimizer for performance issues
  • 使用laravel-debugger获取Laravel特定调试帮助
  • 使用laravel-testing-expert编写失败测试用例(阶段4)
  • 使用eloquent-specialist进行关系调试
  • 使用laravel-performance-optimizer解决性能问题

Quick Reference

快速参考

PhaseLaravel-Specific ActivitiesSuccess Criteria
1. Root CauseCheck logs, Telescope, Tinker, recent changesUnderstand WHAT and WHY
2. PatternFind working Laravel examples, check docsIdentify differences
3. HypothesisForm theory, test in TinkerConfirmed or new hypothesis
4. ImplementationCreate Pest test, fix, verifyBug resolved, tests pass
阶段Laravel特定操作成功标准
1. 根本原因查看日志、Telescope、Tinker、最近变更理解问题是什么及为什么发生
2. 模式分析查找Laravel可行示例、查看文档找出差异点
3. 假设验证形成理论、在Tinker中测试假设得到确认或形成新假设
4. 实施修复编写Pest测试、修复、验证Bug已解决,测试通过

Remember

谨记

  • Laravel has excellent error messages - read them fully
  • Use Telescope for comprehensive debugging
  • Tinker is your friend for testing hypotheses
  • Follow Laravel conventions - fighting the framework causes bugs
  • 95% of "weird Laravel behavior" is misunderstanding the framework
Always investigate systematically, understand the root cause, then fix once correctly.
  • Laravel的错误信息非常完善——请完整阅读
  • 使用Telescope进行全面调试
  • Tinker是验证假设的好帮手
  • 遵循Laravel约定——与框架作对会导致bug
  • 95%的“奇怪Laravel行为”都是因为对框架的误解
始终系统化地调查问题,理解根本原因,然后一次性正确修复。