大多数项目直接用默认异常处理就够了。要不要新建一个类,主要看响应格式和日志需求。
在ThinkPHP6中,自定义异常处理类需要继承框架内置的think\exception\Handle,并重写render方法。框架默认的异常处理逻辑都封装在Handle类中,只有当你需要对异常响应格式或日志记录做定制时,才需要新建一个自定义类。例如,接口项目通常希望将异常统一输出为JSON结构,而默认的HTML错误页显然不满足要求。此时,在app\exception\Http.php中新建类并继承Handle,就是最直接的判断依据。
实现步骤
确定要覆盖默认行为后,操作路径很固定。
实现自定义异常处理类的第一步是编写类文件,命名空间建议使用app\exception,类名如Http。接着在render方法中编写自己的逻辑,注意方法签名必须与父类一致:public function render(Throwable $e): Response。在方法内部,你可以通过$e instanceof \think\exception\HttpException来判断HTTP异常,并针对不同状态码返回不同的JSON结构。最后,在app\config目录下的exception.php配置文件中,将'handle'的值改为你的自定义类名,例如'handle' => '\\app\\exception\\Http',这样框架就会加载你的类。
一个最简单的实现可以是下面这样。注意方法签名里的 Response 和 Throwable 都要引入,少了任何一个都会在运行时报错。
<?php
namespace app\exception;
use think\exception\Handle;
use think\response\Response;
use Throwable;
class Http extends Handle
{
public function render(Throwable $e): Response
{
if ($e instanceof \think\exception\HttpException) {
return json([
'code' => $e->getCode(),
'msg' => $e->getMessage(),
]);
}
return parent::render($e);
}
}
配置与缓存检查
类写好后,还要改配置文件。在 config 目录下找到 exception.php,把 handle 改为你的完整类名。这里要注意字符串转义,双反斜杠不能省。
// config/exception.php
return [
'handle' => '\\app\\exception\\Http',
];
配置文件是在应用初始化时读取的,所以如果之前生成过 runtime 缓存,修改后必须清掉。最简单的做法是手动删除 runtime 目录下的缓存文件,再刷新页面;如果你用的脚手架带 clear 命令,也可以先执行命令,再确认效果。不同版本清理方式可能不一样,需要结合当前环境确认。
验证与常见坑
怎么确认自定义类真的生效?建议在控制器里手动抛一个 RuntimeException,然后访问对应路由,看响应格式有没有变成你的 JSON 结构。如果仍然输出默认错误页,先检查类名是否写对、app\exception 目录下文件是否存在、文件名与类名是否一致(首字母大写)。另外,调试模式会干扰验证,建议在关闭调试模式(APP_DEBUG=false)的环境下测试。
一个常见的坑是,在自定义render方法中调用parent::render($e)时,发现异常信息还是默认格式。原因是调试模式下父类的render方法会直接调用系统的错误页面,而不会走你自定义的逻辑。正确做法是,在render方法开头先判断当前是否为调试模式,如果是,则直接调用父类方法,或者根据自己的需求返回更详细的错误信息。另一个坑是,自定义类中没有正确引入Response类,导致报错。记得在文件顶部使用use think\response\Response,否则方法返回类型声明会触发致命错误。
风险边界
还要明确一点:自定义异常处理类只改变响应的外观,不会替代框架底层的日志记录。框架的日志照常写入,所以不要在 render 方法里额外调用日志方法,避免同一错误被记录两次。
另外,render 方法自身一旦抛出未捕获异常,框架会退回默认处理机制。因此要控制这段逻辑的复杂度,尽量只做异常类型判断和响应格式转换,不要在异常处理里做业务查询或外部请求。异常处理类保持轻量,才能减少新故障点。