有了以前的《簡述 Laravel Model Events 的使用》mp.weixin.qq.com/s/XrhDq1S5R…,大體瞭解了 Event
的使用。php
今天咱們就來扒一扒 Event
的源碼。數組
開始以前,須要說下兩個 EventServiceProvider
的區別:閉包
App\Providers\EventServiceProvider
Illuminate\Events\EventServiceProvider
第一個 App\Providers\EventServiceProvider
主要是定義 event
和 listener
的關聯;第二個 Illuminate\Events\EventServiceProvider
是 Laravel
的三大基礎 ServiceProvider
之一,主要負責「分派」工做。app
好了,下面開始具體的分析工做。ide
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
:oop
<?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()
中:ui
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);
}
}
複製代碼
這裏主要看兩個函數:this
Event::listen($event, $listener);
Event::subscribe($subscriber);
就這麼簡單,咱們說完了第一個 EventServiceProvider
,咱們開始第二個。spa
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 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
作了比較詳細的梳理,大體瞭解了它的整個流程,下一步就是看看怎麼和隊列結合在一塊兒,和利用「觀察者模式」的那部分代碼邏輯了。