扒一扒 EventServiceProvider 原始碼

coding01發表於2018-10-08

有了之前的《簡述 Laravel Model Events 的使用》mp.weixin.qq.com/s/XrhDq1S5R…,大致瞭解了 Event 的使用。

今天我們就來扒一扒 Event 的原始碼。

開始之前,需要說下兩個 EventServiceProvider 的區別:

  • AppProvidersEventServiceProvider
  • IlluminateEventsEventServiceProvider

第一個 AppProvidersEventServiceProvider 主要是定義 eventlistener 的關聯;第二個 IlluminateEventsEventServiceProviderLaravel 的三大基礎 ServiceProvider 之一,主要負責「分派」工作。

好了,下面開始具體的分析工作。

AppProvidersEventServiceProvider

主要是定義 eventlistener 的關聯,如:

<?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;
    }
}
複製程式碼

把定義 eventlistener 的關聯交給使用者自己去做,然後父類 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(),留著之後再說。

好了,整個 eventlistener 就關聯在一起了。接下來就開始看執行方法了。

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,在 EventServiceProviderlisten 陣列,填入這三種方式的關聯情況:

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 做了比較詳細的梳理,大致瞭解了它的整個流程,下一步就是看看怎麼和佇列結合在一起,和利用「觀察者模式」的那部分程式碼邏輯了。

相關文章