转发自白狼栈:查看原文

上一节课咱们封装了对立响应的办法,敲过一遍代码的小伙伴可能会发现一个显著的问题,对于业务逻辑的解决,咱们往往会写在 Service 中,比方像上面这样:

App\Service\IndexService (自行创立)如下:

<?phpdeclare(strict_types=1);namespace App\Service;class IndexService{    public function info(int $id)    {        if ($id <= 0) {            throw new \Exception("id有效");        }        return ['info' => 'data info'];    }}

这个时候就须要咱们在 IndexController::info 办法内通过 try/catch 捕捉解决了,如下:

use App\Service\IndexService;use Hyperf\Di\Annotation\Inject;use Hyperf\HttpServer\Annotation\AutoController;#[AutoController]class IndexController extends AbstractController{    /**     * @var IndexService     */    #[Inject]    public $indexService;    public function info()    {        try {            return $this->response->success($this->indexService->info($id));        } catch (\Throwable $e) {            return $this->response->fail(500, $e->getMessage());        }    }}
其中,IndexService 是通过 #[Inject] 注解注入由 @var 注解申明的属性类型对象。依赖注入参考 [https://hyperf.wiki/3.0/#/zh-...
](https://hyperf.wiki/3.0/#/zh-...)

这样看似解决了被动抛异样的问题,然而也带来了新的问题,比方每个办法都要用 try/catch 捕捉,无疑减少了很多麻烦。

hyperf 提供了异样处理器(ExceptionHandler),专门解决业务流程中没有捕捉的异样。

异样处理器须要在 config/autoload/exceptions.php 文件内配置。hyperf 框架默认配置了两个 ErrorHandle,如下:

return [    'handler' => [        'http' => [            Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,            App\Exception\Handler\AppExceptionHandler::class,        ],    ],];

咱们找到 Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler 的 isValid 办法,会发现它针对的是 Hyperf\HttpMessage\Exception\HttpException 的异样解决。

public function isValid(Throwable $throwable): bool{    return $throwable instanceof HttpException;}

解决的形式(参考 Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::handle)也很简略,看上面3行代码

public function handle(Throwable $throwable, ResponseInterface $response){    // ①    $this->logger->debug($this->formatter->format($throwable));    // ②    $this->stopPropagation();    // ③    return $response->withStatus($throwable->getStatusCode())->withBody(new SwooleStream($throwable->getMessage()));}
  • ①、控制台输入异样信息
  • ②、异样解决到此为止,不再持续冒泡传递
  • ③、http 间接响应 http 状态码并输入异样信息

curl 申请一个不存在的路由测试一下

➜  ~ curl -I http://127.0.0.1:9501/index/info222HTTP/1.1 404 Not FoundServer: swoole-http-serverConnection: keep-aliveContent-Type: text/htmlDate: Tue, 01 Nov 2022 08:40:10 GMT

Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler 是 hyperf 的源码文件,咱们不好批改,先间接屏蔽掉,等下在 AppExceptionHandler 内对立解决。

当初咱们只保留了一个 App\Exception\Handler,看下 AppExceptionHandler::handle 办法,也是向控制台输入错误信息、异样栈,并输入 Internal Server Error。这对咱们也没什么意义,改写 AppExceptionHandler 之前,先创立一个业务异样类,后续被动抛异样次要应用这个类即可。

app/Exception 目录新增 BusinessException,继承 Hyperf\Server\Exception\ServerException,代码如下:

<?phpdeclare(strict_types=1);namespace App\Exception;use Hyperf\Server\Exception\ServerException;class BusinessException extends ServerException{}

AppExceptionHandler 目前为止,咱们须要针对 App\Exception\BusinessException、Hyperf\HttpMessage\Exception\HttpException 独自捕捉并解决,如下:

<?phpdeclare(strict_types=1);namespace App\Exception\Handler;use App\Components\Response;use App\Exception\BusinessException;use Hyperf\ExceptionHandler\ExceptionHandler;use Hyperf\ExceptionHandler\Formatter\FormatterInterface;use Hyperf\HttpMessage\Exception\HttpException;use Hyperf\Logger\LoggerFactory;use Hyperf\Utils\ApplicationContext;use Psr\Container\ContainerInterface;use Psr\Http\Message\ResponseInterface;use Throwable;class AppExceptionHandler extends ExceptionHandler{    protected $logger;    /**     * @var Response     */    protected $response;    public function __construct(ContainerInterface $container, Response $response)    {        $this->logger = $container->get(LoggerFactory::class)->get('exception');        $this->response = $response;    }    public function handle(Throwable $throwable, ResponseInterface $response)    {        $formatter = ApplicationContext::getContainer()->get(FormatterInterface::class);        // 业务异样类        if ($throwable instanceof BusinessException) {            return $this->response->fail($throwable->getCode(), $throwable->getMessage());        }        // HttpException        if ($throwable instanceof HttpException) {            return $this->response->fail($throwable->getStatusCode(), $throwable->getMessage());        }        $this->logger->error($formatter->format($throwable));        return $this->response->fail(500, env('APP_ENV') == 'dev' ? $throwable->getMessage() : 'Server Error');    }    public function isValid(Throwable $throwable): bool    {        return true;    }}

isValid 办法返回 true,示意接管所有异样并解决,在解决办法 handle 中,除了 App\Exception\BusinessException、Hyperf\HttpMessage\Exception\HttpException 异样,其余均通过 App\Components\Response::fail 办法解决并把所有异样记录到日志文件。

当初 IndexController::info 办法改写回来如下:

public function info(){    $id = (int) $this->request->input('id', 0);    return $this->response->success($this->indexService->info($id));}

IndexService::info 改写如下:

use App\Exception\BusinessException;class IndexService{    public function info(int $id)    {        if ($id <= 0) {            throw new BusinessException("id有效");        }        return ['info' => 'data info'];    }}

curl测试下:

➜  ~ curl http://127.0.0.1:9501/index/info?id=0{"code":500,"message":"id有效"}%➜  ~ curl http://127.0.0.1:9501/index/info222{"code":404,"message":"Not Found"}%

到这里,咱们的框架曾经初现雏形了,但咱们的论坛筹备面向全世界的用户,像下面这样抛出一个“id有效”,老外还认为是乱码了。

下一节咱们让框架反对国际化。