Laravel 事件系统

Laravel 事件系统#

Dispatcher 是一个观察者模式的实现,它最大的优点就是在对象之间没有依赖关系的情况下,把对象内部的状态暴露给对该状态感兴趣的对象,
通常我们把这类对象称为监听器,监听器的本质是一个可执行的函数;状态称为事件,通常事件都有一个唯一的名称,和携带一个可选的状态数据。

通过下面的例子可以体验下事件系统的优点。


class Page
{
    protected $dispatcher;

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


    public function render()
    {
        $this->dispatcher->dispatch('css.loaded');

        $this->dispatcher->dispatch('javascript.loaded');

        $this->dispatcher->dispatch('html.painting');

        $this->dispatcher->dispatch('html.painted');
    }
}

$dispatcher = new Dispatcher();
$dispatcher->listen('css.loaded', function () {
    echo "css loaded ok \n";
});

$dispatcher->listen('javascript.loaded', function () {
    echo "javascript loaded ok \n";
});

$dispatcher->listen('html.painting', function () {
    echo "ready for  painting\n";
});
$dispatcher->listen('html.painted', function () {
    echo "render ok \n";
});


$page = new Page($dispatcher);
$page->render();

在写 render 方法不用考虑对外的依赖关系,只需要把相应的事件派发就好,这些事件派发的点,本质就是一个功能延申扩展的切入点。

这种用法在 laravel 很多地方都有使用,例如每个 http 请求被处理完成之后都有派发一个 new RequestHandled($request, $response)
这个事件表示请求已经被处理,如果你想在请求被处理完成之后,做一个额外的操作–比如记录日志,就可以监听该事件。

基本使用#

通常 Dispatcher 会作为一个全局的单例对象被使用,在系统初始化的事件设置监听器,然后在具体业务逻辑中派发相应的事件。

定义事件#

事件通常有两部分组成

  • 事件的唯一名称,为了简单,如果事件是一个类,可以使用类的全名称作为事件的唯一标记;
  • 事件携带的数据,这部分是可选;

RequestHandled 就是一个事件,它的事件名称为 Illuminate\Foundation\Http\Events\RequestHandled
它包含了请求对象和相应对象两个数据属性。


class RequestHandled
{
    /**
     * The request instance.
     *
     * @var \Illuminate\Http\Request
     */
    public $request;

    /**
     * The response instance.
     *
     * @var \Illuminate\Http\Response
     */
    public $response;

    /**
     * Create a new event instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Http\Response  $response
     * @return void
     */
    public function __construct($request, $response)
    {
        $this->request = $request;
        $this->response = $response;
    }
}

事件监听器设置#

使用 public function listen($events, $listener = null) 设置监听器,通常有以下 4 种用法

  • $events 为事件的唯一名称,$listener 为一个函数
  • $events 为一个函数,但函数的第一个参数为事件的唯一名称,$listener 为空
  • $events 为事件的唯一名称 $listener 为一个监听器类的名称,但是该类必须有 handle 方法或者 __invoke
  • $events 为事件的唯一名称 $listener` 为一个数组,数组的第一个元素为监听器类名称,第二个参数为处理事件的方法名称

// 方式一 函数监听器
$dispatcher->listen(RequestHandled::class,function (RequestHandled  $event){
    echo sprintf("请求%s已经被处理,相应的状态码为%d\n",$event->request->url(),$event->response->status());
});


// 方式二 省略事件名称
$dispatcher->listen(function (RequestHandled  $event){
    echo sprintf("请求%s已经被处理,相应的状态码为%d\n",$event->request->url(),$event->response->status());
});


//方式三:使用类监听器,默认是使用监听器的`handle`方法处理,如果找不到则使用`__invoke`方法,否则处抛出异常
class RequestHandledListener{
    public function handle($event){
        echo sprintf("请求%s已经被处理,相应的状态码为%d\n",$event->request->url(),$event->response->status());
    }
}
$dispatcher->listen(RequestHandled::class,RequestHandledListener::class);

// 方式四:指定监听器处理的方法
class RequestHandledListenerMethod{
    public function hand2($event){
        echo sprintf("请求%s已经被处理,相应的状态码为%d\n",$event->request->url(),$event->response->status());
    }
}

$dispatcher->listen(RequestHandled::class,[RequestHandledListenerMethod::class,'hand2']);

事件订阅器#

事件订阅器的是一个带有 subscribe 方法的类,subscribe 方法内部实现一些列的监听器的设置。

下面例子的本质是把用户相关的事件监听器的设置封装到一个名为 UserEventSubscriber

class UserEventSubscriber
{
    /**
     * Handle user login events.
     */
    public function handleUserLogin($event) {}

    /**
     * Handle user logout events.
     */
    public function handleUserLogout($event) {}

    /**
     * Register the listeners for the subscriber.
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     * @return void
     */
    public function subscribe($events)
    {
        $events->listen(
            'Illuminate\Auth\Events\Login',
            [UserEventSubscriber::class, 'handleUserLogin']
        );

        $events->listen(
            'Illuminate\Auth\Events\Logout',
            [UserEventSubscriber::class, 'handleUserLogout']
        );
    }
}


$dispatcher->subscribe(UserEventSubscriber::class); // 设置订阅器
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。