laravel 糙解之事件
- laravel
- 2019-11-28
- 2702
- 0
事件
事件可以解耦系统程序代码,使系统模块分明。事件本质上是一对多的关系,即当某个事件发生,会触发一系列的系统更新操作。php 提供了SplSubject, SplObserver, SplObjectStorage 标准库接口用来实现事件功能。
Laravel 的事件提供了一个简单的观察者实现,允许你在应用中订阅和监听各种发生的事件。
说明
在开始阅读之前,最好对 laravel 的基础知识有些微了解
1. 服务容器
2. 服务提供者
3. 队列
EventServiceProvider 服务提供者
在 laravel 系统中,EventServiceProvider 负责提供事件的实现与调度,作为 laravel 核心服务提供者,在系统初始化函数中就被注册,核心代码块为
// vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php
public function register()
{
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make(QueueFactoryContract::class);
});
});
}
说明:
- 核心是往 laravel 的服务容器中绑定事件接口和事件的实现类
- Dispatcher 类是事件实现的核心类。
- QueueFactoryContract 是标注对应的队列实现类
为了简明逻辑,将核心放到事件实现上,可以忽略队列相关东西,可以将代码简化为
public function register()
{
$this->app->singleton('events', function ($app) {
return (new Dispatcher($app));
});
}
Dispatcher 核心类
Dispatcher 类是 laravel 提供事件服务的核心代码,事件本质上就两个核心函数
1、listen 方法,负责绑定事件名称和事件监听器代码的对应关系,事件名称通过判断是否包含 “ * “ 分为明确事件名称和通配符事件名称
2、dispatch 方法,负责调度监听器代码,完成系统事件更新
listen 方法解析
public function listen($events, $listener)
{
foreach ((array) $events as $event) {
if (Str::contains($event, '*')) {
$this->setupWildcardListen($event, $listener);
} else {
$this->listeners[$event][] = $this->makeListener($listener);
}
}
}
laravel 将事件映射分别存储到 wildcards 和 listeners 属性中
public function makeListener($listener, $wildcard = false)
{
if (is_string($listener)) {
return $this->createClassListener($listener, $wildcard);
}
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return $listener($event, $payload);
}
return $listener(...array_values($payload));
};
}
在 makeListener 方法中,分依据 $listener 参数类型不同,分情况解析监听器代码
- 当 $listener 为字符串时,会通过 createClassListener 方法进一步解析处理
- 当 $listener 为闭包函数时,会进一步进行包装,将事件名和参数作为闭包函数的参数,在闭包函数内,依据 $wildcard 直接调用对应的监听器代码。这一步封装主要为了在调度时方便统一处理
public function createClassListener($listener, $wildcard = false)
{
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return call_user_func($this->createClassCallable($listener), $event, $payload);
}
return call_user_func_array(
$this->createClassCallable($listener), $payload
);
};
}
protected function createClassCallable($listener)
{
[$class, $method] = $this->parseClassCallable($listener);
***省了队列的一些处理****
return [$this->container->make($class), $method];
}
protected function parseClassCallable($listener)
{
return Str::parseCallback($listener, 'handle');
}
- 先对字符进行处理,laravel 预期的字符串为 \mespace\XXclass@method ,parseClassCallable 会用 @ 符号截取字符串,获得类名和方法名,方法名默认为 handle
- createClassCallable 方法会通过服务容器,解析出监听器类实例
- createClassListener 方法也会进行闭包封装,参数依然是事件名称和参数,这一点和上述对闭包的封装一样
dispatch 方法解析
当对应事件触发时,系统会通过 dispatch 方法进行调度,调用之前注册过的监听函数,完成事件更新任务。
public function dispatch($event, $payload = [], $halt = false)
{
[$event, $payload] = $this->parseEventAndPayload(
$event, $payload
);
**** 省略队列处理代码 ****
$responses = [];
foreach ($this->getListeners($event) as $listener) {
$response = $listener($event, $payload);
if ($halt && ! is_null($response)) {
return $response;
}
if ($response === false) {
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
这是事件调度的核心代码
protected function parseEventAndPayload($event, $payload)
{
if (is_object($event)) {
[$payload, $event] = [[$event], get_class($event)];
}
return [$event, Arr::wrap($payload)];
}
该方法主要是为了解析一下事件名和参数,$event 解析为字符串,$payload 解析为数组。当参数 $event 为对象时, laravel 会解析出类名作为事件名称,并且将类实例作为 $payload 数组参数返回
public function getListeners($eventName)
{
$listeners = $this->listeners[$eventName] ?? [];
$listeners = array_merge(
$listeners,
$this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
);
return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}
protected function getWildcardListeners($eventName)
{
$wildcards = [];
foreach ($this->wildcards as $key => $listeners) {
if (Str::is($key, $eventName)) {
$wildcards = array_merge($wildcards, $listeners);
}
}
return $this->wildcardsCache[$eventName] = $wildcards;
}
protected function addInterfaceListeners($eventName, array $listeners = [])
{
foreach (class_implements($eventName) as $interface) {
if (isset($this->listeners[$interface])) {
foreach ($this->listeners[$interface] as $names) {
$listeners = array_merge($listeners, (array) $names);
}
}
}
return $listeners;
}
- getWildcardListeners 方法会解析 wildcards 中的监听事件,这一部分主要是带通配符的事件名称,并且解析完后会进行内存缓存
- addInterfaceListeners 方法会向上发散,会找到事件类所有实现的接口类,并且进一步解析 listeners 中是否有对应接口类的监听器函数,借此实现了类似 JavaScript 中的事件冒泡原理
- 获取到事件的所有监听器函数之后,会按照顺序依次调用,由参数 halt 或者 监听器函数返回值( false ) 来决定是否停止继续执行剩余监听器代码,所以,在绑定事件监听器时,绑定的顺序也是很重要的
Dispatcher 的其余代码
打开 Dispatcher 实现的接口类,发现还有一些其他方法,例如:push、flush、forget、hasListeners 等等,这些都是一些辅助的方法函数,都是对 listen / dispatch 的调用,或者是对 listeners / wildcards / wildcardsCache 的处理
laravel 对队列的使用
事件注册机制
通过上述分析,事件注册本质上就是调用 listener 函数,进行事件名和事件处理函数的关系绑定。
$event = $app->make("events");
// 绑定事件名称 和 类字符串
$event->listen('order',App\Listeners\OrderListenerOne::class);
$event->listen(App\Events\OrderEvent::class,App\Listeners\OrderListenerOne::class);
// 绑定事件名称 和 闭包函数
$event->listen('order',function( $a , $b ){
echo "<hr>";
echo $a, "<br>";
echo $b, "<br>";
echo "<hr>";
});
事件触发调度
// 字符串名称触发
$event->dispatch("order",[1,11,22]);
// 类事件触发
$one = new App\Events\OrderEvent(1);
$event->dispatch($one,[1,11,22]); // 如果是类的话,后边参数会被类覆盖,
说明
- laravel 事件处理,每个地方都在依据是否带有通配符进行分情况处理,带通配符的话,会将事件名作为参数传递
- 如果触发事件的是类实例,laravel 会解析出类名作为事件名称
- laravel 的事件也有向上冒泡功能