统一异常响应需在各PHP框架中注册全局处理器并分层捕获业务异常,生产环境关闭错误显示、过滤敏感日志,开发环境保留详细堆栈。

你需要在多个PHP框架中统一异常响应格式并确保生产环境日志不泄露敏感信息,同时让开发时能快速定位到具体文件和行号。
统一接管未捕获异常与错误
所有主流PHP框架(Laravel、Symfony、ThinkPHP)都会在应用启动阶段调用 set_exception_handler() 和 set_error_handler(),这是异常处理的起点。没做这一步,任何 try-catch 之外的致命错误都会直接暴露堆栈给用户。
方法一:手动注册全局处理器(适用于原生或轻量框架)
在入口文件(如 index.php)顶部添加:
set_exception_handler(function($e) {<br> error_log(sprintf('[EXCEPTION] %s in %s:%d', $e->getMessage(), $e->getFile(), $e->getLine()));<br> http_response_code(500);<br> echo '系统繁忙,请稍后再试';<br>});
立即学习“PHP免费学习笔记(深入)”;
方法二:框架内置方式(Laravel)
直接修改 app/Exceptions/Handler.php 中的 render() 方法,它会自动拦截所有未捕获异常。
【注意:Laravel 的 Handler 类默认会将开发环境异常渲染为详细页面,生产环境才走 JSON 或文本响应,切勿在 .env 中误设 APP_DEBUG=true】
分层捕获业务异常并附加上下文
不要把所有异常都扔给全局处理器——业务逻辑中的可预期失败(如参数校验失败、库存不足)应主动抛出自定义异常,并在控制器层捕获后返回结构化响应。
第一步:定义自定义异常类
创建 app/Exceptions/ValidationException.php,继承 Exception,不需额外逻辑。
第二步:在服务层抛出
例如用户注册时邮箱已存在:throw new ValidationException('邮箱已被注册');
第三步:控制器中针对性 catchtry { $user = UserService::register($data); } catch (ValidationException $e) { return response()->json(['code' => 400, 'msg' => $e->getMessage()]); }
这比用通用 Exception 捕获更安全——避免把数据库连接失败也当成参数错误返回给前端。
日志通道配置与敏感信息过滤
Monolog 是 Laravel/Symfony 默认日志库,ThinkPHP 7+ 也兼容。关键不是“记日志”,而是“记什么”和“记到哪”。
方法1:按级别分流
ERROR 及以上写入文件 + 推送到 Sentry;INFO 级别只写本地文件;DEBUG 级别仅开发环境输出到 console。
方法2:动态过滤敏感字段
在 Monolog 的 Processor 中移除请求体里的 password、token、credit_card 等键名:
$logger->pushProcessor(new CallbackFilterProcessor(function ($record) {<br> if (isset($record['context']['request']['body'])) {<br> $body = $record['context']['request']['body'];<br> unset($body['password'], $body['api_token']);<br> $record['context']['request']['body'] = $body;<br> }<br> return $record;<br>}));
【必须在日志写入前完成过滤,否则原始数据已落盘,无法撤回】
生产环境错误显示与日志开关控制
开发时要看到完整错误,生产时绝不能显示任何错误详情——这是硬性安全边界。
在 php.ini 或启动脚本中设置:
ini_set('display_errors', 'Off');<br>ini_set('log_errors', 'On');<br>ini_set('error_log', '/var/log/myapp/php_error.log');
同时,在框架配置中关闭调试模式:
Laravel 修改 .env:APP_DEBUG=false
Symfony 修改 .env:APP_ENV=prod → 自动禁用错误显示
这一步漏掉,一个未捕获异常就会把数据库密码、绝对路径全打在页面上。



















