序言
如果你正在使用Laravel作为你的开发框架,那么肯定会了解或者使用过它的事件API。Laravel的事件API是基于观察者这种设计模式实现的,并且结合了实际情况做了优化和改进。下面就依次介绍和Laravel事件实现相关的一些内容。
观察者模式
首先举一个例子,假如你是一个司机,在马路上开车时违反了交通规则,被交警拦下后罚了款。这是在现实中生活中应用到事件机制的一个例子。司机就是一个被观察的对象,而交警就是观察者,当交警观察到司机违法时,就像被打开了一个开关一样,会不自觉地对司机进行处罚。
下面是一张观察者模式的类图,左边的Concrete Subject类就好比司机,而右边的Concrete Observer就好比交警。Concrete Subject的Notify方法是将司机的违法行为暴露给交警,而Concrete Observer的Update方法是交警的处罚行为。
Laravel事件与监听器
Laravel中的事件API包含了监听(Listen)和触发(Fire)两个核心的方法,其中Listen方法就是将事件(Event)与监听器(Listener)进行绑定,监听和触发方法都封装在Dispatcher类中。Dispatcher类就是一个Concrete Subject,Event就好比司机的违法行为,Fire方法就是上面说到的Notify方法,Listener就好比是交警,它是一个Concrete Observer,Listener类中需要实现handle方法用来做类似交警罚款的动作。
注册事件与监听器
- 判断Listener是否为字符串
public function makeListener($listener)
{
return is_string($listener) ? $this->createClassListener($listener) : $listener;
}
- 解析Listener类和handle方法,handle方法可以替换成自定义的方法
protected function parseClassCallable($listener)
{
$segments = explode('@', $listener);
return [$segments[0], count($segments) == 2 ? $segments[1] : 'handle'];
}
- 判断Listener的handle方法是否需要队列异步执行(考虑为何需要异步执行?)
protected function handlerShouldBeQueued($class)
{
try {
return (new ReflectionClass($class))->implementsInterface(
'Illuminate\Contracts\Queue\ShouldQueue'
);
} catch (Exception $e) {
return false;
}
}
- 创建Listener的Callable
protected function createClassCallable($listener, $container)
{
list($class, $method) = $this->parseClassCallable($listener);
if ($this->handlerShouldBeQueued($class)) {
return $this->createQueuedHandlerCallable($class, $method);
} else {
return [$container->make($class), $method];
}
}
- 判断Event字符串是否包含通配符,把上面生成的Listener放入对应的类属性,注意$priority
public function listen($events, $listener, $priority = 0)
{
foreach ((array) $events as $event) {
if (Str::contains($event, '*')) {
$this->setupWildcardListen($event, $listener);
} else {
$this->listeners[$event][$priority][] = $this->makeListener($listener);
unset($this->sorted[$event]);
}
}
}
protected function setupWildcardListen($event, $listener)
{
$this->wildcards[$event][] = $this->makeListener($listener);
}
触发事件
- 判断Event是否为对象,解析生成payload和Event字符串
if (is_object($event)) {
list($payload, $event) = [[$event], get_class($event)];
}
- 格式化payload,将event字符串写入firing属性
if (! is_array($payload)) {
$payload = [$payload];
}
$this->firing[] = $event;
- 通过队列广播事件
if (isset($payload[0]) && $payload[0] instanceof ShouldBroadcast) {
$this->broadcastEvent($payload[0]);
}
- 根据Event字符串获取Listeners
public function getListeners($eventName)
{
$wildcards = $this->getWildcardListeners($eventName);
if (! isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
return array_merge($this->sorted[$eventName], $wildcards);
}
- 对不包含通配符的事件Listener按注册时的priority进行排序
protected function sortListeners($eventName)
{
$this->sorted[$eventName] = [];
if (isset($this->listeners[$eventName])) {
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = call_user_func_array(
'array_merge', $this->listeners[$eventName]
);
}
}
- 循环执行Listener Callable,payload作为参数传过去。$halt表示返回内容不为空时中断执行,立即返回响应的内容。执行结束后,将Event字符串从firing属性中删除。如果返回内容为false,则跳出循环。
foreach ($this->getListeners($event) as $listener) {
$response = call_user_func_array($listener, $payload);
if (! is_null($response) && $halt) {
array_pop($this->firing);
return $response;
}
if ($response === false) {
break;
}
$responses[] = $response;
}
array_pop($this->firing);
return $halt ? null : $responses;
总结
Laravel的事件API支持一个事件绑定多个监听器,也支持包含通配符的事件,但是包含通配符的事件不支持按优先级触发监听器。Laravel的事件触发后,Listener可以通过队列异步执行,保证了程序可以不中断,且快速响应,就好比本文开头的举例中,如果被电子警察拍到违法行为,可以在指定时间内延迟缴纳罚款。Laravel的事件也支持通过队列广播,Dispatcher可以被Listener订阅,在Listener中完成事件的注册,便于更好地解耦和复用。