登录
ZH

用 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 中手动调试

  1. 断点:点击 PHP 代码行左侧的边槽。右键单击可设置条件(一个 PHP 表达式:仅当为真时才停下,例如 $this->score < 0);Ctrl+F8 在光标处设置断点。断点按项目保存,并会在你编辑时跟随所在行移动。
  2. 调试(Shift+F9):程序开始运行,并停在第一个断点上。该行被标记,Debug 工具窗口随之打开。
  3. 查看:调用栈(点击某个栈帧查看其变量)、按需读取的变量树、watches,以及一个可在停下的位置求值任意 PHP 表达式的输入框。
  4. 修改值:双击变量或按 F2,输入一个 PHP 表达式(9、"Ada"、[1, 2]、null),然后用新值继续。
  5. 移动:继续 F9、单步跳过 F8、单步进入 F7、单步跳出 Shift+F8、运行到光标处 Alt+F9、停止。
  6. 异常:在异常处停止…(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 随后用上述工具这样做:

  1. 找到加分的那一行并在那里设置断点;如果这一行每帧都会执行,就加上条件($this->ballX < 0);
  2. 在后台启动测试并等待停止(debug_wait / wait_for_stop);
  3. 读取 $this->score、$this->scored 和调用栈,看是谁调用了这一行;
  4. 执行一次跳出(debug_step 的 step_out)来查看第二个调用者:这一分被计了两次;
  5. 不修改任何内容就验证自己的想法:给 $this->scored 赋值(debug_set_value / set_variable),继续运行,看分数是否保持正确;
  6. 然后才修改代码,并在不设断点的情况下再次运行测试。

小技巧

  • 直接说明:“使用调试器”。否则代理常常会退回到 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。