PHP 异常处理规范:什么时候该抛异常什么时候返回错误码
结论先说清楚:PHP 里判断该抛异常还是返回错误码,只看一条——调用方能不能在当场合理处理。可预期、属于业务规则的失败返回错误码;不可预期、属于程序缺陷或环境故障的失败抛异常。方向用反的代价是:异常被空 catch 吞掉变成静默失败,或者错误码层层上报最后没人管。
一条判断标准:调用方当场能处理吗
结论:能被调用方立刻、且有明确补救动作处理的失败,返回错误码;调用方无法当场补救、必须中断当前流程的,抛异常。
落地成三个问题:第一,这个失败是不是"正常业务分支"(余额不足、附件格式不符、权限不够);第二,调用方接到失败后有没有具体动作可做(提示用户、跳过这条、换个参数重试);第三,如果谁都不处理,结果是静默出错还是直接崩掉。前两条满足就返回错误码,否则抛异常。
什么时候返回错误码
结论:业务规则校验、用户输入校验、可枚举的预期失败,一律返回错误码或结构化结果对象,不要抛异常。
典型如文件上传:扩展名不在白名单、体积超过 PHP 的 upload_max_filesize / post_max_size、目录不可写——这三类都是可预期分支。Clara BBS 这类无框架 PHP 系统的做法是页面直接提示具体原因(超过 PHP 上限、白名单不符、目录不可写),而不是抛一个未捕获异常给用户看 500。同理,注册验证问答不通过、敏感词拦截、登录连错锁定(默认 5 次锁 15 分钟),都属预期拒绝,返回值即可。
实现上建议返回结构化结果而不是裸 bool 或魔法数字,例如 `['ok'=>false,'code'=>'UPLOAD_TOO_LARGE','msg'=>'文件超过服务器上限']`;错误码用字符串常量统一定义;不要为了少写 if 就把校验失败抛出去让上层 catch 兜底。
什么时候必须抛异常
结论:依赖缺失、环境不满足、参数类型错误、配置损坏、数据库连不上——这类失败调用方无法原地补救,必须抛异常中断执行。
原因是这些属于"代码或环境有问题"的信号,继续往下跑只会写出更脏的数据。PHP 版本不落在 7.4–8.5、MySQL 5.7+ 连不上、必填配置项为空,正确做法是立刻抛出,由全局处理器记录日志并返回错误页。注意两点:异常类要有层次(InvalidArgumentException、RuntimeException、自定义 ValidationException 各司其职),catch 要精确,禁止 `catch (Exception $e) {}` 这种空吞——它会让故障在几个月后才以数据错乱的形式暴露。
不要用异常做流程控制
结论:用异常表达正常业务分支是反模式,问题不只是性能,更是可读性和可维护性。
每次 throw/catch 都是一次非局部跳转,读代码的人必须在调用栈上下反复找入口。业务分支应该老老实实出现在 if/return 里,异常只留给真正意外的情况。
用一个边界把两者接起来
结论:正确架构是"内层返回值、边界转异常、最外层统一渲染",错误码和异常不是二选一。
具体做法:Model/Service 层返回错误码;Controller 层读错误码决定给用户什么提示;只有真异常才一路抛出,由全局异常处理器统一写日志加输出友好页。Clara BBS 的 CSRF 处理就是这个模式:校验失败先提示"页面已过期,请刷新后重试",新版编辑器还会自动拉取新 token 重发一次,重试仍失败才把问题交给用户。数据库增量升级同理——幂等、可重复执行,失败就明确报错,不靠猜继续跑。
一句话收束:能被当场处理的是"值",不能被当场处理的是"异常";值走返回值,异常走 throw,中间用一层边界做翻译。
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员





