目录

介绍

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

  • 事件逻辑解释图

https://www.huodongxing.com/file/ue/20150615/11D882E32C07F2FFC12332D0382A4AC43B/30734097652827212.jpg