未捕获的致命错误(如Parse error、Fatal error)会直接终止PHP脚本,导致后续ob_flush()和flush()无法执行;而Exception若未被try/catch捕获,也会触发Uncaught Exception致命错误。

PHP 实时输出(ob_flush() + flush())本身不会因异常自动停止,但一旦未捕获的致命错误(如 Fatal error、Parse error)发生,脚本会立即中止,后续的输出逻辑(包括 flush)根本不会执行 —— 所以你“看不到后续输出”,不是 flush 停了,是进程死了。
未捕获异常导致实时输出中断的真实原因
PHP 的输出缓冲机制和错误处理是解耦的。实时输出依赖于输出缓冲区状态和 Web 服务器(如 Apache/Nginx)及浏览器的行为;而异常是否中断脚本,取决于错误类型和错误处理器是否让脚本继续运行。
-
Notice/Warning不会终止脚本,实时输出照常(除非你手动die()) -
Parse error、Fatal error、TypeError(未被try/catch捕获时)会直接终止执行,flush()后面的代码不运行 -
Exception默认不终止脚本?错 —— 未被try/catch包裹的throw会触发Fatal error: Uncaught Exception,效果等同于致命错误 - 即使开了
display_errors = On,错误信息也走标准输出流,可能混在你 flush 的内容里,也可能被缓冲卡住(尤其 Nginx + FastCGI 场景)
用 set_exception_handler + ob_end_flush 安全兜底
全局异常处理器无法用 try/catch 继续执行后续逻辑,但它能让你在脚本退出前做最后的输出。关键是:必须在 handler 中主动清空并发送缓冲区,且不能依赖 flush() 单独生效(需配对 ob_end_flush() 或强制关缓冲)。
set_exception_handler(function ($e) {
// 确保有输出缓冲开启(否则 ob_get_level() === 0)
if (ob_get_level()) {
ob_end_clean(); // 清掉可能出错前积压的脏数据
}
// 输出自定义错误行(注意换行,便于前端按行解析)
echo "[ERROR] " . $e->getMessage() . "\n";
// 强制刷新并关闭所有输出缓冲
@ob_end_flush();
@flush();
// 可选:sleep(1) 让浏览器更大概率收到这行(尤其长连接场景)
});
- 不要在 handler 里再开
ob_start()—— 此时缓冲已不稳定,容易报Cannot modify header information -
@抑制ob_end_flush()失败警告,因为某些环境(CLI、fpm 非响应模式)下它本来就不支持 - 该方式仅对
Exception有效;Error类(PHP 7+)需额外注册set_error_handler或set_exception_handler与set_error_handler共同覆盖(推荐统一用set_exception_handler,它也能捕获Error)
实时输出中 try/catch 的正确姿势
如果你的实时输出逻辑里调用了可能抛异常的函数(如 file_get_contents()、数据库查询、API 调用),必须在每个可能中断的位置用 try/catch 包裹,并在 catch 块内显式输出 + flush,否则异常一出就静默终止。
立即学习“PHP免费学习笔记(深入)”;
echo "Step 1 done\n"; flush(); ob_flush();
try {
$data = risky_api_call();
echo "API success\n";
} catch (Exception $e) {
echo "[API FAILED] " . $e->getMessage() . "\n";
}
flush(); ob_flush(); // 每次输出后都 flush,别省
echo "Step 2 starting...\n"; flush(); ob_flush();
- 不要把整个长循环包在一个
try里 —— 一旦中间某次迭代异常,后面全部跳过;应把异常点粒度拆细 -
ob_flush()和flush()要成对调用,顺序不能反(ob_flush()清 PHP 缓冲,flush()推送给 Web 服务器) - 某些 SAPI(如 PHP-FPM + Nginx)默认禁用
flush(),需确认fastcgi_buffering off;或fastcgi_max_temp_file_size 0;
真正难的不是捕获异常,而是确保异常发生时,你写进缓冲区的那几行字真能抵达浏览器 —— 这取决于缓冲层级(PHP output buffer / web server buffer / proxy buffer / browser render buffer)、SAPI 模式、甚至 HTTP/1.1 分块编码是否启用。线上环境务必用 curl -N 或前端 EventSource 监听原始响应流验证,别只靠 F12 Network 面板判断。











