ThinkPHP自定义异常的核心目标是实现错误信息可控、响应格式统一、业务逻辑清晰;需继承对应语义的框架异常基类,在Handler中统一输出JSON结构,控制器中主动抛出而非手动返回,中间件前置拦截高频错误场景。

ThinkPHP 中自定义异常类的核心目标,是让错误信息更可控、响应格式更统一、业务逻辑更清晰。不是简单替换默认异常,而是把“出错了”变成“按我们定的规则来反馈”。
继承系统异常基类,明确异常类型
ThinkPHP 的异常处理基于 think\Exception(基础异常)或更具体的如 think\exception\HttpException、think\exception\ValidateException 等。自定义时推荐继承对应语义的基类,而非直接继承 PHP 原生 Exception:
- 业务校验失败 → 继承 think\exception\ValidateException,框架会自动返回 422 状态码和 JSON 格式校验错误
- 权限不足 → 继承 think\exception\HttpException 并传入 403,便于中间件或全局异常处理器识别
- 自定义业务异常(如“余额不足”“订单已取消”)→ 继承 think\Exception,并设置有意义的
$statusCode和$errorCode属性(需在异常处理器中读取)
在异常处理器中统一输出格式
修改 app/exception/Handler.php 的 render() 方法,对自定义异常做差异化响应:
- 判断
$e instanceof YourCustomException,返回标准 JSON 结构:['code' => $e->getCode(), 'msg' => $e->getMessage(), 'data' => []] - 对
ValidateException,提取$e->getError()数组,合并到响应体中,避免只返回第一条错误 - 开发环境可保留 trace 信息,生产环境一律屏蔽敏感路径和变量,用
config('app.app_debug', false)控制
在控制器中主动抛出自定义异常
别总用 return json([...]) 表示失败,该抛异常时就抛,让处理逻辑下沉到统一出口:
立即学习“PHP免费学习笔记(深入)”;
- 校验失败:不用手写 if-else 返回,直接
throw new ValidateException($validate->getError()) - 关键业务断点:如支付前检查用户状态,不满足直接
throw new BusinessException('USER_DISABLED', '账号已被禁用', 400) - 数据库操作失败不建议直接 throw PDOException,应捕获后转为更语义化的业务异常,隐藏底层细节
配合中间件提前拦截常见错误场景
有些异常适合前置拦截,比如 token 过期、IP 黑名单、请求频率超限。这类逻辑放在中间件里比等走到控制器再抛异常更高效:
- 中间件中检测失败,直接
throw new HttpException(401, 'Token expired'),由异常处理器统一响应 - 避免在多个控制器重复写鉴权代码,也防止异常被 try-catch 吞掉导致静默失败
- 注意中间件异常抛出时机——必须在
$next($request)之前,否则无法中断请求流程
自定义异常不是炫技,是把错误当成接口契约的一部分来设计。响应结构一致、状态码准确、提示友好,前端才好做统一 Toast 或跳转,运维也更容易定位问题类型。



















