第二部分 追源码
事件服务的注册
laravel中每个服务,需要先注册再启动,其中注册是一定的,启动过程可以没有。事件服务也不例外。但事件服务的注册位置较为特殊,
位于Application.php
protected function registerBaseServiceProviders()
{
# 事件服务就是在此注册的
# 注意application的register方法实际上调用了服务提供者的register方法
t
h
i
s
−
>
r
e
g
i
s
t
e
r
(
n
e
w
E
v
e
n
t
S
e
r
v
i
c
e
P
r
o
v
i
d
e
r
(
this->register(new EventServiceProvider(
this−>register(newEventServiceProvider(this));
t
h
i
s
−
>
r
e
g
i
s
t
e
r
(
n
e
w
L
o
g
S
e
r
v
i
c
e
P
r
o
v
i
d
e
r
(
this->register(new LogServiceProvider(
this−>register(newLogServiceProvider(this));
t
h
i
s
−
>
r
e
g
i
s
t
e
r
(
n
e
w
R
o
u
t
i
n
g
S
e
r
v
i
c
e
P
r
o
v
i
d
e
r
(
this->register(new RoutingServiceProvider(
this−>register(newRoutingServiceProvider(this));
}
事件服务提供者 Illuminate\Events\EventServiceProvider
public function register()
{
# 注意此处的singleton绑定 后面会使用到
t
h
i
s
−
>
a
p
p
−
>
s
i
n
g
l
e
t
o
n
(
′
e
v
e
n
t
s
′
,
f
u
n
c
t
i
o
n
(
this->app->singleton('events', function (
this−>app−>singleton(′events′,function(app) {
// 绑定的是一个disaptcher实例 并为事件服务设置了队列解析器
// 注意此闭包是在我们尝试从容器中解析事件服务的时候才会执行
return (new Dispatcher(
a
p
p
)
)
−
>
s
e
t
Q
u
e
u
e
R
e
s
o
l
v
e
r
(
f
u
n
c
t
i
o
n
(
)
u
s
e
(
app))->setQueueResolver(function () use (
app))−>setQueueResolver(function()use(app) {
return $app->make(QueueFactoryContract::class);
});
});
}
看Illuminate\Events\Dispatcher类
简单的构造方法
public function __construct(ContainerContract $container = null)
{
$this->container = $container ?: new Container;
}
setQueueResolver方法 一个简单的set
public function setQueueResolver(callable $resolver)
{
$this->queueResolver = $resolver;
return $this;
}
可以看到事件服务的注册实际上是向容器中注册了一个事件的分发器
事件服务的启动一(获取所有的事件和监听者)
框架启动的过程中会调用app/Providers下所有服务提供者的boot方法,事件服务也不例外。
App\Providers\EventServiceProvider文件
class EventServiceProvider extends ServiceProvider
{
# 此数组键为事件名,值为事件的监听者
# 事件服务的启动阶段会读取此配置,将所有的事件和事件监听者对应起来并挂载到事件分发器Dispatcher上
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
# 事件服务启动真正调用的方法 可以看到调用了父类的boot方法
# 也可以在boot方法中向事件分发器中自行绑定事件和监听者
public function boot()
{
parent::boot();
//
}
}
EventServiceProvider的父类
注册事件监听器
public function boot()
{
// getEvents方法 获取事件和监听器
$events = $this->getEvents();
foreach ($events as $event => $listeners) {
foreach (array_unique($listeners) as $listener) {
// 此处Event facade对应的是Dispatcher的listen方法
// facade的原理和使用之前介绍过
Event::listen($event, $listener);
}
}
foreach ($this->subscribe as $subscriber) {
// 调用的是Dispatcher的subscribe方法
Event::subscribe($subscriber);
}
}
getEvents方法
public function getEvents()
{
if ($this->app->eventsAreCached()) {
$cache = require $this->app->getCachedEventsPath();
return $cache[get_class($this)] ?? [];
} else {
return array_merge_recursive(
// 如果事件非常多,也可以设置事件和监听者的目录,让框架自行帮助查找
// 如果需要开启discoveredEvents功能,需要在App\Providers\EventServiceProvider中
// 重写shouldDiscoverEvents方法 并返回true 代表开启事件自动发现
// 如果需要指定事件和监听者的目录,需要重写discoverEventsWithin方法,其中返回目录数组
// 当然你也可以全部写在listen属性中
// 当重写了以上两个方法的时候 返回的数组和$listen属性的格式是完全一致的 以事件名称为key 监听者为value
$this->discoveredEvents(),
// 返回的就是App\Providers\EventServiceProvider下的listen数组
$this->listens()
);
}
}
discoveredEvents方法 此方法触发的前提是重写了shouldDiscoverEvents方法
public function discoverEvents()
{
// 使用了laravel提供的collect辅助函数 文档有详细章节介绍
// collect函数返回collection集合实例方便我们链式操作
// reject方法的作用是 回调函数返回 true 就会把对应的集合项从集合中移除
// reduce方法的作用是 将每次迭代的结果传递给下一次迭代直到集合减少为单个值
return collect($this->discoverEventsWithin())
// discoverEventsWithin方法返回查找事件监听者的目录数组
// 默认返回 (array)
t
h
i
s
−
>
a
p
p
−
>
p
a
t
h
(
′
L
i
s
t
e
n
e
r
s
′
)
/
/
我
们
自
然
可
以
重
写
d
i
s
c
o
v
e
r
E
v
e
n
t
s
W
i
t
h
i
n
方
法
,
返
回
我
们
指
定
的
监
听
者
目
录
−
>
r
e
j
e
c
t
(
f
u
n
c
t
i
o
n
(
this->app->path('Listeners') // 我们自然可以重写discoverEventsWithin方法,返回我们指定的监听者目录 ->reject(function (
this−>app−>path(′Listeners′)//我们自然可以重写discoverEventsWithin方法,返回我们指定的监听者目录−>reject(function(directory) {
// 移除集合中不是目录的元素
return ! is_dir(KaTeX parse error: Expected 'EOF', got '}' at position 21: …tory); }̲) ->red…discovered, $directory) {
return array_merge_recursive(
d
i
s
c
o
v
e
r
e
d
,
/
/
使
用
S
y
m
f
o
n
y
的
F
i
n
d
e
r
组
件
查
找
L
i
s
t
e
n
e
r
文
件
D
i
s
c
o
v
e
r
E
v
e
n
t
s
:
:
w
i
t
h
i
n
(
discovered, // 使用Symfony的Finder组件查找Listener文件 DiscoverEvents::within(
discovered,//使用Symfony的Finder组件查找Listener文件DiscoverEvents::within(directory, base_path())
);
}, []);
}
Illuminate\Foundation\Events\DiscoverEvents::within方法
提取给定目录中的全部监听者
public static function within($listenerPath, KaTeX parse error: Expected '}', got 'EOF' at end of input: …)->files()->in(listenerPath),
b
a
s
e
P
a
t
h
)
)
−
>
m
a
p
T
o
D
i
c
t
i
o
n
a
r
y
(
f
u
n
c
t
i
o
n
(
basePath ))->mapToDictionary(function (
basePath))−>mapToDictionary(function(event, KaTeX parse error: Expected '}', got 'EOF' at end of input: … return [event => $listener];
})->all();
}
protected static function getListenerEvents($listeners, $basePath)
{
$listenerEvents = [];
//
l
i
s
t
e
n
e
r
s
是
F
i
n
d
e
r
组
件
返
回
指
定
目
录
下
的
迭
代
器
,
遍
历
可
以
拿
到
目
录
下
的
所
有
文
件
f
o
r
e
a
c
h
(
listeners是Finder组件返回指定目录下的迭代器,遍历可以拿到目录下的所有文件 foreach (
listeners是Finder组件返回指定目录下的迭代器,遍历可以拿到目录下的所有文件foreach(listeners as $listener) {
try {
l
i
s
t
e
n
e
r
=
n
e
w
R
e
f
l
e
c
t
i
o
n
C
l
a
s
s
(
/
/
将
绝
对
路
径
转
换
为
类
名
s
t
a
t
i
c
:
:
c
l
a
s
s
F
r
o
m
F
i
l
e
(
listener = new ReflectionClass( // 将绝对路径转换为类名 static::classFromFile(
listener=newReflectionClass(//将绝对路径转换为类名static::classFromFile(listener, $basePath)
);
} catch (ReflectionException $e) {
continue;
}
if (! $listener->isInstantiable()) {
continue;
}
// dump($listener->getMethods(ReflectionMethod::IS_PUBLIC));
foreach ($listener->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
// 代表着一个监听者类中 可以设置多个监听器
if (! Str::is('handle*', $method->name) ||
! isset($method->getParameters()[0])) {
continue;
}
$listenerEvents[$listener->name.'@'.$method->name] =
// 可以认为此处返回的是事件名
// 写在handle*方法中的参数 我建议一定要加上类型提示,并且将类型名参数作为第一个参数传入
Reflector::getParameterClassName($method->getParameters()[0]);
}
}
// 过滤事件参数名为空的监听器并返回
return array_filter($listenerEvents);
}
事件服务的启动二(注册事件监听者)
上面获取了全部的事件监听者 下面就要注册这些事件监听者了
继续看EventServiceProvider::boot方法
使用php artisan event:list 可以查看框架中已经注册的事件
php artisan event:cache php artisan event:clear
public function boot()
{
# 拿到了$listen属性和要求自动发现的所有事件(如果开启了自动发现的话)
$events =
t
h
i
s
−
>
g
e
t
E
v
e
n
t
s
(
)
;
/
/
d
u
m
p
(
this->getEvents(); // dump(
this−>getEvents();//dump(events);
foreach ($events as $event => $listeners) {
foreach (array_unique($listeners) as $listener) {
// 调用dispatcher的listen方法
// 事件名为key 事件监听者为value 进行事件的注册监听
Event::listen($event, $listener);
}
}
foreach ($this->subscribe as $subscriber) {
// subscribe方法请自行查看
Event::subscribe($subscriber);
}
}
Dispatcher::listen方法
遍历getEvents中所有的事件和监听者 通过实际调用Dispatcher的makeListener创建监听者
以event名为键 创建的监听者闭包为值 保存在数组属性中 供事件触发的时候查找调用
// 向调度器注册事件监听器
public function listen($events, KaTeX parse error: Expected '}', got 'EOF' at end of input: … { // dump(events, $listener);
foreach ((array) $events as KaTeX parse error: Expected '}', got 'EOF' at end of input: …(Str::contains(event, ‘*’)) {
t
h
i
s
−
>
s
e
t
u
p
W
i
l
d
c
a
r
d
L
i
s
t
e
n
(
this->setupWildcardListen(
this−>setupWildcardListen(event, $listener);
} else {
// 正常事件名
t
h
i
s
−
>
l
i
s
t
e
n
e
r
s
[
this->listeners[
this−>listeners[event][] =
t
h
i
s
−
>
m
a
k
e
L
i
s
t
e
n
e
r
(
this->makeListener(
this−>makeListener(listener);
}
}
}
绑定事件和监听者闭包的映射
protected function setupWildcardListen($event, $listener)
{
// 当一系列的事件都想触发指定的监听者的时候 就可以使用*进行匹配
t
h
i
s
−
>
w
i
l
d
c
a
r
d
s
[
this->wildcards[
this−>wildcards[event][] =
t
h
i
s
−
>
m
a
k
e
L
i
s
t
e
n
e
r
(
this->makeListener(
this−>makeListener(listener, true);
// 每次更新了通配事件后 都清除缓存
$this->wildcardsCache = [];
}
官方注释表名此方法向事件分发器中注册一个事件监听者
其实就是返回事件触发时执行的监听者闭包
传入的listener可以使App\Listener\MyListener 或 App\Listener\MyListener@myHandle这种字符串
或者是一个接收两个参数的闭包
public function makeListener($listener, KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_string(listener)) {
// 如果传递的是一个字符串的话 调用createClassListener放回闭包
return
t
h
i
s
−
>
c
r
e
a
t
e
C
l
a
s
s
L
i
s
t
e
n
e
r
(
this->createClassListener(
this−>createClassListener(listener, $wildcard);
}
// 如果listener是个闭包 那么直接将事件对象作为参数传入监听者
// 事件触发的时候 直接执行此闭包
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return $listener($event, $payload);
}
// 可变数量的参数列表
return $listener(...array_values($payload));
};
}
createClassListener方法
public function createClassListener($listener, KaTeX parse error: Expected '}', got 'EOF' at end of input: …turn function (event,
p
a
y
l
o
a
d
)
u
s
e
(
payload) use (
payload)use(listener, KaTeX parse error: Expected '}', got 'EOF' at end of input: … { if (wildcard) {
// createClassCallable返回一个数组 第一个参数是
l
i
s
t
e
n
e
r
的
实
例
第
二
个
参
数
是
m
e
t
h
o
d
r
e
t
u
r
n
c
a
l
l
u
s
e
r
f
u
n
c
(
listener的实例 第二个参数是method return call_user_func(
listener的实例第二个参数是methodreturncalluserfunc(this->createClassCallable($listener), $event, $payload);
}
return call_user_func_array(
$this->createClassCallable($listener), $payload
);
};
}
createClassCallable方法
protected function createClassCallable(KaTeX parse error: Expected '}', got 'EOF' at end of input: …中获取类名和方法名 [class, $method] = t h i s − > p a r s e C l a s s C a l l a b l e ( this->parseClassCallable( this−>parseClassCallable(listener);
// 判断是否需要队列化监听器
if ($this->handlerShouldBeQueued($class)) {
// class类名 method 方法名
return $this->createQueuedHandlerCallable($class, $method);
}
// 如果不需要异步化执行监听者 直接返回[$listener, 'method']数组
// class通过container获得 意味着我们可以利用容器方便的注入listner需要的依赖
// 注意此处返回的是listener的实例 和 调用监听者时执行的方法名
return [$this->container->make($class), $method];
}
handlerShouldBeQueued方法 判断如果一个监听者实现了ShouldQueue接口 就认为此监听者需要队列化执行
protected function handlerShouldBeQueued(KaTeX parse error: Expected '}', got 'EOF' at end of input: …eflectionClass(class))->implementsInterface(
ShouldQueue::class
);
} catch (Exception $e) {
return false;
}
}
createQueuedHandlerCallable方法
protected function createQueuedHandlerCallable($class, KaTeX parse error: Expected '}', got 'EOF' at end of input: …nction () use (class, $method) {
a
r
g
u
m
e
n
t
s
=
a
r
r
a
y
m
a
p
(
f
u
n
c
t
i
o
n
(
arguments = array_map(function (
arguments=arraymap(function(a) {
return is_object($a) ? clone $a : KaTeX parse error: Expected 'EOF', got '}' at position 12: a; }̲, func_get_args…this->handlerWantsToBeQueued($class, $arguments)) {
t
h
i
s
−
>
q
u
e
u
e
H
a
n
d
l
e
r
(
this->queueHandler(
this−>queueHandler(class, $method, $arguments);
}
};
}
handlerWantsToBeQueued
protected function handlerWantsToBeQueued($class, $arguments)
{
$instance =
t
h
i
s
−
>
c
o
n
t
a
i
n
e
r
−
>
m
a
k
e
(
this->container->make(
this−>container−>make(class);
// 动态判断是否需要异步化事件处理
// 需要我们在监听器shouldQueue方法中return bool值
if (method_exists($instance, 'shouldQueue')) {
// 可以在监听者的shouldQueue方法中返回bool值 动态判断是否需要异步化
return $instance->shouldQueue($arguments[0]);
}
return true;
}
queueHandler方法
// 判断listener的各种属性 将监听者投递到队列
// laravel 队列以后会单独讲解 此篇先到这里
protected function queueHandler($class, $method, KaTeX parse error: Expected '}', got 'EOF' at end of input: …uments) { [listener, $job] =
t
h
i
s
−
>
c
r
e
a
t
e
L
i
s
t
e
n
e
r
A
n
d
J
o
b
(
this->createListenerAndJob(
this−>createListenerAndJob(class, $method, $arguments);
// resolveQueue获取注册事件服务时设置的queueResolver
$connection = $this->resolveQueue()->connection(
$listener->connection ?? null
);
$queue = $listener->queue ?? null;
isset($listener->delay)
? $connection->laterOn($queue, $listener->delay, $job)
: $connection->pushOn($queue, $job);
}
以上便是事件注册的基本代码 总体来说 我们看到调用Dispatcher的listen方法 可以注册监听者和事件的绑定
监听者都已闭包的形式进行包裹 这样的好处是可以保存上下文变量
涉及到的异步处理 其他文章会进行讲解
值得注意的是 注册好的闭包 并不会执行 当触发相应的事件时才会执行
事件的触发
业务代码中调用event()方法就可以触发一个事件了 执行的就是Dispatch::dispatch方法
public function dispatch($event, $payload = [], KaTeX parse error: Expected '}', got 'EOF' at end of input: …eEvent()) [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;
}
// 如果某个监听者返回了false 那么终止后续监听者的执行
if ($response === false) {
break;
}
$responses[] = $response;
}
// 返回结果给事件触发
return $halt ? null : $responses;
}
parseEventAndPayload方法
protected function parseEventAndPayload($event, KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (is_object(event)) {
[$payload,
e
v
e
n
t
]
=
[
[
event] = [[
event]=[[event], get_class($event)];
}
// 如果event是一个字符串 那么直接包装payload
return [$event, Arr::wrap($payload)];
}
// 获取所有事件监听者
public function getListeners($eventName)
{
$listeners =
t
h
i
s
−
>
l
i
s
t
e
n
e
r
s
[
this->listeners[
this−>listeners[eventName] ?? [];
$listeners = array_merge(
$listeners,
$this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
);
// 如果插入的event类存在
return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}
addInterfaceListeners方法
protected function addInterfaceListeners($eventName, array KaTeX parse error: Expected '}', got 'EOF' at end of input: …ass_implements(eventName) as KaTeX parse error: Expected '}', got 'EOF' at end of input: … if (isset(this->listeners[KaTeX parse error: Expected '}', got 'EOF' at end of input: … foreach (this->listeners[$interface] as $names) {
l
i
s
t
e
n
e
r
s
=
a
r
r
a
y
m
e
r
g
e
(
listeners = array_merge(
listeners=arraymerge(listeners, (array) $names);
}
}
}
// 返回合并后的监听者
return $listeners;
}
部分其他方法
until方法
触发事件 并返回第一个不为null的监听器结果
public function until($event, $payload = [])
{
return
t
h
i
s
−
>
d
i
s
p
a
t
c
h
(
this->dispatch(
this−>dispatch(event, $payload, true);
}
push方法
调用的还是listen方法 只不过指定了payload参数
public function push($event, $payload = [])
{
t
h
i
s
−
>
l
i
s
t
e
n
(
this->listen(
this−>listen(event.’_pushed’, function () use ($event, $payload) {
t
h
i
s
−
>
d
i
s
p
a
t
c
h
(
this->dispatch(
this−>dispatch(event, $payload);
});
}
flush方法 调用push注册的监听者
public function flush($event)
{
t
h
i
s
−
>
d
i
s
p
a
t
c
h
(
this->dispatch(
this−>dispatch(event.’_pushed’);
}
第三部分 使用
使用一 通过触发事件给监听者传参
1 在App\Porviders\EventServiceProvider的listen属性中绑定事件和监听者映射关系
…
use App\Events\TestEvent1;
use App\Listeners\TestListener1;
use App\Listeners\TestListener2;
…
protected $listen = [
…
TestEvent1::class => [
TestListener1::class,
// 自定义监听者闭包调用的方法myHandle
TestListener2::class . ‘@myHandle’
]
];
…
2 php artisan event:generate 按照listen数组的事件监听者映射生成
3 我们不在TestEvent1事件中做过多处理 在本示例中保持原样即可
4 编写TestListener1文件
<?php namespace App\Listeners; use App\Components\Log\LogManager; use App\Events\TestEvent1; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use App\Providers\LogManagerServiceProvider; class TestListener1 { protected $logger; // 通过上面的分析 我们知道监听者时通过容器解析出来的 所以可以尽情的注入 public function __construct(LogManager $logger) { $this->logger = $logger; } // 自定义传参给事件监听者 public function handle(TestEvent1 $event, string $type) { // dump($type); // dump(debug_backtrace()); $this->logger->driver($type)->logCertains('emergency', 'something emergency'); } } 5 编写TestListener2文件 <?php namespace App\Listeners; use App\Events\TestEvent1; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; class TestListener2 { /** * Create the event listener. * * @return void */ public function __construct() { // } // 其实指定了myHandle方法之后 原生的handle方法就可以注释或者什么都不写了 // 通过上面的分析我们知道 框架会执行监听者中所有以handle开头的方法 // public function handle(TestEvent1 $event) // { // echo '345'; // } // 此方法也是会执行的 // public function handleabc(TestEvent1 $event) // { // echo 'abc'; // } public function myHandle(TestEvent1 $event) { dump('do whatever you like'); } } 6 编写测试路由 触发事件 Route::get('event1', function () { // 情况一 传递的是事件实例 如果你需要在事件实例中注入依赖 当然可以使用容器解析事件对象 // 当注入的是一个事件对象的时候 会触发事件类名这个事件 并且将事件对象作为payload传递给handle方法 // Event::dispatch(new \App\Events\TestEvent1()); // 情况二 传递的是事件名 第二个参数生效 // 演示如何不依赖事件对象 传递参数 Event::dispatch(TestEvent1::class, [new TestEvent1(), 'stream']); }); 使用二 设置事件自动发现 1 在App\Porviders\EventServiceProvider中重写shouldDiscoverEvents方法 启用事件发现 // 启用事件自动发现 public function shouldDiscoverEvents() { return true; } 2 我们更近一步 设置自动监听者所在目录 重写discoverEventsWithin方法 指定自动发现目录 // 指定自动发现目录 // 默认的就是app_path('Listeners') public function discoverEventsWithin() { return [ // 这里可以注释掉Listeners目录为了避免和上面的listen属性重复 导致所有的监听者都会执行两遍 // app_path('Listeners'), app_path('MyListeners') ]; } 3 编写事件App\Events\TestEvent2文件 我这里的代码没有任何的实际意义 <?php namespace App\Events; class TestEvent2 { protected $name = 'xy'; public function __construct() { // 随意发挥 } public function getName() { return $this->name; } } 4 手动创建App\MyListeners\Listener1文件 # 通过上面的源码分析 我们知道laravel会将所有以handle开头的方法参数遍历 # 然后将第一个参数的类名作为要触发的事件名,将事件参数作为payload传入 <?php namespace App\MyListeners; use App\Events\TestEvent2; class MyListener1 { public function handle(TestEvent2 $evt) { dump($evt->getName(), 'abc'); return false; // 如果不注释掉此行代码,事件的调用链到此终结 } public function handleAbc(TestEvent2 $evt) { dump($evt->getName()); } } 5 手动创建App\MyListeners\Listener2文件 <?php namespace App\MyListeners; use App\Events\TestEvent2; class MyListener2 { public function handle(TestEvent2 $evt) { dump($evt->getName()); } } 6 创建事件自动发现路由 // 测试自动发现 Route::get('event2', function(){ Event::dispatch(new TestEvent2()); }); 使用三 implement的使用 当事件实现了其他事件接口,会自动触发其他事件绑定的监听者 对应的方法为Dispatcher::addInterfaceListeners 请看第二部分 1 创建事件接口 <?php namespace App\Events; interface TestEvent3 { } <?php namespace App\Events; interface TestEvent4 { } 2 创建监听者 <?php namespace App\Listeners; class TestListener3 { public function handle() { dump('listener3 added by event interface3'); } } <?php namespace App\Listeners; class TestListener4 { public function handle() { dump('listener3 added by event interface4'); } } <?php namespace App\Listeners; class TestListener5 implements TestEvent3, TestEvent4 { public function handle() { dump('five own listener'); } } 3 事件实现上面的两个接口 <?php namespace App\Events; class TestEvent5 implements TestEvent3, TestEvent4 { } 4 注册事件监听者 protected $listen = [ ... TestEvent3::class => [ TestListener3::class ], TestEvent4::class => [ TestListener4::class ], # 甚至可以注释掉下面3行 只需要TestEvent5实现上面两个接口即可触发上面注册的监听者 TestEvent5::class => [ TestListener5::class ] ]; 5 最重要的一步 force and brutal 改源码 没错 就是改源码 # Dispatcher::getListeners方法 ... // return class_exists($eventName, false) return class_exists($eventName) ? $this->addInterfaceListeners($eventName, $listeners) : $listeners; ... 6 创建测试路由 Route::get('event5', function () { Event::dispatch(TestEvent5::class); }); 使用四 until和flush until方法默认调用dispatch方法 当时间监听者返回不为null则停止执行后面的监听者 并返回结果给事件触发位置 1 配置时间监听者 protected $listen = [ ... TestEvent6::class => [ TestListener6::class, TestListener7::class, TestListener8::class ] ]; 2 php artisan event:generate 3 简单编写事件监听者 # listener6 public function handle(TestEvent6 $event) { dump('return null'); } # listener7 public function handle(TestEvent6 $event) { // 注意此监听者是有返回值的 return 123; } # listener8 public function handle(TestEvent6 $event) { // 并不会执行7后面的监听者 根本就不会执行 return 'return something in vain'; } 4 编写测试路由 Route::get('event6', function () { $res = Event::until(new TestEvent6()); // 可以看到监听者8并没有执行 因为7返回的不是null dump($res); }); 使用五 push&flush 请查看上面的源码分析 push方法就是提前将event和payload注册好 供flush调用 1 在App\Providers\EventServiceProvider::boot方法中注册(这里只是举例在boot中进行注册,你可以在你喜欢的任何地方注册) public function boot() { parent::boot(); // 注册一个保存了payload的事件监听者 Event::push('longAssEventName', ['type' => 'redis']); Event::listen('longAssEventName', function ($type) { dump($type); }); } 2 创建测试路由 Route::get('event7', function () { Event::flush('longAssEventName'); }); 龙华大道1号 http://www.kinghill.cn/Dynamics/2106.html