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));
}
}