必须创建自定义异常类以区分业务错误语义,如InsufficientBalanceException需置于src/Exception/下、继承RuntimeException、提供带消息构造方法,并在业务代码中抛出;再通过ExceptionListener监听kernel.exception事件实现全局捕获与JSON响应。

在 Symfony 7.0 项目中,当业务逻辑需要区分“用户余额不足”“订单已取消”“库存超限”这类语义明确的错误时,必须创建自定义异常类,否则所有错误都退化为通用 Exception 或 RuntimeException,日志无法分类、监控难以打标、前端响应难统一。
定义自定义异常类
在 src/Exception/ 目录下新建 PHP 文件,例如 InsufficientBalanceException.php。
类名必须以 Exception 结尾,且继承 \Exception(或更精准的 \RuntimeException,若属于程序运行时可预期的业务中断)。
这一步不能放在 Controller 或 Service 目录里——Symfony 自动加载器只扫描 src/ 下的 Exception 命名空间,放错位置会导致类找不到。
添加至少一个带消息的构造方法,方便抛出时携带上下文:
```php
namespace App\Exception;
class InsufficientBalanceException extends \RuntimeException
{
public function __construct(string $message = '账户余额不足', int $code = 400, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
```
在业务代码中抛出自定义异常
进入具体业务逻辑文件,比如 src/Service/PaymentService.php。
检查用户余额是否低于订单金额,不满足条件时直接 throw 新建的异常实例:
```php
if ($user->getBalance() getTotal()) {
throw new InsufficientBalanceException(
sprintf('当前余额 %.2f 元,不足以支付 %.2f 元', $user->getBalance(), $order->getTotal())
);
}
```
注意:不要用字符串拼接后 throw new \Exception(...) —— 这样丢失了类型信息,后续无法按类名做差异化处理。
全局捕获并响应自定义异常
创建监听器类 src/EventListener/ExceptionListener.php,实现 kernel.exception 事件监听。
第一步:声明类并实现 onKernelException 方法,接收 ExceptionEvent 对象:
```php
namespace App\EventListener;
use App\Exception\InsufficientBalanceException;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpFoundation\JsonResponse;
class ExceptionListener
{
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
if ($exception instanceof InsufficientBalanceException) {
$response = new JsonResponse([
'error' => 'INSUFFICIENT_BALANCE',
'message' => $exception->getMessage(),
'code' => 400,
], 400);
$event->setResponse($response);
}
}
}
```
第二步:确保该监听器被注册为 kernel.exception 事件处理器。在 config/services.yaml 中添加服务标签:
```yaml
App\EventListener\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
```
【必须加 tags 块】 否则即使类存在、方法签名正确,Symfony 也不会触发它——自动发现机制对事件监听器不生效,必须显式声明。
第三步:验证监听是否生效。触发一次余额不足场景,观察响应状态码是否为 400、body 是否含 error 字段。若仍返回 500 页面,检查 ExceptionListener 类是否在 src/EventListener/ 目录下、命名空间是否为 App\EventListener、服务配置是否缩进正确。


















