laravel 糙解之事件
- laravel
- 2019-11-28
- 542
- 0
事件
事件可以解耦系统程序代码,使系统模块分明。事件本质上是一对多的关系,即当某个事件发生,会触发一系列的系统更新操作。php 提供了SplSubject, SplObserver, SplObjectStorage 标准库接口用来实现事件功能。
Laravel 的事件提供了一个简单的观察者实现,允许你在应用中订阅和监听各种发生的事件。
说明
在开始阅读之前,最好对 laravel 的基础知识有些微了解
1. 服务容器2. 服务提供者3. 队列
EventServiceProvider 服务提供者
在 laravel 系统中,EventServiceProvider 负责提供事件的实现与调度,作为 laravel 核心服务提供者,在系统初始化函数中就被注册,核心代码块为
// vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.phppublic 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 的事件也有向上冒泡功能