Hyperf-事件机制+异常处理


Hyperf-事件机制+异常处理

标签(空格分隔): php, hyperf

异常处理器

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

异常记录到日志

// 日志类

    <?php
    declare(strict_types=1);
    
    namespace App\Utils;
    
    class NativeLog
    {
        private $dirPath;
    
    
        public function __construct()
        {
            $this->dirPath = BASE_PATH . '/runtime/logs/' . date("Ymd") . '/';
            if (!is_dir($this->dirPath)) {
                mkdir($this->dirPath, 0777, true);
            }
        }
    
        public function error(string $data) : bool
        {
            $data = "[ ERROR ] [" . date("Y-m-d H:i:s") . "] " . $data . PHP_EOL;
            file_put_contents($this->dirPath . "error.log", $data, FILE_APPEND);
            return true;
        }
    
    
    
    
    }

hyperf 本身就实现了异常类的接管,如果有异常会打印到控制台输出。

// 增加异常信息的记录日志

/**
 * 记录文本异常日志
 * @param Throwable $throwable
 */
public function writeLog(Throwable $throwable)
{
    $AppExceptionLog['server'] = "http";
    $AppExceptionLog['method'] = $this->request->getMethod();
    $AppExceptionLog['path'] = $this->request->url();
    $AppExceptionLog['params'] = $this->request->all();
    $AppExceptionLog['file'] = $throwable->getFile();
    $AppExceptionLog['line'] = $throwable->getLine();
    $AppExceptionLog['message'] = $throwable->getMessage();
    (new NativeLog())->error(json_encode($AppExceptionLog));
}

日志记录效果

但是有个问题,try catch 捕获的代码如果有异常就不会记录

引入事件机制

    事件模式是一种经过了充分测试的可靠机制,是一种非常适用于解耦的机制,分别存在以下 3 种角色:

事件(Event) 是传递于应用代码与 监听器(Listener) 之间的通讯对象
监听器(Listener) 是用于监听 事件(Event) 的发生的监听对象
事件调度器(EventDispatcher) 是用于触发 事件(Event) 和管理 监听器(Listener) 与 事件(Event) 之间的关系的管理者对象
用通俗易懂的例子来说明就是,假设我们存在一个 UserService::register() 方法用于注册一个账号,在账号注册成功后我们可以通过事件调度器触发 UserRegistered 事件,由监听器监听该事件的发生,在触发时进行某些操作,比如发送用户注册成功短信,在业务发展的同时我们可能会希望在用户注册成功之后做更多的事情,比如发送用户注册成功的邮件等待,此时我们就可以通过再增加一个监听器监听 UserRegistered 事件即可,无需在 UserService::register() 方法内部增加与之无关的代码。

代码示例

// 新增异常事件

<?php

declare(strict_types=1);

namespace App\Event;

/**
 * 系统异常事件
 * Class AppException
 * @package App\Event
 */
class AppException
{
    public $throwable;

    public function __construct($throwable)
    {
        $this->throwable = $throwable;
    }
}


// 新增异常事件监听器

<?php

declare(strict_types=1);

namespace App\Listener;

use App\Event\AppException;
use App\Utils\NativeLog;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\HttpServer\Contract\RequestInterface;

/**
 * 异常事件监听器
 * Class AppExceptionListener
 * @package App\Listener
 * @Listener()
 */
class AppExceptionListener implements ListenerInterface
{

    /**
     * @Inject()
     * @var RequestInterface
     */
    private $request;


    /**
     * @Inject()
     * @var NativeLog
     */
    private $nativeLog;

    /**
     * @inheritDoc
     */
    public function listen(): array
    {
        // TODO: Implement listen() method.
        // 返回一个该监听器要监听的事件数组,可以同时监听多个事件
        return [
            AppException::class,
        ];
    }

    /**
     * @param object $throwable
     */
    public function process(object $throwable)
    {
        $throwable = $throwable->throwable;
        $request = $this->request;
        $AppExceptionLog['server'] = "http";
        $AppExceptionLog['method'] = $request->getMethod();
        $AppExceptionLog['path'] = $request->url();
        $AppExceptionLog['params'] = $request->all();
        $AppExceptionLog['file'] = $throwable->getFile();
        $AppExceptionLog['line'] = $throwable->getLine();
        $AppExceptionLog['message'] = $throwable->getMessage();
        $this->nativeLog->error(json_encode($AppExceptionLog));
    }
}

// 控制器

<?php

declare(strict_types=1);

namespace App\Admin\Controller;

use App\Admin\Model\UserModel;
use App\Admin\Service\UserService;
use App\Event\AppException;
use App\Rpc\Inter\UserServiceInter;
use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Utils\Context;
use Psr\EventDispatcher\EventDispatcherInterface;
use function _HumbugBoxa5be08ba8ddb\React\Promise\Stream\first;

/**
 * 用户控制器
 * Class UserController
 * @package App\Admin\Controller
 * @AutoController()
 */
class UserController extends AdminBaseController
{
    /**
     * @Inject
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;


    /**
     * 异常测试
     */
    public function exception()
    {
        try {
            array_column();
        } catch (\Throwable $throwable) {
            $this->eventDispatcher->dispatch(new AppException($throwable));
        }
    }