用 Xdebug 或 PHP Debugger 调试,手动或借助 AI
JumPHP IDE 带有真正的单步调试器:你可以让程序停在某一行,查看变量,修改其中一个,然后一步一步继续。它使用 Xdebug 的协议 DBGp,因此可以驱动两个引擎:
- Xdebug,经典引擎(默认);
- PHP Debugger,较新的 DBGp 调试器,免费开源,兼容 Xdebug 的设置,开销几乎为零。
在 IDE 中两者的用法完全相同。你也可以把调试器交给 AI(Claude Code):它自己设置断点、运行程序、读取变量并找出 bug。
该选哪个引擎
| Xdebug | PHP Debugger | |
|---|---|---|
| 在 JumPHP 中 | 默认,所有运行时 | 桌面运行时(PHP 8.2 至 8.5) |
| 开销 | PHP 明显变慢 | "near-zero overhead when idle",始终开启,无需设置触发条件 |
async 运行时(PHP 8.6,runtime: async) |
支持 | 不支持:改用 Xdebug,IDE 会提示 |
| AI | JumPHP 的 MCP 服务器(bin/jumphp debug) |
它自己的 MCP 服务器(dbgp mcp),还能看到脚本的输出和 PHP 警告 |
实际上:日常调试、长时间运行(要跑 600 帧的游戏、测试套件)以及 AI 调试,请选 PHP Debugger。如果你使用 async 运行时(线程),或需要只有 Xdebug 才有的功能,就继续用 Xdebug。两者在 IDE 中可以互换,所以可以在同一个项目上都试试。
PHP Debugger 自己的页面没有给出基准测试数字:它只说在你调试之前开销几乎为零。如果差别对你很重要,请在自己的项目上测量。
在 IDE 中选择引擎
- 标题栏的调试按钮(运行旁边的虫子图标,
Shift+F9)会展开一个菜单:调试方式(本次运行使用 Xdebug 还是 PHP Debugger)和 默认引擎(此后普通点击所用的引擎)。在按钮上右键单击会打开同一个菜单。 - 第一次使用某个引擎运行时会构建一次 Docker 镜像(约一分钟)。之后的运行立即启动。
- 如果程序始终没有连上调试器(它先结束了,或 3 分钟后仍无动静),会有通知提醒你。原因通常可以在“运行”窗口的输出中找到。
在终端中选择引擎
一切照旧通过 bin/jumphp:
# Xdebug (default)
JUMPHP_XDEBUG=9003 bin/jumphp test pong
# PHP Debugger
JUMPHP_DEBUGGER=php-debugger JUMPHP_XDEBUG=9003 bin/jumphp test pong
JUMPHP_XDEBUG=<port>开启单步调试:PHP 连接到在你电脑该端口上监听的调试器(composer、phpstan 和php -l不使用它)。JUMPHP_DEBUGGER=xdebug|php-debugger选择引擎(默认xdebug)。- 使用
php-debugger时,bin/jumphp会基于桌面镜像一次性构建镜像docker/desktop-phpdebugger,桌面镜像本身保持不变。 - 在 async 运行时(PHP 8.6)上会显示消息 "PHP Debugger does not run on PHP 8.6 (async runtime): Xdebug is used",并改用 Xdebug 运行。
在 IDE 中手动调试
- 断点:点击 PHP 代码行左侧的边槽。右键单击可设置条件(一个 PHP 表达式:仅当为真时才停下,例如
$this->score < 0);Ctrl+F8在光标处设置断点。断点按项目保存,并会在你编辑时跟随所在行移动。 - 调试(
Shift+F9):程序开始运行,并停在第一个断点上。该行被标记,Debug 工具窗口随之打开。 - 查看:调用栈(点击某个栈帧查看其变量)、按需读取的变量树、watches,以及一个可在停下的位置求值任意 PHP 表达式的输入框。
- 修改值:双击变量或按
F2,输入一个 PHP 表达式(9、"Ada"、[1, 2]、null),然后用新值继续。 - 移动:继续
F9、单步跳过F8、单步进入F7、单步跳出Shift+F8、运行到光标处Alt+F9、停止。 - 异常:在异常处停止…(Debug 窗口的警告图标):
*或类名(TypeError、App\GameOver)。无论是否被捕获,它都会停在异常被抛出的地方,并显示是哪个异常及其消息。
悬停检查器 (将在下一次 IDE 更新中提供):程序停在断点上时,把鼠标移到编辑器中的变量上,会出现其值的可展开树,带有 Copy 和 Edit。
游戏小提示:请调试无头运行(test、shot):停在窗口里的游戏在你恢复之前会一直卡住。
借助 AI 调试
AI 用调试器比用 var_dump() 调试得更好:它读取真实的值,一次问一个问题,并且不会改动你的代码。有两个 MCP 服务器可用。同一时间只使用其中一个(两者都监听 9003 端口)。
1. JumPHP 服务器:bin/jumphp debug
IDE 自己的调试器,作为 MCP 服务器提供(在 Docker 中运行,使用 Xdebug):
claude mcp add jumphp-debug -- bin/jumphp debug
它的工具:
| 工具 | 作用 |
|---|---|
debug_breakpoints |
设置文件的断点(lines,或 {line, condition}),不带路径时列出断点,clear_all |
debug_exceptions |
会让程序停下的异常(* = 全部,[] = 无) |
debug_wait |
等待程序停在断点或异常上,然后给出位置、调用栈和变量 |
debug_state |
停在哪里、调用栈、某个栈帧的变量 |
debug_step |
step_over、step_into、step_out、run、stop:等待下一次停止 |
debug_run_to |
运行到某一行 |
debug_eval |
在停下的位置对 PHP 表达式求值 |
debug_variables |
某个栈帧的变量,或其中某个变量的子项($this->player、$list[3]) |
debug_set_value |
给变量赋新值(一个 PHP 表达式) |
路径相对于项目。Claude 会自己在后台用 JUMPHP_XDEBUG=9003 启动程序,然后调用 debug_wait。
2. PHP Debugger 的服务器:dbgp mcp
PHP Debugger 在一个小程序 dbgp 中自带 MCP 服务器(MIT 许可证;Linux 和 macOS,amd64 和 arm64;Windows amd64)。
# install (macOS / Linux)
curl -fsSL https://github.com/php-debugger/dbgp-client/releases/latest/download/install.sh | sh
dbgp -version
# for this project / for all your projects
claude mcp add dbgp -- dbgp mcp
claude mcp add --scope user dbgp -- dbgp mcp
在 Claude Code 中,/mcp 会显示它已连接。它的工具,按 PHP Debugger 文档中的名称:
- 连接:
status、listen、unlisten、sessions、wait_for_session、detach; - 断点:
add_breakpoint、remove_breakpoint、breakpoints; - 运行:
continue、wait_for_stop、stop; - 查看:
stack、variables、variable、variable_value、source、output、warnings; - 操作:
eval、set_variable; - Docker / 远程:
add_path_mapping、path_mappings。
常用参数(dbgp mcp -h 查看全部):-php-port PORT(PHP 连接的端口,默认 9003)、-mcp-port PORT、-idekey KEY、-map LOCAL=REMOTE(路径映射,可重复)以及 -no-eval,它禁止 eval、set_variable 和条件断点:AI 只能查看。它不会隐藏值,所以密码和密钥对 AI 仍然可见。
通过 bin/jumphp,JUMPHP_DEBUGGER=php-debugger bin/jumphp debug 会替你启动 dbgp mcp(你的电脑上需要已安装 dbgp)。然后用相同的引擎启动程序:
JUMPHP_DEBUGGER=php-debugger JUMPHP_XDEBUG=9003 bin/jumphp test pong
一次完整的会话
你对 Claude Code 说:
在
pong游戏中,球越过左边球拍时分数加 2 而不是 1。请使用调试器,不要用 var_dump。 在增加分数的位置设置断点(看PlayScene),用JUMPHP_XDEBUG=9003运行bin/jumphp test pong,在断点处停下,读取变量,告诉我原因。先把原因给我看,再去修复。
AI 随后用上述工具这样做:
- 找到加分的那一行并在那里设置断点;如果这一行每帧都会执行,就加上条件(
$this->ballX < 0); - 在后台启动测试并等待停止(
debug_wait/wait_for_stop); - 读取
$this->score、$this->scored和调用栈,看是谁调用了这一行; - 执行一次跳出(
debug_step的step_out)来查看第二个调用者:这一分被计了两次; - 不修改任何内容就验证自己的想法:给
$this->scored赋值(debug_set_value/set_variable),继续运行,看分数是否保持正确; - 然后才修改代码,并在不设断点的情况下再次运行测试。
小技巧
- 直接说明:“使用调试器”。否则代理常常会退回到
var_dump()。 - 告诉它能复现 bug 的命令、你期望的结果,以及你怀疑的位置。
- 小步前进:几次
step_over,然后查看。条件断点($enemy->hp <= 0)比单步走 600 帧强得多。 - 让它读取变量、对表达式求值:不用
var_dump(),不用echo,事后也无需清理。 - 优先使用无头运行(
test、shot、php),而不是窗口。 - 机密:AI 能看到它读取的每个变量(密码、令牌、API 密钥)。请用测试数据调试;如果不想让它在你的程序中执行代码,对
dbgp使用-no-eval。 - 不要在你不调试的项目里一直连着调试器的 MCP 服务器:用
claude mcp remove移除它。
故障排除
什么都没停下 / "did not connect"。
端口上有调试器在监听吗(IDE 的 Debug 窗口,或某个 MCP 服务器)?程序必须用 JUMPHP_XDEBUG=<port> 启动,并使用与监听方相同的引擎。
端口已被占用。
9003 上有两个监听者:IDE 与 bin/jumphp debug(或 dbgp mcp),或者两个 IDE。停掉一个,或改用其他端口:JUMPHP_DEBUG_PORT=9010 bin/jumphp debug 配合 JUMPHP_XDEBUG=9010,或 dbgp -php-port 9010 mcp。
断点始终没有命中。
它设在没有代码的行(注释、空行、右花括号)上,或设在程序从未加载的文件里。运行 debug_state / 查看调用栈,看看程序在哪里。使用 -map 或 IDE 时,路径必须一致(见下文)。
Docker 中的路径。
bin/jumphp 把你的项目挂载到容器内的 /workspace:调试器报告 file:///workspace/...。IDE 和 bin/jumphp debug 会自动把它映射到项目文件,你给出的路径相对于项目。单独使用 dbgp mcp 时,需要映射文件夹:dbgp mcp -map /home/you/project=/workspace。
PHP 8.6(async 运行时)。
PHP Debugger 在那里无法运行:会使用 Xdebug。要使用 PHP Debugger,请在默认运行时(PHP 8.5)上调试,或在本次会话中移除 runtime: async。
浏览器游戏。
调试器在桌面运行时上工作。在浏览器中,请在桌面上用 bin/jumphp test 或 shot 复现 bug。
另请参阅 编写代码、运行和测试、IDE 中的 Claude 和 IDE Remote。