ThinkPHP6事件清清楚楚
目录
介绍
ThinkPHP6事件,可以说是之前的行为及钩子的升级版,事件也比中间件在粒度上会更细,事件的目的也一样,解耦观察者和被观察者,主要是为了方便系统的易拓展、易维护。
事件与中间件目的是一致的,解耦系统,增强扩展性。但事件比起中间件粒度更小,也就是触发更精准,中间件最小的控制粒度是一个action(比如路由中间件),而事件可以精确到action中某个逻辑代码前后。
tp6事件使用了观察者模式。官方文档没有具体使用场景范例,事件定义、事件绑定、事件监听、事件订阅这些概念的通俗理解,特别是既然从观察者模式出发,应该结合观察者模式,讲解下对应的订阅者角色、事件、事件触发者、事件触发动作,这样能让更多对于观察者模式理解不够深刻的同学可以快速理解。
见过有人用订阅视频的方式讲解了观察者模式:订阅者(很多腾讯视频用户)、订阅什么事件(庆余年更新、王牌对王牌更新等多个事件-subscribe)、事件触发者(腾讯视频上新-publish)、订阅者做什么(视频用户收藏、马上看、喜欢、查看评论等等-update)
三个对象:订阅者、发布者、工具(监听发布者、通知发布者)
观察者模式也可以参考这个简单模式介绍:23-观察者模式 - 9ong
定义事件
首先我们要知道,tp的事件系统不依赖事件类,如果没有额外的需求,仅通过事件标识也可以使用,省去定义事件类的麻烦。
也就是说只要想触发哪个类的哪个方法,都可以认为是触发了事件,并不一定要特意去定义一个事件类,才能触发一个事件类的方法操作。
当然我们还要看正常的事件类定义:
php think make:event UserLogin
完善事件类UserLogin代码:
declare (strict_types = 1);
namespace app\event;
class UserLogin
{
private $user = [];//为了测试方便,这里以数组代替User对象
public function __construct(Array $user)
{
$this->user = $user;
common_debug_log($user,true);
}
}
事件绑定
事件绑定,就是为事件绑定一个别名。
绑定事件别名,不同于配置中间件分组、别名及优先顺序,事件别名一般在应用目录下的event.php文件中定义。而中间件的别名配置需要在对应的config目录下middleware.php配置。
// 事件定义文件
return [
'bind' => [
"UserLogin"=> \app\event\UserLogin::class,//绑定事件别名
],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [
],
];
当然我们还可以动态的去绑定别名:
Event::bind(['UserLogin' => \app\event\UserLogin::class]);
再次强调:tp的事件系统不依赖事件类,如果没有额外的需求,仅通过事件标识也可以使用,省去定义事件类的麻烦。
官方也说了:如果你没有定义事件类的话,则无需绑定。对于大部分的场景,可能确实不需要定义事件类。
事件监听
前面的事件定义与事件绑定在实践中,是很少使用的,更多的还是通过事件监听与事件订阅来实现事件逻辑体系。
创建一个用户登录事件监听器UserLogin,用来监听事件触发UserLogin(我们需要在event.php中定义事件别名UserLogin及对应事件监听器,后面会介绍),并通知订阅者(遍历subscribe订阅者,订阅者执行onUserLogin方法,后面会介绍)
php think make:event UserLogin
事件监听器类:\app\listener\UserLogin
declare (strict_types = 1);
namespace app\listener;
class UserLogin
{
/**
* 事件监听处理
*
* @return mixed
*/
public function handle($event)
{
common_debug_log(__METHOD__,true);
}
}
事件订阅
创建两个订阅者:User与TsingChan
php think make:subscribe User
php think make:subscribe TsingChan
订阅者User订阅了UserLogin和UserLogout两个事件:
namespace app\subscribe;
class User
{
/**
* 监听事件的方法命名规范是on+事件标识(驼峰命名)
*
* @param type $user
*/
public function onUserLogin($user)
{
// UserLogin事件响应处理
common_debug_log(__METHOD__,true);
common_debug_log($user,true);
}
public function onUserLogout($user)
{
// UserLogout事件响应处理
common_debug_log(__METHOD__,true);
}
}
订阅者TsingChan订阅了UserLogin一个事件:
namespace app\subscribe;
class TsingChan
{
/**
* 监听事件的方法命名规范是on+事件标识(驼峰命名)
*
* @param type $user
*/
public function onUserLogin($user)
{
// UserLogin事件响应处理
common_debug_log(__METHOD__,true);
common_debug_log($user,true);
}
}
事件配置
定义了事件监听器类和事件订阅者类后,我们可以在app/event.php配置事件监听器与订阅,配置的意思就是:启动了哪些事件监听器,哪些订阅者参与订阅了:
// 事件定义文件
return [
'bind' => [
],
'listen' => [
"UserLogin"=>[app\listener\UserLogin::class],
],
'subscribe' => [
app\subscribe\User::class,
\app\subscribe\TsingChan::class,
],
];
事件触发
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
public function login()
{
//接受验证用户请求数据等操作
common_debug_log(__METHOD__,true);
//触发用户登录事件,也就是说发生了一个UserLogin事件,通知事件监听器app\listener\UserLogin发生了一个UserLogin事件,框架根据事件event.php配置监听与订阅关系,遍历所有订阅者,执行订阅者对这个事件的响应方法,比如订阅者\app\Subscribe\User::onUserLogin()
//观察者模式,做到了将事件触发者与订阅者解耦
//事件监听器与事件订阅者是一个多对多的关系
event("UserLogin", ['name'=>"9ong.com","sex"=>1]);
return __METHOD__.PHP_EOL;
}
}
测试输出:
##2021-03-21 14:48:53
app\middleware\Check::handle=>before controller
##2021-03-21 14:48:53
app\controller\Index::login
##2021-03-21 14:48:53
app\listener\UserLogin::handle
##2021-03-21 14:48:53
app\subscribe\User::onUserLogin
##2021-03-21 14:48:53
Array
(
[name] => 9ong.com
[sex] => 1
)
##2021-03-21 14:48:53
app\subscribe\TsingChan::onUserLogin
##2021-03-21 14:48:53
Array
(
[name] => 9ong.com
[sex] => 1
)
##2021-03-21 14:48:53
app\middleware\Check::handle=>after controller
##2021-03-21 14:48:53
app\middleware\Check::end
- 事件逻辑解释图