有了之前的《简述 Laravel Model Events 的使用》https://mp.weixin.qq.com/s/XrhDq1S5RC9UdeULVVksoA,大致了解了 Event
的使用。
今天我们就来扒一扒 Event
的源码。
开始之前,需要说下两个 EventServiceProvider
的区别:
App\Providers\EventServiceProvider
Illuminate\Events\EventServiceProvider
第一个 App\Providers\EventServiceProvider
主要是定义 event
和 listener
的关联;第二个 Illuminate\Events\EventServiceProvider
是 Laravel
的三大基础 ServiceProvider
之一,主要负责「分派」工作。
好了,下面开始具体的分析工作。
App\Providers\EventServiceProvider
主要是定义 event
和 listener
的关联,如:
<?php namespace App\Providers; use Illuminate\Support\Facades\Event; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = [ 'App\Events\RssPublicEvent' => [ 'App\Listeners\RssPublicListener1', ], 'App\Events\*Event' => [ 'App\Listeners\RssPublicListener2', 'App\Listeners\RssPublicListener3', ], 'Illuminate\Contracts\Broadcasting\ShouldBroadcast' => [ 'App\Listeners\RssPublicListener4', 'App\Listeners\RssPublicListener5', ], ]; /** * Register any events for your application. * * @return void */ public function boot() { parent::boot(); } }
主要继承 Illuminate\Foundation\Support\Providers\EventServiceProvider
:
<?php namespace Illuminate\Foundation\Support\Providers; use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * The event handler mappings for the application. * * @var array */ protected $listen = []; /** * The subscriber classes to register. * * @var array */ protected $subscribe = []; /** * Register the application's event listeners. * * @return void */ 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); } } /** * {@inheritdoc} */ public function register() { // } /** * Get the events and handlers. * * @return array */ public function listens() { return $this->listen; } }
把定义 event
和 listener
的关联交给用户自己去做,然后父类 EventServiceProvider
只是做关联工作,在 boot()
中:
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);
}
}
这里主要看两个函数:
Event::listen($event, $listener);
Event::subscribe($subscriber);
就这么简单,我们说完了第一个 EventServiceProvider
,我们开始第二个。
Illuminate\Events\EventServiceProvider
看过之前文章的知道,Event
有个全局函数:
Artisan::command('public_echo', function () {
event(new RssPublicEvent());
})->describe('echo demo');
...
if (! function_exists('event')) {
/** * Dispatch an event and call the listeners. * * @param string|object $event * @param mixed $payload * @param bool $halt * @return array|null */
function event(...$args)
{
return app('events')->dispatch(...$args);
}
}
而 Illuminate\Events\EventServiceProvider
,是 Laravel
三个基础 ServiceProvider
之一:
/** * Register all of the base service providers. * * @return void */
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
我们接着看 Illuminate\Events\EventServiceProvider
:
<?php namespace Illuminate\Events; use Illuminate\Support\ServiceProvider; use Illuminate\Contracts\Queue\Factory as QueueFactoryContract; class EventServiceProvider extends ServiceProvider { /** * Register the service provider. * * @return void */ public function register() { $this->app->singleton('events', function ($app) { return (new Dispatcher($app))->setQueueResolver(function () use ($app) { return $app->make(QueueFactoryContract::class); }); }); } }
它注册了单例形式,并创建和返回 Dispatcher
对象:
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Contracts\Broadcasting\Factory as BroadcastFactory;
use Illuminate\Contracts\Container\Container as ContainerContract;
class Dispatcher implements DispatcherContract
{
/** * The IoC container instance. * * @var \Illuminate\Contracts\Container\Container */
protected $container;
/** * The registered event listeners. * * @var array */
protected $listeners = [];
/** * The wildcard listeners. * * @var array */
protected $wildcards = [];
/** * The queue resolver instance. * * @var callable */
protected $queueResolver;
...
}
主要实现 Dispatcher
接口:
<?php namespace Illuminate\Contracts\Events; interface Dispatcher { /** * Register an event listener with the dispatcher. * * @param string|array $events * @param mixed $listener * @return void */ public function listen($events, $listener); /** * Determine if a given event has listeners. * * @param string $eventName * @return bool */ public function hasListeners($eventName); /** * Register an event subscriber with the dispatcher. * * @param object|string $subscriber * @return void */ public function subscribe($subscriber); /** * Dispatch an event until the first non-null response is returned. * * @param string|object $event * @param mixed $payload * @return array|null */ public function until($event, $payload = []); /** * Dispatch an event and call the listeners. * * @param string|object $event * @param mixed $payload * @param bool $halt * @return array|null */ public function dispatch($event, $payload = [], $halt = false); /** * Register an event and payload to be fired later. * * @param string $event * @param array $payload * @return void */ public function push($event, $payload = []); /** * Flush a set of pushed events. * * @param string $event * @return void */ public function flush($event); /** * Remove a set of listeners from the dispatcher. * * @param string $event * @return void */ public function forget($event); /** * Forget all of the queued listeners. * * @return void */ public function forgetPushed(); }
下面我们来解说每一个函数。
listen()
Register an event listener with the dispatcher.
public function listen($events, $listener)
{
foreach ((array) $events as $event) {
if (Str::contains($event, '*')) {
$this->wildcards[$event][] = $this->makeListener($listener, true);
} else {
$this->listeners[$event][] = $this->makeListener($listener);
}
}
}
这就好理解了,把通配符的放在 wildcards
数组中,另一个放在 listeners
数组中。接下来看函数 makeListener()
。
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));
};
}
如果传入的 $listener
为字符串,则执行函数 createClassListener
:
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
);
};
}
先来看看函数 createClassCallable()
:
protected function createClassCallable($listener)
{
list($class, $method) = Str::parseCallback($listener, 'handle');
if ($this->handlerShouldBeQueued($class)) {
return $this->createQueuedHandlerCallable($class, $method);
}
return [$this->container->make($class), $method];
}
第一个函数还是很好理解:
public static function parseCallback($callback, $default = null)
{
return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
}
就看传入的 listener
是不是 class@method
结构,如果是就用 @
分割,否则就默认的就是 class
类名,然后 method
就是默认的 handle
函数 —— 这也是我们创建 Listener
类提供的做法。
接着就看是否可以放入队列中:
protected function handlerShouldBeQueued($class)
{
try {
return (new ReflectionClass($class))->implementsInterface(
ShouldQueue::class
);
} catch (Exception $e) {
return false;
}
}
也就判断该 listener
类是否实现了接口类 ShouldQueue
。如果实现了,则可以将该类放入队列中 (返回闭包函数):
protected function createQueuedHandlerCallable($class, $method)
{
return function () use ($class, $method) {
$arguments = array_map(function ($a) {
return is_object($a) ? clone $a : $a;
}, func_get_args());
if ($this->handlerWantsToBeQueued($class, $arguments)) {
$this->queueHandler($class, $method, $arguments);
}
};
}
我们接着看 handlerWantsToBeQueued
:
protected function handlerWantsToBeQueued($class, $arguments)
{
if (method_exists($class, 'shouldQueue')) {
return $this->container->make($class)->shouldQueue($arguments[0]);
}
return true;
}
所以说,如果在 listener
类中写了 shouldQueue
方法,则就看该方法是不是返回 true
或者 false
来决定是否放入队列中:
protected function queueHandler($class, $method, $arguments)
{
list($listener, $job) = $this->createListenerAndJob($class, $method, $arguments);
$connection = $this->resolveQueue()->connection(
$listener->connection ?? null
);
$queue = $listener->queue ?? null;
isset($listener->delay)
? $connection->laterOn($queue, $listener->delay, $job)
: $connection->pushOn($queue, $job);
}
*注:*和队列相关的放在之后再做分析,此处省略
好了,回到开始的地方:
// createClassCallable($listener)
return [$this->container->make($class), $method];
到此,也就明白了,如果是 通配符
的,则对应的执行函数 (默认的为: handle
) 传入的参数有两个:$event
事件对象和 $payload
;否则对应执行函数,传入的参数就只有一个了:$payload
。
同理,如果传入的 listener
是个函数的话,返回的闭包就这样的:
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return $listener($event, $payload);
}
return $listener(...array_values($payload));
};
整个流程就通了,listener
函数的作用就是:在 Dispatcher
中的 $listeners
和 $wildcards
的数组中,存储 ['event' => Callback]
的结构数组,以供执行使用。
说完了第一个函数 Event::listen()
,第二个函数了:Event::subscribe()
,留着之后再说。
好了,整个 event
和 listener
就关联在一起了。接下来就开始看执行方法了。
dispatch()
Dispatch an event and call the listeners.
正如上文的 helpers
定义的,所有 Event
都是通过该函数进行「分发」事件和调用所关联的 listeners
:
/** * Fire an event and call the listeners. * * @param string|object $event * @param mixed $payload * @param bool $halt * @return array|null */
public function dispatch($event, $payload = [], $halt = false)
{
// When the given "event" is actually an object we will assume it is an event
// object and use the class as the event name and this event itself as the
// payload to the handler, which makes object based events quite simple.
list($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 a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ($halt && ! is_null($response)) {
return $response;
}
// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
if ($response === false) {
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
先理解注释的函数 parseEventAndPayload()
:
When the given “event” is actually an object we will assume it is an event object and use the class as the event name and this event itself as the payload to the handler, which makes object based events quite simple.
protected function parseEventAndPayload($event, $payload)
{
if (is_object($event)) {
list($payload, $event) = [[$event], get_class($event)];
}
return [$event, Arr::wrap($payload)];
}
如果 $event
是个对象,则将 $event
的类名作为事件的名称,并将该事件 [$event]
作为 $payload
。
接着判断 $payload
是否可以「广播」出去,如果可以,那就直接广播出去:
protected function shouldBroadcast(array $payload)
{
return isset($payload[0]) &&
$payload[0] instanceof ShouldBroadcast &&
$this->broadcastWhen($payload[0]);
}
就拿上文的例子来说吧:
<?php namespace App\Events; use Carbon\Carbon; use Illuminate\Broadcasting\Channel; use Illuminate\Queue\SerializesModels; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class RssPublicEvent implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; /** * Create a new event instance. * * @return void */ public function __construct() { // } /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn() { return new Channel('public_channel'); } /** * 指定广播数据。 * * @return array */ public function broadcastWith() { // 返回当前时间 return ['name' => 'public_channel_'.Carbon::now()->toDateTimeString()]; } }
首先它实现接口 ShouldBroadcast
,然后看是不是还有额外的条件来决定是否可以广播:
/** * Check if event should be broadcasted by condition. * * @param mixed $event * @return bool */
protected function broadcastWhen($event)
{
return method_exists($event, 'broadcastWhen')
? $event->broadcastWhen() : true;
}
由于本实例没有实现 broadcastWhen
方法,所以返回默认值 true
。
所以可以将本实例广播出去:
/** * Broadcast the given event class. * * @param \Illuminate\Contracts\Broadcasting\ShouldBroadcast $event * @return void */
protected function broadcastEvent($event)
{
$this->container->make(BroadcastFactory::class)->queue($event);
}
这就交给 BroadcastManager
来处理了,此文不再继续深挖。
注:下篇文章我们再来扒一扒
BroadcastManager
源码
当把事件广播出去后,我们就开始执行该事件的各个监听了。通过之前的文章知道,一个 Event
,不止一个 Listener
监听,所以需要通过一个 foreach
循环来遍历执行 Listener
,首先获取这些 Listener
:
/** * 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->getWildcardListeners($eventName)
);
return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}
该方法主要通过三种方式累加获取所有 listeners
:该类中的属性:$listeners
和 $wildcards
,以及如果该 $event
是个对象的,还包括该类的所有接口关联的 listeners
数组。
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;
}
*注:*class_implements — 返回指定的类实现的所有接口。
接下来就是执行每个 listener
了:
$response = $listener($event, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ($halt && ! is_null($response)) {
return $response;
}
// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
if ($response === false) {
break;
}
$responses[] = $response;
由上文可以知道 $listener
,实际上就是一个闭包函数,最终的结果相当于执行 handle
函数:
public function handle(RssPublicEvent $event)
{
info('listener 1');
}
...
public function handle(RssPublicEvent $event, array $payload)
{
info('listener 2');
}
写个 demo
我们写个 demo
,在 EventServiceProvider
的 listen
数组,填入这三种方式的关联情况:
protected $listen = [
'App\Events\RssPublicEvent' => [
'App\Listeners\RssPublicListener1',
],
'App\Events\*Event' => [
'App\Listeners\RssPublicListener2',
'App\Listeners\RssPublicListener3',
],
'Illuminate\Contracts\Broadcasting\ShouldBroadcast' => [
'App\Listeners\RssPublicListener4',
'App\Listeners\RssPublicListener5',
],
];
然后在每个 RssPublicListener*
的 handle
方法输出对应的值,最后运行 php artisan public_echo
,看结果:
[2018-10-06 20:05:57] local.INFO: listener 1
[2018-10-06 20:05:58] local.INFO: listener 2
[2018-10-06 20:05:59] local.INFO: listener 3
[2018-10-06 20:05:59] local.INFO: listener 4
[2018-10-06 20:06:00] local.INFO: listener 5
其他函数
说完了执行函数,基本上也就说完了整个 Event
事件流程了。剩下的只有一些附属函数,一看基本都理解:
/** * 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');
}
/** * Determine if a given event has listeners. * * @param string $eventName * @return bool */
public function hasListeners($eventName)
{
return isset($this->listeners[$eventName]) || isset($this->wildcards[$eventName]);
}
/** * Fire an event until the first non-null response is returned. * * @param string|object $event * @param mixed $payload * @return array|null */
public function until($event, $payload = [])
{
return $this->dispatch($event, $payload, true);
}
/** * Remove a set of listeners from the dispatcher. * * @param string $event * @return void */
public function forget($event)
{
if (Str::contains($event, '*')) {
unset($this->wildcards[$event]);
} else {
unset($this->listeners[$event]);
}
}
/** * Forget all of the pushed listeners. * * @return void */
public function forgetPushed()
{
foreach ($this->listeners as $key => $value) {
if (Str::endsWith($key, '_pushed')) {
$this->forget($key);
}
}
}
总结
对 Event
做了比较详细的梳理,大致了解了它的整个流程,下一步就是看看怎么和队列结合在一起,和利用「观察者模式」的那部分代码逻辑了。