tp6异常处理必须显式配置并返回response实例:render()需用response()->json()或json()返回json响应,report()仅记录日志,cli和多应用需手动绑定异常处理器,api场景应组合判断请求类型。

render() 必须返回 Response 实例,否则响应会被框架二次包装
很多人在 render() 里写 return json([...]) 却发现返回的是带框架 footer 的 HTML 页面——这是因为 TP6 要求返回值必须是 think\Response 实例,而裸调 json() 在某些上下文里可能被自动转成字符串或数组。
常见错误现象:接口报错时返回 200 状态码的混合页面,而不是预期的 JSON + 500;或响应体里混入了 <div class="think-error"> 这类模板结构。
<ul>
<li>正确写法是显式用 <code>response()->json() 或 json()(该函数在 TP6 中默认返回 Response):return json(['code' => -1, 'msg' => $e->getMessage()], 500);
$e->getStatusCode(),原生 Exception 或自定义异常常返回 0,导致状态码变成 200return response($this->view->fetch('error/500'), 500)->contentType('text/html');
render() 中 echo、exit、die,会破坏响应生命周期report() 只做日志,别塞格式化逻辑或返回值
report() 的唯一职责是记录异常,不是生成响应。很多团队把错误消息组装、JSON 封装甚至发短信的逻辑全塞进去,结果 render() 里没兜底,接口直接返回空 500 响应。
典型问题:日志字段超长被 MySQL 截断、$_POST 明文落库、堆栈信息泄露敏感路径。
-
report()内只调用Log::error()或第三方 SDK(如 Sentry),不要return、不要throw、不要echo - 精简堆栈:
$trace = array_slice($e->getTrace(), 0, 10);,再json_encode($trace)存储 - 敏感字段过滤:检查
$e->getMessage()是否含'SQLSTATE'、'PDOException',再决定是否脱敏$_POST或$_SERVER['HTTP_AUTHORIZATION'] - 异步更稳妥:
Log::channel('daily')->error(...)比同步写文件更少阻塞主流程
CLI 和多应用环境下 app_exception 配置不生效
HTTP 请求走自定义异常处理器,但 php think run、命令行任务、单元测试仍打印原生堆栈——这不是代码写错了,是 TP6 默认不加载 app.php 的 app_exception 配置到 CLI 生命周期中。
多应用模式(APP_MULTI = true)下,根配置对子应用无效,容易误以为“已经配了”。
- CLI 入口需手动绑定:
App::bind('think\exception\Handle', \app\common\exception\Handler::class); - 每个子应用的
config/app.php都要单独设'app_exception' => \app\common\exception\Handler::class - 验证是否生效:
App::getContainer()->has('think\exception\Handle')返回true才算绑定成功 - 中间件可能覆盖绑定:调试类中间件有时会重置
Handle实例,建议在中间件 handle 前加日志确认
API 场景下 render() 判断请求类型不能只靠 isAjax()
现代前端用 fetch 发请求默认不带 X-Requested-With: XMLHttpRequest,所以 $request->isAjax() 返回 false,render() 就 fallback 到 HTML 错误页,而不是 JSON。
Postman、curl、小程序等也常缺失 Accept: application/json,导致判断失效。
- 组合判断更可靠:
$request->isAjax() || $request->expectsJson() || $request->routeIs('api.*') - 路由前缀兜底:检查
$request->path()是否以'api/'开头 - 别直接
return json(...),要用response()->json(...)保证状态码和中间件链完整 - 生产环境强制脱敏:
if (!app()->isDebug()) { $e = new \think\Exception('服务器错误'); }避免暴露 SQL 或路径
最易被忽略的一点:TP6 彻底移除了 think_exception 类,且不再自动注册全局异常处理器。哪怕你写了 app/exception.php,只要没在 config/exception.php 里明确配 'handle' => \app\ExceptionHandle::class,异常就直接透出 PHP 原生错误页。这个配置项不存在默认值,不写就是不生效。











