关于 systematic-debugging-laravel
面向 Laravel 开发者的调试技能,要求先调查根因再修改代码。四阶段流程依次进行问题调查、模式分析、假设验证和修复确认。
主要功能
- 01四阶段根因调查流程
- 02Laravel 日志、Telescope 和 Tinker 技巧
- 03模式对比与假设测试
- 04通过测试验证修复
适用场景
- 01调查测试失败或路由错误
- 02追踪 Eloquent 查询和关联问题
- 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 阶段:调查根本原因
在尝试任何修复之前:
-
仔细阅读错误消息
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() -
检查 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 -
启用调试模式(仅限本地)
APP_DEBUG=true APP_ENV=local -
使用 Laravel Telescope
composer require laravel/telescope --dev php artisan telescope:install php artisan migrate # Access at /telescope # View: Requests, Queries, Jobs, Events, Exceptions -
检查近期变更
# 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 -
稳定复现问题
# Can you trigger it every time? php artisan tinker >>> App\Models\Post::first(); # Try in different environments APP_ENV=testing php artisan test -
追踪 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 阶段:模式分析
修复前先找出模式:
-
在 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 -
对照 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); } } -
检查 Laravel 文档
- 阅读完整章节,不要略读
- 首先严格按照示例操作
- 理解之后再进行定制
-
识别差异
// 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 阶段:提出假设并测试
遵循科学方法:
-
提出单一假设
Hypothesis: "Posts aren't saving because mass assignment protection is blocking the 'user_id' field" Expected: Adding 'user_id' to $fillable will fix it -
进行最小化测试
// Before (broken) protected $fillable = ['title', 'content']; // Test change (ONE variable) protected $fillable = ['title', 'content', 'user_id']; // Don't change multiple things at once -
在 Tinker 中验证
php artisan tinker >>> $post = Post::create(['title' => 'Test', 'content' => 'Test', 'user_id' => 1]); >>> $post->user_id; // Should be 1 -
遇到不确定的情况时
- 直接说“我不明白 X 为什么会发生”
- 在 Laravel GitHub issues 中查找类似问题
- 在 Laravel Discord/论坛中提供具体信息并寻求帮助
- 不要假装自己知道
第 4 阶段:实施
修复根本原因,而非症状:
-
创建会失败的测试用例
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 -
实施单一修复
// ONE fix for the root cause protected $fillable = ['title', 'content', 'user_id']; // NO bundled improvements like: // - Adding casts // - Refactoring methods // - Changing other code -
验证修复
# 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([...]); -
如果修复无效
- 停下
- 统计你尝试过多少种修复方案
- 如果少于 3 种:根据新信息返回第 1 阶段
- 如果不少于 3 种:停下并质疑当前方案
-
如果 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