Webman异常处理是框架内置的闭环机制,通过config/exception.php配置异常类到处理器的映射,BusinessException决定JSON响应结构,debug配置影响trace字段与响应粒度,report()是日志唯一入口。

Webman 的异常处理不是靠 set_exception_handler 或手动 try/catch 撑起来的,而是框架在请求生命周期中主动拦截、分发、渲染的一套闭环机制。你改对 config/exception.php,就能接管所有未捕获异常;但若漏掉 BusinessException 的语义约定或 debug 配置的影响,返回结果会和预期严重不符。
config/exception.php 是异常路由表,不是开关
这个文件本质是一张「异常类 → 处理器类」的映射表,框架按继承链向上匹配最具体的处理器。空字符串 '' 是兜底项,必须存在:
-
BusinessException和它的子类(如MyBusinessException)会优先匹配到自己注册的处理器,而不是兜底的support\exception\Handler - 如果只写了
BusinessException::class => app\exception\BusinessExceptionHandler::class,但没写'' => ...,那遇到InvalidArgumentException这类 PHP 原生异常就直接崩了(500 页面空白或原始报错) - 多应用或插件模式下,插件可单独配
plugin.cms.exception,但主项目仍需保证自己的config/exception.php完整
BusinessException 不是装饰品,它决定响应结构
抛出 BusinessException 时,框架不会走默认 HTML 错误页逻辑,而是强制走 JSON 响应(除非你重写了 render() 方法)。它默认输出格式为:
{"code":3000,"msg":"参数错误"}
注意以下几点:
立即学习“PHP免费学习笔记(深入)”;
- 构造时传的第二个参数是
$code,不是 HTTP 状态码,而是业务码,会被直接塞进 JSON 的code字段 - 如果你需要字段名是
message而非msg,必须自定义异常类并重写render(),不能只改构造参数 - 不要在业务层
catch BusinessException后再throw—— 这会绕过框架的自动处理,导致返回 HTML 或空响应
debug=false 时,trace 字段彻底消失,别指望前端调试
config/app.php 中的 debug 值不仅控制是否显示堆栈,还决定整个异常响应的字段粒度:
- debug=true:JSON 响应含
trace字段,HTTP 状态码为 500,且会渲染带源码行号的 HTML 页面(开发用) - debug=false:无论什么异常,JSON 只有
code和msg(或你自定义的字段),trace字段完全不生成,连日志里都不会记录完整堆栈(除非你在report()方法里手动加) - 线上环境一旦关掉 debug,
BusinessException('xxx', 4001)和new \Exception('xxx')在响应体上看起来一模一样,只能靠 code 区分 —— 所以业务码设计必须有唯一性、可读性
日志落盘依赖 report() 方法,不是自动发生的
异常处理器的 report() 方法是日志写入的唯一入口,support\exception\Handler 默认只记到 runtime/log/exception/ 下,但很多团队会覆盖它来对接 ELK 或 Sentry:
- 如果你写了自定义处理器,但没实现
report(),或者里面只写了return;,那异常就真的“发生过但没留下痕迹” - 注意
report()是同步阻塞调用,别在里面做 HTTP 请求或长耗时操作,否则拖慢整个请求响应 - 敏感信息(如数据库密码、token)可能出现在
$e->getTraceAsString()里,report()中要主动过滤,不能原样打日志
真正容易被忽略的是:异常处理链条里没有“中间件式”的前置/后置钩子,所有定制都落在 report() 和 render() 两个方法上,而且它们的执行顺序和参数来源是固定的 —— 想加监控指标、动态降级、请求上下文注入,都得从这两个方法里硬抠逻辑出来。



















