ThinkPHP全局异常捕获需正确配置app_exception于config/app.php、render()必须返回Response实例、CLI环境需手动绑定异常处理器,三者缺一不可。

ThinkPHP 全局异常捕获不是配个类就完事,app_exception 配置写错位置、render() 返回值类型不对、CLI 环境没适配——这三处任一出错,你看到的还是原始 PHP 堆栈或空白页,而不是你写的 JSON 错误响应。
app_exception 配置必须落在 config/app.php 里
TP8 和 TP5.1+ 不再从 app/exception.php 自动加载处理器,只认 config/app.php 中的 'app_exception' 键。常见错误是:你在 Web 请求下改了配置,但跑 php think run 或单元测试时,命令行入口压根没加载这份配置。
-
'app_exception' => \app\exception\ExceptionHandle::class必须出现在config/app.php的return []数组中 - 多应用模式(
APP_MULTI = true)下,每个子应用的config/app.php都要单独配,根目录配置无效 - CLI 场景下需手动绑定:
App::bind('think\exception\Handle', \app\exception\ExceptionHandle::class),加在think命令文件顶部
render() 方法必须返回 Response 实例
TP8 对 render() 返回值做了强校验:如果不是 think\Response 实例,框架会自动用默认 HTML 模板二次包装你的输出——哪怕你写了 return json([]),最终响应体里也会混入框架 footer 和样式。
- API 场景下,明确写:
return json(['code' => -1, 'msg' => $e->getMessage()], 500) - Web 页面场景下,显式构造:
return response($this->view->fetch('error/500'), 500)->contentType('text/html') - 绝对不要在
render()里echo、exit或直接return [],这些都会触发二次渲染 -
$e->getStatusCode()在多数自定义异常中为 0,别依赖它;HTTP 状态码应由你显式传入
report() 里 trace 要截断、敏感字段要过滤
默认 report() 调用父类逻辑,但 $e->getTraceAsString() 动辄上万字符,MySQL TEXT 字段静默截断、日志文件暴涨、甚至阻塞主线程。
立即学习“PHP免费学习笔记(深入)”;
- 精简堆栈深度:
$trace = array_slice($e->getTrace(), 0, 8),再json_encode($trace) - 过滤敏感上下文:若
$e instanceof \think\db\exception\DataNotFoundException或消息含'SQLSTATE',跳过$_POST、$_SERVER['HTTP_AUTHORIZATION']等字段 - 异步写日志更稳:
Log::channel('daily')->error(...)替代同步写文件
最常被忽略的是 CLI 环境和 render() 返回值类型——这两个点不处理,你在浏览器里看着正常,一跑命令行就崩回原始堆栈,而且根本查不到原因。



















