异常处理器

Hyperf 里,业务代码都运行在 Worker 进程 上,也就意味着一旦任意一个请求的业务存在没有捕获处理的异常的话,都会导致对应的 Worker 进程 被中断退出,这对服务而言也是不能接受的,捕获异常并输出合理的报错内容给客户端也是更加友好的。
我们可以通过对各个 server 定义不同的 异常处理器(ExceptionHandler),一旦业务流程存在没有捕获的异常,都会被传递到已注册的 异常处理器(ExceptionHandler) 去处理。

自定义一个异常处理

注册异常处理器

目前仅支持配置文件的形式注册 异常处理器(ExceptionHandler),配置文件位于 config/autoload/exceptions.php,将您的自定义异常处理器配置在对应的 server 下即可:

  1. <?php
  2. // config/autoload/exceptions.php
  3. return [
  4. 'handler' => [
  5. // 这里的 http 对应 config/autoload/server.php 内的 server 所对应的 name 值
  6. 'http' => [
  7. // 这里配置完整的类命名空间地址已完成对该异常处理器的注册
  8. \App\Exception\Handler\FooExceptionHandler::class,
  9. ],
  10. ],
  11. ];

每个异常处理器配置数组的顺序决定了异常在处理器间传递的顺序。

定义异常处理器

我们可以在任意位置定义一个 类(Class) 并继承抽象类 Hyperf\ExceptionHandler\ExceptionHandler 并实现其中的抽象方法,如下:

  1. <?php
  2. namespace App\Exception\Handler;
  3. use Hyperf\ExceptionHandler\ExceptionHandler;
  4. use Hyperf\HttpMessage\Stream\SwooleStream;
  5. use Psr\Http\Message\ResponseInterface;
  6. use App\Exception\FooException;
  7. use Throwable;
  8. class FooExceptionHandler extends ExceptionHandler
  9. {
  10. public function handle(Throwable $throwable, ResponseInterface $response)
  11. {
  12. // 判断被捕获到的异常是希望被捕获的异常
  13. if ($throwable instanceof FooException) {
  14. // 格式化输出
  15. $data = json_encode([
  16. 'code' => $throwable->getCode(),
  17. 'message' => $throwable->getMessage(),
  18. ], JSON_UNESCAPED_UNICODE);
  19. // 阻止异常冒泡
  20. $this->stopPropagation();
  21. return $response->withStatus(500)->withBody(new SwooleStream($data));
  22. }
  23. // 交给下一个异常处理器
  24. return $response;
  25. // 或者不做处理直接屏蔽异常
  26. }
  27. /**
  28. * 判断该异常处理器是否要对该异常进行处理
  29. */
  30. public function isValid(Throwable $throwable): bool
  31. {
  32. return true;
  33. }
  34. }

定义异常类

  1. <?php
  2. namespace App\Exception;
  3. use App\Constants\ErrorCode;
  4. use Hyperf\Server\Exception\ServerException;
  5. use Throwable;
  6. class FooException extends ServerException
  7. {
  8. }

触发异常

  1. namespace App\Controller;
  2. use App\Exception\FooException;
  3. class IndexController extends Controller
  4. {
  5. public function index()
  6. {
  7. throw new FooException('Foo Exception...', 800);
  8. }
  9. }

在上面这个例子,我们先假设 FooException 是存在的一个异常,以及假设已经完成了该处理器的配置,那么当业务抛出一个没有被捕获处理的异常时,就会根据配置的顺序依次传递,整一个处理流程可以理解为一个管道,若前一个异常处理器调用 $this->stopPropagation() 则不再往后传递,若最后一个配置的异常处理器仍不对该异常进行捕获处理,那么就会交由 Hyperf 的默认异常处理器处理了。

集成 Whoops

框架提供了 Whoops 集成。

首先安装 Whoops

  1. composer require --dev filp/whoops

然后配置 Whoops 专用异常处理器。

  1. // config/autoload/exceptions.php
  2. return [
  3. 'handler' => [
  4. 'http' => [
  5. \Hyperf\ExceptionHandler\Handler\WhoopsExceptionHandler::class,
  6. ],
  7. ],
  8. ];

效果如图:

whoops

Error 监听器

框架提供了 error_reporting() 错误级别的监听器 Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler

配置

config/autoload/listeners.php 中添加监听器

  1. <?php
  2. return [
  3. \Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler::class
  4. ];

则当出现类似以下的代码时会抛出 \ErrorException 异常

  1. <?php
  2. try {
  3. $a = [];
  4. var_dump($a[1]);
  5. } catch (\Throwable $throwable) {
  6. var_dump(get_class($throwable), $throwable->getMessage());
  7. }
  8. // string(14) "ErrorException"
  9. // string(19) "Undefined offset: 1"

如果不配置监听器则如下,且不会抛出异常。

  1. PHP Notice: Undefined offset: 1 in IndexController.php on line 24
  2. Notice: Undefined offset: 1 in IndexController.php on line 24
  3. NULL