systematic-debugging-laravel
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSystematic 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 FIRSTIf 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:
-
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() -
Check Laravel Logsbash
# 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 -
Enable Debug Mode (Local Only)env
APP_DEBUG=true APP_ENV=local -
Use Laravel Telescopebash
composer require laravel/telescope --dev php artisan telescope:install php artisan migrate # Access at /telescope # View: Requests, Queries, Jobs, Events, Exceptions -
Check Recent Changesbash
# 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 -
Reproduce Consistentlybash
# Can you trigger it every time? php artisan tinker >>> App\Models\Post::first(); # Try in different environments APP_ENV=testing php artisan test -
Trace Data Flow for Eloquent Issuesphp
// 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());
在尝试任何修复之前:
-
仔细阅读错误信息
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() -
查看Laravel日志bash
# 主Laravel日志 tail -f storage/logs/laravel.log # 查找特定错误 grep "SQLSTATE" storage/logs/laravel.log # 日志过大时清空 > storage/logs/laravel.log -
启用调试模式(仅本地环境)env
APP_DEBUG=true APP_ENV=local -
使用Laravel Telescopebash
composer require laravel/telescope --dev php artisan telescope:install php artisan migrate # 访问地址 /telescope # 查看:请求、查询、任务、事件、异常 -
检查最近的变更bash
# 哪些变更可能导致此问题? git log --oneline -10 git diff HEAD~5 # 检查迁移是否已执行 php artisan migrate:status # 检查配置是否已缓存 php artisan config:show -
稳定复现问题bash
# 能否每次都触发问题? php artisan tinker >>> App\Models\Post::first(); # 在不同环境中尝试 APP_ENV=testing php artisan test -
追踪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:
-
Find Working Examples in Laravelbash
# 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 -
Compare Against Laravel Conventionsphp
// ❌ 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); } } -
Check Laravel Documentation
- Read the COMPLETE section, don't skim
- Follow examples exactly first
- Customize only after understanding
-
Identify Differencesphp
// 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 }
修复前先找到模式:
-
在Laravel中查找可行示例bash
# 搜索类似的可行代码 grep -r "belongsTo" app/Models/ grep -r "middleware" app/Http/ # 查看Laravel文档中的模式 # 检查其他可正常工作的模型 -
与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); } } -
查看Laravel文档
- 完整阅读相关章节,不要略读
- 先完全遵循示例
- 理解后再进行定制
-
找出差异php
// 可正常工作的模型 class User extends Model { protected $fillable = ['name', 'email']; } // 有问题的模型 - 差异:缺少批量赋值保护 class Post extends Model { // 未定义$fillable或$guarded }
Phase 3: Hypothesis and Testing
阶段3:假设与测试
Scientific method:
-
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 -
Test Minimallyphp
// Before (broken) protected $fillable = ['title', 'content']; // Test change (ONE variable) protected $fillable = ['title', 'content', 'user_id']; // Don't change multiple things at once -
Verify in Tinkerbash
php artisan tinker >>> $post = Post::create(['title' => 'Test', 'content' => 'Test', 'user_id' => 1]); >>> $post->user_id; // Should be 1 -
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
遵循科学方法:
-
形成单一假设
假设:“文章无法保存是因为批量赋值保护阻止了'user_id'字段” 预期结果:将'user_id'添加到$fillable中可解决问题 -
最小化测试php
// 之前的代码(有问题) protected $fillable = ['title', 'content']; // 测试变更(仅修改一个变量) protected $fillable = ['title', 'content', 'user_id']; // 不要同时修改多个内容 -
在Tinker中验证bash
php artisan tinker >>> $post = Post::create(['title' => 'Test', 'content' => 'Test', 'user_id' => 1]); >>> $post->user_id; // 应返回1 -
当你不确定时
- 直接说“我不理解为什么X会发生”
- 在Laravel GitHub issues中查找类似问题
- 在Laravel Discord/论坛中详细提问
- 不要不懂装懂
Phase 4: Implementation
阶段4:实施修复
Fix the root cause, not the symptom:
-
Create Failing Test Casephp
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 -
Implement Single Fixphp
// ONE fix for the root cause protected $fillable = ['title', 'content', 'user_id']; // NO bundled improvements like: // - Adding casts // - Refactoring methods // - Changing other code -
Verify Fixbash
# 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([...]); -
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
-
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.
修复根本原因,而非症状:
-
编写失败的测试用例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 -
实施单一修复php
// 针对根本原因的单一修复 protected $fillable = ['title', 'content', 'user_id']; // 不要附带其他改进,比如: // - 添加类型转换 // - 重构方法 // - 修改其他代码 -
验证修复效果bash
# 测试现在应通过 php artisan test --filter=user_can_create_post # 所有测试仍需通过 php artisan test # 手动验证 php artisan tinker >>> $post = Post::create([...]); -
如果修复无效
- 立即停止
- 统计:已经尝试了多少次修复?
- 如果少于3次:结合新信息返回阶段1
- 如果≥3次:停止并质疑当前方法
-
如果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
undefinedbash
undefinedList 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);
undefinedphp artisan tinker
route('posts.show', 1);
undefinedQueue Debugging
队列调试
bash
undefinedbash
undefinedSee 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();
undefinedphp artisan tinker
DB::table('jobs')->first();
undefinedValidation 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
快速参考
| Phase | Laravel-Specific Activities | Success Criteria |
|---|---|---|
| 1. Root Cause | Check logs, Telescope, Tinker, recent changes | Understand WHAT and WHY |
| 2. Pattern | Find working Laravel examples, check docs | Identify differences |
| 3. Hypothesis | Form theory, test in Tinker | Confirmed or new hypothesis |
| 4. Implementation | Create Pest test, fix, verify | Bug 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行为”都是因为对框架的误解
始终系统化地调查问题,理解根本原因,然后一次性正确修复。