【Laravel-海贼王系列】第九章, Events 功能解析

news/2024/7/5 4:34:53

Events 注册

  1. 框架如何在启动的时候加载注册的事件?
  1. 框架如何触发事件?

1,先在容器中注册 events 的全局对象。

Application 构造函数中对 events 进行注册代码

protected function registerBaseServiceProviders(){$this->register(new EventServiceProvider($this));$this->register(new LogServiceProvider($this));$this->register(new RoutingServiceProvider($this));}
复制代码

展开 $this->register(new EventServiceProvider($this));

这里的 $this->register() 方法就是调用 EventServiceProvider 对象的 register() 方法,最终在容器中对应的 events 对象

class EventServiceProvider extends ServiceProvider
{public function register(){$this->app->singleton('events', function ($app) {return (new Dispatcher($app))->setQueueResolver(function () use ($app) {return $app->make(QueueFactoryContract::class);});});}
}
复制代码

2,注册用户定义的事件

这里的部分涉及到 Provider 启动 相关的流程,第八章的时候有讲,我们直接跳到如何启动 EventServiceProvider 这里。

先看 app.providers 中配置要加载的服务提供者。

'providers' => [...App\Providers\EventServiceProvider::class,...],
复制代码

App\Providers\EventServiceProvider::class 这里的 boot() 方法是被框架加载服务提供者的时候调用的。

class EventServiceProvider extends ServiceProvider
{protected $listen = [Registered::class => [SendEmailVerificationNotification::class,],];public function boot(){parent::boot();}
}
复制代码

先启动父类的 boot() 方法

class EventServiceProvider extends ServiceProvider
{protected $listen = [];protected $subscribe = [];public function boot(){foreach ($this->listens() as $event => $listeners) {foreach ($listeners as $listener) {Event::listen($event, $listener);}}foreach ($this->subscribe as $subscriber) {Event::subscribe($subscriber);}}public function register(){//}public function listens(){return $this->listen;}
}
复制代码

上面代码就是我们注册的核心了,首先先遍历 $listen 这个对象

 protected $listen = [Registered::class => [SendEmailVerificationNotification::class,],];
复制代码

这段代码就是绑定事件的核心

  Event::listen($event, $listener);
复制代码

这里我们来看 Event 门面返回的是什么

class Event extends Facade
{...protected static function getFacadeAccessor(){return 'events';}
}
复制代码

实际上面回到了最初的地方, events 是最初绑定的闭包

 $this->app->singleton('events', function ($app) {return (new Dispatcher($app))->setQueueResolver(function () use ($app) {return $app->make(QueueFactoryContract::class);});});
复制代码

events 就是 Illuminate\Events\Dispatcher 这个类! 我们来看看 Dispatcherlisten 方法

 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);}}}复制代码

这里的代码其实就是赋值的过程 如果是全局的事件就放入 $this->wildcards 中否则就放入 $this->listeners 中。

继续看$this->makeListener($listener);

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));};}
复制代码

这里解析出来的闭包会赋值给 $this->listeners[$event] 。 到这里我们就已经解析完了框架是如何对已经写好的事件进行注册 的。

3,订阅者的注册

事件除了一对一的绑定,还实现了一对多的绑定就是订阅者

public function boot(){...foreach ($this->subscribe as $subscriber) {Event::subscribe($subscriber);}}
复制代码

回到 boot() 的方法中,执行完常规的 Event 的注册,之后开始注册 subscribeDispatcher 对象中。

 public function subscribe($subscriber){$subscriber = $this->resolveSubscriber($subscriber); // 从容器中解析传入的抽象,返回对应实例$subscriber->subscribe($this); // 调用返回实例的subscribe($this)方法,同时传入 $dispatcher 对象}
复制代码

那么 subscribe($this) 里面都做了什么呢?

这里我列举了一个demo,写法也是参照官方给出的。

 public function subscribe($events){$events->listen('App\Events\MyEvents','App\Listeners\MySubscribe@funName()');}
复制代码

最后还是调用了 listen方法,只不过这种方式可以支持一个订阅者监听多个事件,根据事件的不同选择性的触发对应的方法。

fire events !

前面都是讲怎么把事件绑定到 $dispatcher 对象中,这节我们开始讲怎么触发事件!

事件调用的核心方法。

public function dispatch($event, $payload = [], $halt = false){[$event, $payload] = $this->parseEventAndPayload($event, $payload);if ($this->shouldBroadcast($payload)) {$this->broadcastEvent($payload[0]);}$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;}
复制代码

我们来看个系统的默认的事件返回值

[$event, $payload] = $this->parseEventAndPayload($event, $payload);
复制代码

这段代码是事件广播的触发

if ($this->shouldBroadcast($payload)) {$this->broadcastEvent($payload[0]);}
复制代码

开始遍历从给定事件解析出来的监听器类

foreach ($this->getListeners($event) as $listener) {......}
复制代码

我们直接看 $this->getListeners($event);方法

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;}
复制代码

这里的主要逻辑就是从之前的 $this->listeners 中找是否存在绑定的类,如果存在则返回对应的类,否则返回对应的实现接口。

最后执行 $response = $listener($event, $payload);

这里的 $listener 就是在上面绑定的时候调用 makeListerer() 返回的闭包。

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));};}
复制代码

这里传入的参数 $event 是我们编写的 Event 类的对象,$payload 是你要传入携带的参数。

这里的闭包逻辑后续详解。

结语

Events 提供了解耦开发的优点,框架很多地方都有使用,比如模型的观察器就是基于事件系统来实现的。


http://lihuaxi.xjx100.cn/news/236125.html

相关文章

org.springframework.data.redis 一次连接获取特定key所有k-v(pipeline)

2019独角兽企业重金招聘Python工程师标准>>> 当我们需要一次性获取在redis中以hash方式存储的所有key-value时&#xff0c;我们可以使用下面的方式来获取。 public void testGetMore() throws IOException {RedisCallback<List<Object>> pipelineCallba…

关于java.util.LinkedHashMap cannot be cast to ......的解决办法

今天在项目中遇到一个问题&#xff0c;接口接收到list在对list进行遍历的时候报出如下错误: 断点看一下这个list感觉没有任何的问题: 那为什么会报这个错误呢 这个接口是这样的&#xff0c;在想会不会是json在转list的时候把这个list给整坏了。 于是&#xff0c;我把这个list再…

Python基础04-数据类型:数字、布尔、字符串

目录 数字 布尔 字符串 字符串的常用函数 字符串的内存分析 字符串练习题 数字 判断是数字类型还是字符串类型。 # <class str> 123 a "123" print(type(a), a)# <class int> 123 b int(a) print(type(b), b) 十进制、二进制、八进制、十六进…

人工智能承诺就业革命,但仍需传统的体力劳动

在北京郊区的一栋五层苏式工厂大楼里&#xff0c;一群年轻女性整齐的坐在工位前&#xff0c;每天盯着电脑&#xff0c;进行着重复性的工作。她们需要观察日常生活中的图像&#xff0c;然后汇总成图表。人工智能的到来被称为第四次工业革命&#xff0c;它承诺将人类从大量重复性…

java基础(十三)-----详解内部类——Java高级开发必须懂的

java基础(十三)-----详解内部类——Java高级开发必须懂的 目录 为什么要使用内部类内部类基础静态内部类 成员内部类 成员内部类的对象创建继承成员内部类局部内部类推荐博客匿名内部类正文 可以将一个类的定义放在另一个类的定义内部&#xff0c;这就是内部类。 回到顶部为什么…

Python基础05-数据类型:列表list

目录 列表 列表的一般用法 列表的方法 列表 列表的一般用法 列表用[]括起来&#xff0c;用逗号分隔元素。元素可以是任意的类型。 可以用len获取列表的长度&#xff0c;也就是元素的个数。 # 列表是个大杂烩&#xff0c;什么类型都可以往里面装 li [2019, 12, "存储…

分享:用promise封装ajax

用promise封装ajaxvar ajaxOptions {url: url,method: GET,async: true,data: null,dataType: text, } function ajax(protoOptions) {var options {};for(var i in ajaxOptions){options[i] protoOptions[i] || ajaxOptions[i];}return new Promise(function(resolve, reje…

其他进制的数字

JS中如果需要表示16进制的数字,则需要以0X开头 0X10 八进制数字以0开头 070 070有些浏览器会以8进制解析,但是有些则用10进制解析,10进制为70,8进制为56 所以parseint() 第二个参数可以设定进制,比如 parseint(“070”,10)代表以10进制解析070 2进制以0b开头,但是不是所有浏览…