TP8 彻底移除 App::error 和 App::fatal,全面转向 PSR-15 中间件与异常处理器机制,需通过自定义 Handler.php 处理异常,配合 register_shutdown_function 拦截致命错误,并用 set_error_handler 单独兜底 PHP 警告。

ThinkPHP 8 的 App::error 和 App::fatal 已被移除
直接说结论:TP8 不再支持旧版的全局错误钩子函数,强行注册 App::error 会报 Call to undefined method think\App::error()。这不是配置没生效,是底层彻底删了——因为 TP8 全面转向 PSR-15 中间件 + 异常处理器机制。
替代方案是自定义异常处理器,它接管所有未捕获异常(包括 ParseError、FatalError),但注意:它不处理 E_WARNING 这类 PHP 警告,那些仍需靠 set_error_handler 单独兜底。
- TP6 还能用
App::error捕获警告,TP8 必须拆成两套:异常处理器 + 自定义错误处理器 - 异常处理器默认只响应
Exception和Error子类,E_NOTICE类警告不会进来 - 若项目从 TP6 升级,原
app/exception.php文件会被忽略,必须迁移到app/exception/Handler.php
如何写一个兼容 E_ERROR/E_PARSE 的异常处理器
TP8 的 app/exception/Handler.php 是唯一入口,但它默认不处理致命错误(如语法错误、内存溢出)。要真正“全局”,得在构造函数里手动注册 register_shutdown_function,并配合 error_get_last() 拦截。
关键点在于区分错误类型:不是所有 shutdown 都是致命错误,比如正常 exit 也会触发,必须先判断 error_get_last() 是否非空且 type 属于 E_ERROR、E_PARSE、E_CORE_ERROR 等。
立即学习“PHP免费学习笔记(深入)”;
- 在
__construct()里调用register_shutdown_function([$this, 'handleShutdown']) -
handleShutdown()中用error_get_last()获取最后错误,过滤掉NULL或E_USER_DEPRECATED - 对匹配的致命错误,手动抛出
FatalErrorException(需自己定义),让后续render()统一处理 - 别在
handleShutdown里直接输出或记录日志——此时输出缓冲可能已关闭,应交由render()返回响应
render() 方法里怎么区分开发/生产环境并避免敏感信息泄漏
TP8 默认在 render() 中根据 app_debug 配置决定是否显示详细错误页。但很多人直接把 $e->getTraceAsString() 输出到前端,这是高危操作——数据库密码、密钥可能藏在变量值里。
正确做法是:开发环境用框架自带的 think\exception\Handle 渲染器(它做过脱敏),生产环境一律返回通用提示,且禁止暴露 file、line、trace。
- 生产环境强制返回
Response::create('系统繁忙,请稍后再试', 'html', 500),不调用父类render() - 若需记录原始错误,用
Log::error('Fatal error: '.$e->getMessage(), ['trace' => $e->getTrace()]),但日志路径不能是 web 可访问目录 - 避免在
render()中做耗时操作(如远程上报),否则错误叠加错误,可能触发二次 shutdown
为什么 set_error_handler 仍需保留
因为 Exception 处理器对 E_WARNING、E_NOTICE 完全无效。这些错误不抛异常,只走 PHP 原生错误处理流程。想统一收集或转为异常,必须显式设置 set_error_handler。
典型场景:数据库查询失败触发 PDO::prepare() 的 E_WARNING,或者 file_get_contents() 打不开文件。这类错误默认被忽略,但线上需要感知。
- 在
bootstrap/app.php开头注册:set_error_handler([$handler, 'handleError']) -
handleError()中对E_WARNING和E_NOTICE调用Log::warning(),其他错误(如E_STRICT)可直接忽略 - 不要在
handleError中 throw Exception——TP8 的异常处理器无法保证捕获它,可能造成脚本中断且无响应
最易被忽略的是错误类型的交叉覆盖:set_error_handler 拦不到 E_ERROR,而异常处理器拦不到 E_WARNING。两者必须共存,且各自职责清晰——谁该记录、谁该响应、谁该终止,边界划不清,线上就容易出现“错误没日志”或“500 页面空白”。



















