思考并回答以下问题:
前言
Laravel的事件系统是一个简单的观察者模式,主要目的是用于代码的解耦,可以防止不同功能的代码耦合在一起。Laravel中事件系统由两部分构成,一个是事件的名称,事件的名称可以是个字符串,例如event.email,也可以是一个事件类,例如App\Events\OrderShipped;另一个是事件的Listener,可以是一个闭包,还可以是监听类,例如App\Listeners\SendShipmentNotification。
事件服务的注册
事件服务的注册分为两部分,一个是Application启动时所调用的registerBaseServiceProviders函数:
Illuminate\Foundation\Application.php
1 | /** |
其中的EventServiceProvider是/Illuminate/Events/EventServiceProvider:
Illuminate\Support\ServiceProvider.php
1 | abstract class ServiceProvider |
Illuminate\Events\EventServiceProvider.php
1 |
|
这部分为IoC容器注册了events实例,Dispatcher就是events真正的实现类。QueueResolver是队列化事件的实现。
另一个注册是普通注册类/app/Providers/EventServiceProvider:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen =
[
'App\Events\SomeEvent' => [
'App\Listeners\EventListener',
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}
这个注册类的主要作用是事件系统的启动,这个类继承自/Illuminate/Foundation/Support/Providers/EventServiceProvider:
1 |
|
可以看到,事件系统的启动主要是进行事件系统的监听与订阅。
事件系统的监听Listen
所谓的事件监听,就是将事件名与闭包函数,或者事件类与监听类之间建立关联。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35/**
* Register an event listener with the dispatcher.
*
* @param string|array $events
* @param \Closure|string $listener
* @return void
*/
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);
}
}
}
/**
* Setup a wildcard listener callback.
*
* @param string $event
* @param \Closure|string $listener
* @return void
*/
protected function setupWildcardListen($event, $listener)
{
$this->wildcards[$event][] = $this->makeListener($listener, true);
$this->wildcardsCache = [];
}
对于有通配符的事件名,会统一放入wildcards数组中,makeListener是创建事件的关键:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* Register an event listener with the dispatcher.
*
* @param \Closure|string $listener
* @param bool $wildcard
* @return \Closure
*/
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会再包上一层闭包函数,根据是否含有通配符来确定具体的参数。
对于监听类来说,会继续createClassListener:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38/**
* Create a class based listener using the IoC container.
*
* @param string $listener
* @param bool $wildcard
* @return \Closure
*/
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
);
};
}
/**
* Create the class based event callable.
*
* @param string $listener
* @return callable
*/
protected function createClassCallable($listener)
{
[$class, $method] = $this->parseClassCallable($listener);
if ($this->handlerShouldBeQueued($class))
{
return $this->createQueuedHandlerCallable($class, $method);
}
return [$this->container->make($class), $method];
}
对于监听类来说,程序首先会判断监听类对应的函数:1
2
3
4
5
6
7
8
9
10/**
* Parse the class listener into class and method.
*
* @param string $listener
* @return array
*/
protected function parseClassCallable($listener)
{
return Str::parseCallback($listener, 'handle');
}
如果未指定监听类的对应函数,那么会默认handle函数。
如果当前监听类是队列的话,会将任务推送给队列。
触发事件
事件的触发可以利用事件名,或者事件类的实例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46/**
* 触发事件并调用侦听器
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return array|null
*/
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;
}
// 如果从侦听器返回false,则我们将停止将事件传播到链中任何其他侦听器,
// 否则我们将继续遍历侦听器并触发序列中的每个侦听器。
if ($response === false)
{
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
parseEventAndPayload函数利用传入参数是事件名还是事件类实例来确定监听类函数的参数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/**
* 解析给定的事件和有效负载,并为分发做好准备
*
* @param mixed $event
* @param mixed $payload
* @return array
*/
protected function parseEventAndPayload($event, $payload)
{
if (is_object($event))
{
[$payload, $event] = [[$event], get_class($event)];
}
return [$event, Arr::wrap($payload)];
}
如果是事件类的实例,那么监听函数的参数就是事件类自身;如果是事件类名,那么监听函数的参数就是触发事件时传入的参数。
获得事件与参数后,就要获取监听类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* Get all of the listeners for a given event name.
*
* @param string $eventName
* @return array
*/
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;
}
寻找监听类的时候,也要从通配符监听器中寻找:1
2
3
4
5
6
7
8
9
10
11
12
13
14protected function getWildcardListeners($eventName)
{
$wildcards = [];
foreach ($this->wildcards as $key => $listeners)
{
if (Str::is($key, $eventName))
{
$wildcards = array_merge($wildcards, $listeners);
}
}
return $wildcards;
}
如果监听类继承自其他类,那么父类也会一并当做监听类返回。
获得了监听类之后,就要调用监听类相应的函数。
触发事件时有一个参数halt,这个参数如果是true的时候,只要有一个监听类返回了结果,那么就会立刻返回。例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public function testHaltingEventExecution()
{
unset($_SERVER['__event.test']);
$d = new Dispatcher;
$d->listen('foo', function ($foo) {
$this->assertTrue(true);
return 'here';
});
$d->listen('foo', function ($foo) {
throw new Exception('should not be called');
});
$d->until('foo', ['bar']);
}
多个监听类在运行的时候,只要有一个返回了false,那么就会中断事件。
push函数
push函数可以将触发事件的参数事先设置好,这样触发的时候只要写入事件名即可,例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public function testQueuedEventsAreFired()
{
unset($_SERVER['__event.test']);
$d = new Dispatcher;
$d->push('update', ['name' => 'taylor']);
$d->listen('update', function ($name) {
$_SERVER['__event.test'] = $name;
});
$this->assertFalse(isset($_SERVER['__event.test']));
$d->flush('update');
$this->assertEquals('taylor', $_SERVER['__event.test']);
}
原理也很简单:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* Register an event and payload to be fired later.
*
* @param string $event
* @param array $payload
* @return void
*/
public function push($event, $payload = [])
{
$this->listen($event.'_pushed', function () use ($event, $payload) {
$this->dispatch($event, $payload);
});
}
/**
* Flush a set of pushed events.
*
* @param string $event
* @return void
*/
public function flush($event)
{
$this->dispatch($event.'_pushed');
}
数据库Eloquent的事件
数据库模型的事件的注册除了以上的方法还有另外两种,具体详情可以看:Laravel模型事件实现原理;
事件注册
静态方法定义
1 | class EventServiceProvider extends ServiceProvider |
观察者
1 | class UserObserver |
然后在某个服务提供者的boot方法中注册观察者:1
2
3
4
5
6
7
8
9
10
11
12class AppServiceProvider extends ServiceProvider
{
public function boot()
{
User::observe(UserObserver::class);
}
public function register()
{
}
}
这两种方法都是向事件系统注册事件名eloquent.{$event}:{static::class}:
静态方法
1 | /** |
观察者
1 | /** |
事件触发
模型事件的触发需要调用fireModelEvent函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31/**
* Fire the given event for the model.
*
* @param string $event
* @param bool $halt
* @return mixed
*/
protected function fireModelEvent($event, $halt = true)
{
if (! isset(static::$dispatcher))
{
return true;
}
// 首先,我们将获得适当的方法来调用事件分配器,然后尝试为给定事件触发基于对象的自定义事件。
// 如果返回一个结果,我们可以返回该结果,或者我们将调用字符串事件。
$method = $halt ? 'until' : 'dispatch';
$result = $this->filterModelEventResults(
$this->fireCustomModelEvent($event, $method)
);
if ($result === false)
{
return false;
}
return ! empty($result) ? $result : static::$dispatcher->{$method}(
"eloquent.{$event}: ".static::class, $this
);
}
fireCustomModelEvent是我们本文中着重讲的事件类与监听类的触发:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* Fire a custom model event for the given event.
*
* @param string $event
* @param string $method
* @return mixed|null
*/
protected function fireCustomModelEvent($event, $method)
{
if (! isset($this->dispatchesEvents[$event]))
{
return;
}
$result = static::$dispatcher->$method(new $this->dispatchesEvents[$event]($this));
if (! is_null($result))
{
return $result;
}
}
如果没有对应的事件后,会继续利用事件名进行触发。
until是我们上一节讲的如果任意事件返回正确结果,就会直接返回,不会继续进行下一个事件。