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); // 设置订阅器