Xdebug 3 的代码覆盖率必须配置 xdebug.mode=coverage,旧版 xdebug.coverage_enable 已被移除;需用 XDEBUG_CC_UNUSED 参数启用未执行行标记,且覆盖率数据须经 php-code-coverage 处理生成报告。

Xdebug 3 的代码覆盖率分析不能靠 xdebug.coverage_enable 这类旧配置启动——它已被彻底移除,必须用 xdebug.mode=coverage 才能生效,否则调用 xdebug_start_code_coverage() 会静默失败。
确认 Xdebug 3 已启用 coverage 模式
在 CLI 或 Web 环境中运行 php -v,输出中必须包含 with Xdebug v3.x.x 且无报错;再执行 php -i | grep "xdebug.mode",确认返回值含 coverage(如 debug,coverage 或纯 coverage)。若只显示 debug 或为空,说明模式未正确设置。
常见错误是沿用 Xdebug 2 的写法,在 php.ini 里写 xdebug.coverage_enable=1 ——Xdebug 3 完全忽略该配置项,也不会报错,只会让后续所有覆盖率函数失效。
- 正确配置只有一行:
xdebug.mode=coverage(可与其他模式用逗号拼接,如debug,coverage) - 不要设
xdebug.start_with_request=yes全局开启,它会让每个 CLI 脚本都启动覆盖率收集,严重拖慢执行速度 - CLI 下推荐按需启用:运行命令时加
PHP_IDE_CONFIG="serverName=cli" PHP_XDEBUG_MODE=coverage
xdebug_start_code_coverage() 的参数陷阱
该函数默认只记录「实际被执行过的行」,返回数组里不会出现未执行的行号,导致你误以为覆盖率是 100%——其实只是没暴露漏测点。
要真正识别出“哪些函数/行根本没被调用”,必须传参启用死代码检测:
-
xdebug_start_code_coverage(XDEBUG_CC_UNUSED):让xdebug_get_code_coverage()返回包含-1(未执行)和-2(不可执行)的完整行映射 - 加上
XDEBUG_CC_DEAD_CODE会进一步标记不可达分支,但性能开销显著增大,日常调试不建议开启 - 注意:
XDEBUG_CC_UNUSED是整数常量(值为 1),不是字符串,直接写数字或引用常量均可
示例:
Xdebug 3.4.1 是一款功能强大的 PHP 调试扩展工具,于 2025 年 1 月 6 日正式发布。作为 Xdebug 3.4 系列的首个修复版本,3.4.1 版在继承上一版本强大功能的同时,重点解决了稳定性问题。该版本不仅修复了访问超全局变量时可能引发的程序崩溃现象,还增强了对 Windows 平台 PIE 构建机制的支持,为广大 PHP 开发者提供了更加稳定的调试环境。这一版本适合所有
xdebug_start_code_coverage(XDEBUG_CC_UNUSED); require_once 'app.php'; $coverage = xdebug_get_code_coverage(); // 此时 $coverage 中未执行的行会标为 -1,而非直接缺失
生成 HTML 报告必须经由 php-code-coverage,不能直接解析 cachegrind
很多人试图把 Xdebug 生成的 cachegrind.out.* 文件喂给 phpcov 或自己解析,结果失败——因为 cachegrind 是性能分析格式,和代码覆盖率数据结构完全不同。
Xdebug 3 的覆盖率数据只能通过 xdebug_get_code_coverage() 获取原始数组,再交由 phpunit/php-code-coverage 库处理成标准报告。
- PHPUnit 运行时自动完成该流程:
php vendor/bin/phpunit --coverage-html coverage-report - 若需手动控制(比如集成到功能测试中),得先用
Coverage类加载数据:$coverage->append($rawData, $fileName) - 切勿尝试用
file_get_contents()读取xdebug://debug-eval类似路径——那是调试协议,不是文件系统路径
Web 请求中写入覆盖率文件的权限与时机问题
在 Web 环境(如入口 index.php)中调用 xdebug_start_code_coverage() 后,必须确保 register_shutdown_function() 能可靠触发,且目标目录有写权限,否则覆盖率文件会丢失或为空。
典型失败场景:
- 脚本因 fatal error 提前终止,
shutdown函数未执行 - Web 服务器(如 Nginx + PHP-FPM)配置了
request_terminate_timeout,超时后直接 kill 进程 - 覆盖率输出目录(如
/tmp/coverage)属主不是 PHP 进程用户,导致fopen()失败但无提示
建议做法:
- 只在开发环境的简单请求中这么做;生产或复杂路由下,改用 PHPUnit 驱动的单元测试覆盖率更稳定
- 写入前先
is_writable()检查目录,失败时error_log()记录明确信息 - 避免在
__destruct()或异常处理器中做覆盖率 dump,它们的执行上下文不可靠
最易被忽略的一点:Xdebug 3 的覆盖率数据是 per-request 的,无法跨多个 HTTP 请求自动合并——每次请求只能拿到单次执行的覆盖快照,聚合分析必须由外部工具完成。

















