扒一扒 EventServiceProvider 源代碼

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

今天咱們就來扒一扒 Event 的源碼。數組

開始以前,須要說下兩個 EventServiceProvider 的區別:閉包

  • App\Providers\EventServiceProvider
  • Illuminate\Events\EventServiceProvider

第一個 App\Providers\EventServiceProvider 主要是定義 eventlistener 的關聯;第二個 Illuminate\Events\EventServiceProviderLaravel 的三大基礎 ServiceProvider 之一,主要負責「分派」工做。app

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

App\Providers\EventServiceProvider

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

<?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\EventServiceProvideroop

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

把定義 eventlistener 的關聯交給用戶本身去作,而後父類 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(),留着以後再說。

好了,整個 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 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,在 EventServiceProviderlisten 數組,填入這三種方式的關聯狀況:

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

相關文章
相關標籤/搜索