mcpField
S
systematic-debugging-laravel

systematic-debugging-laravel

github.com/iserter开发4882 次安装27 周前更新
代码仓库

关于 systematic-debugging-laravel

面向 Laravel 开发者的调试技能,要求先调查根因再修改代码。四阶段流程依次进行问题调查、模式分析、假设验证和修复确认。

主要功能

  1. 01四阶段根因调查流程
  2. 02Laravel 日志、Telescope 和 Tinker 技巧
  3. 03模式对比与假设测试
  4. 04通过测试验证修复

适用场景

  1. 01调查测试失败或路由错误
  2. 02追踪 Eloquent 查询和关联问题
  3. 03诊断队列、验证或性能问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/iserter/laravel-claude-agents --skill systematic-debugging-laravel

安装后在 Claude Code 中输入/systematic-debugging-laravel确认已加载。

详细介绍

Laravel 系统性调试

概述

在 Laravel 应用中,随机修复既浪费时间,又会引入新错误。快速打补丁会掩盖根本问题。

核心原则: 在尝试修复之前,务必先找到根本原因。只修复症状等于失败。

违背本流程的字面要求,就是违背调试的本意。

铁律

NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST

如果尚未完成第 1 阶段,就不能提出修复方案。

适用场景

遇到任何 Laravel 技术问题时都应使用:

  • 测试失败
  • Eloquent 查询问题
  • 身份验证/授权错误
  • 验证失败
  • 队列任务失败
  • 路由错误
  • 迁移问题
  • N+1 查询问题
  • 性能问题

遇到以下情况时尤其要使用:

  • 时间紧迫
  • 看起来有个显而易见的“快速修复”
  • 已经尝试了多种修复方案
  • 上一次修复没有奏效
  • 你并未完全理解问题

四个阶段

必须完成每个阶段后,才能进入下一阶段。

第 1 阶段:调查根本原因

在尝试任何修复之前:

  1. 仔细阅读错误消息

    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. 检查 Laravel 日志

    # 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. 启用调试模式(仅限本地)

    APP_DEBUG=true
    APP_ENV=local
    
  4. 使用 Laravel Telescope

    composer require laravel/telescope --dev
    php artisan telescope:install
    php artisan migrate
    
    # Access at /telescope
    # View: Requests, Queries, Jobs, Events, Exceptions
    
  5. 检查近期变更

    # 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. 稳定复现问题

    # Can you trigger it every time?
    php artisan tinker
    >>> App\Models\Post::first();
    
    # Try in different environments
    APP_ENV=testing php artisan test
    
  7. 追踪 Eloquent 问题中的数据流

    // 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());
    
第 2 阶段:模式分析

修复前先找出模式:

  1. 在 Laravel 中查找可正常工作的示例

    # 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. 对照 Laravel 惯例

    // ❌ 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. 检查 Laravel 文档

    • 阅读完整章节,不要略读
    • 首先严格按照示例操作
    • 理解之后再进行定制
  4. 识别差异

    // 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
    }
    
第 3 阶段:提出假设并测试

遵循科学方法:

  1. 提出单一假设

    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. 进行最小化测试

    // Before (broken)
    protected $fillable = ['title', 'content'];
    
    // Test change (ONE variable)
    protected $fillable = ['title', 'content', 'user_id'];
    
    // Don't change multiple things at once
    
  3. 在 Tinker 中验证

    php artisan tinker
    >>> $post = Post::create(['title' => 'Test', 'content' => 'Test', 'user_id' => 1]);
    >>> $post->user_id; // Should be 1
    
  4. 遇到不确定的情况时

    • 直接说“我不明白 X 为什么会发生”
    • 在 Laravel GitHub issues 中查找类似问题
    • 在 Laravel Discord/论坛中提供具体信息并寻求帮助
    • 不要假装自己知道
第 4 阶段:实施

修复根本原因,而非症状:

  1. 创建会失败的测试用例

    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. 实施单一修复

    // ONE fix for the root cause
    protected $fillable = ['title', 'content', 'user_id'];
    
    // NO bundled improvements like:
    // - Adding casts
    // - Refactoring methods
    // - Changing other code
    
  3. 验证修复

    # 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. 如果修复无效

    • 停下
    • 统计你尝试过多少种修复方案
    • 如果少于 3 种:根据新信息返回第 1 阶段
    • 如果不少于 3 种:停下并质疑当前方案
  5. 如果 3 种或更多修复方案都失败:质疑架构

    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 专属调试技巧

Eloquent 调试
// 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());
路由调试
# 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);
队列调试
# 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();
验证调试
// See exact validation errors
protected function failedValidation(Validator $validator)
{
    Log::debug('Validation failed', [
        'errors' => $validator->errors()->toArray(),
        'input' => $this->all(),
    ]);
    
    parent::failedValidation($validator);
}

警示信号——停下并遵循流程

如果你发现自己在想:

  • “先快速修复,之后再调查”
  • “试着改一下 X,看看是否有效”
  • “添加 protected $guarded = [],看看是否有帮助”
  • “跳过测试,我会手动验证”
  • “问题可能出在关系定义上”
  • “我并未完全理解 Eloquent,但这也许能奏效”
  • “文档说要用 X,但我会换一种方式”
  • “再试一次修复”(已经尝试过 2 次或更多时)

以上任何一种情况都意味着:停下。返回第 1 阶段。

常见 Laravel 调试场景

场景 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
场景 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'; }
场景 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

与 Laravel Agent 的集成

  • 使用 laravel-debugger 获取 Laravel 专属调试帮助
  • 使用 laravel-testing-expert 创建失败的测试(第 4 阶段)
  • 使用 eloquent-specialist 调试关系
  • 使用 laravel-performance-optimizer 处理性能问题

快速参考

阶段Laravel 特定活动成功标准
1. 根本原因检查日志、Telescope、Tinker 和近期更改理解是什么以及为什么
2. 模式查找可用的 Laravel 示例,查看文档找出差异
3. 假设提出理论,在 Tinker 中测试得到证实或提出新假设
4. 实施创建 Pest 测试、修复问题并验证Bug 已解决,测试通过

请记住

  • Laravel 的错误消息非常出色——请完整阅读
  • 使用 Telescope 进行全面调试
  • Tinker 是测试假设的好帮手
  • 遵循 Laravel 约定——与框架对着干会导致 Bug
  • 95% 的“奇怪 Laravel 行为”都是因为误解了框架

始终系统地调查,理解根本原因,然后一次性正确修复。

评论

支持 Markdown