Events
注册
- 框架如何在启动的时候加载注册的事件?
- 框架如何触发事件?
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
这个类! 我们来看看Dispatcher
的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);
}
}
}
复制代码
这里的代码其实就是赋值的过程 如果是全局的事件就放入 $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
的注册,之后开始注册 subscribe
到 Dispatcher
对象中。
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
提供了解耦开发的优点,框架很多地方都有使用,比如模型的观察器就是基于事件系统来实现的。