有了之前的《簡述 Laravel Model Events 的使用》mp.weixin.qq.com/s/XrhDq1S5R…,大致瞭解了 Event
的使用。
今天我們就來扒一扒 Event
的原始碼。
開始之前,需要說下兩個 EventServiceProvider
的區別:
AppProvidersEventServiceProvider
IlluminateEventsEventServiceProvider
第一個 AppProvidersEventServiceProvider
主要是定義 event
和 listener
的關聯;第二個 IlluminateEventsEventServiceProvider
是 Laravel
的三大基礎 ServiceProvider
之一,主要負責「分派」工作。
好了,下面開始具體的分析工作。
AppProvidersEventServiceProvider
主要是定義 event
和 listener
的關聯,如:
<?php
namespace AppProviders;
use IlluminateSupportFacadesEvent;
use IlluminateFoundationSupportProvidersEventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
`AppEventsRssPublicEvent` => [
`AppListenersRssPublicListener1`,
],
`AppEvents*Event` => [
`AppListenersRssPublicListener2`,
`AppListenersRssPublicListener3`,
],
`IlluminateContractsBroadcastingShouldBroadcast` => [
`AppListenersRssPublicListener4`,
`AppListenersRssPublicListener5`,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
}
}
複製程式碼
主要繼承 IlluminateFoundationSupportProvidersEventServiceProvider
:
<?php
namespace IlluminateFoundationSupportProviders;
use IlluminateSupportFacadesEvent;
use IlluminateSupportServiceProvider;
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
,我們開始第二個。
IlluminateEventsEventServiceProvider
看過之前文章的知道,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);
}
}
複製程式碼
而 IlluminateEventsEventServiceProvider
,是 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));
}
複製程式碼
我們接著看 IlluminateEventsEventServiceProvider
:
<?php
namespace IlluminateEvents;
use IlluminateSupportServiceProvider;
use IlluminateContractsQueueFactory 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 IlluminateContractsEventsDispatcher as DispatcherContract;
use IlluminateContractsBroadcastingFactory as BroadcastFactory;
use IlluminateContractsContainerContainer as ContainerContract;
class Dispatcher implements DispatcherContract
{
/**
* The IoC container instance.
*
* @var IlluminateContractsContainerContainer
*/
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 IlluminateContractsEvents;
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 AppEvents;
use CarbonCarbon;
use IlluminateBroadcastingChannel;
use IlluminateQueueSerializesModels;
use IlluminateBroadcastingPrivateChannel;
use IlluminateBroadcastingPresenceChannel;
use IlluminateFoundationEventsDispatchable;
use IlluminateBroadcastingInteractsWithSockets;
use IlluminateContractsBroadcastingShouldBroadcast;
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 IlluminateBroadcastingChannel|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 IlluminateContractsBroadcastingShouldBroadcast $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 = [
`AppEventsRssPublicEvent` => [
`AppListenersRssPublicListener1`,
],
`AppEvents*Event` => [
`AppListenersRssPublicListener2`,
`AppListenersRssPublicListener3`,
],
`IlluminateContractsBroadcastingShouldBroadcast` => [
`AppListenersRssPublicListener4`,
`AppListenersRssPublicListener5`,
],
];
複製程式碼
然後在每個 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
做了比較詳細的梳理,大致瞭解了它的整個流程,下一步就是看看怎麼和佇列結合在一起,和利用「觀察者模式」的那部分程式碼邏輯了。