Laravel事件系統的解讀

來源:互聯網
上載者:User
這篇文章主要介紹了關於Laravel事件系統的解讀,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

事件系統

Laravel 的事件提供了一個簡單的觀察者實現,能夠訂閱和監聽應用中發生的各種事件。事件機制是一種很好的應用解耦方式,因為一個事件可以擁有多個互不依賴的監聽器。laravel 中事件系統由兩部分構成,一個是事件的名稱,事件的名稱可以是個字串,例如 event.email,也可以是一個事件類別,例如 App\Events\OrderShipped;另一個是事件的 監聽器listener,可以是一個閉包,還可以是監聽類,例如 App\Listeners\SendShipmentNotification

我們還是通過官方文檔裡給出的這個例子來向下分析事件系統的源碼實現,不過在應用註冊事件和監聽器之前,Laravel在應用啟動時會先註冊處理事件用的events服務。

Laravel註冊事件服務

Laravel應用在建立時註冊的基礎服務裡就有Event服務

namespace Illuminate\Foundation;class Application extends Container implements ...{    public function __construct($basePath = null)    {        ...        $this->registerBaseServiceProviders();        ...    }        protected function registerBaseServiceProviders()    {        $this->register(new EventServiceProvider($this));        $this->register(new LogServiceProvider($this));        $this->register(new RoutingServiceProvider($this));    }}

其中的 EventServiceProvider/Illuminate/Events/EventServiceProvider

public function register(){    $this->app->singleton('events', function ($app) {        return (new Dispatcher($app))->setQueueResolver(function () use ($app) {            return $app->make(QueueFactoryContract::class);        });    });}

Illuminate\Events\Dispatcher 就是 events服務真正的實作類別,而Event門面時events服務的靜態代理,事件系統相關的方法都是由Illuminate\Events\Dispatcher來提供的。

應用中註冊事件和監聽

我們還是通過官方文檔裡給出的這個例子來向下分析事件系統的源碼實現,註冊事件和監聽器有兩種方法,App\Providers\EventServiceProvider 有個 listen 數組包含所有的事件(鍵)以及事件對應的監聽器(值)來註冊所有的事件監聽器,可以靈活地根據需求來添加事件。

/** * 應用程式的事件監聽器映射。 * * @var array */protected $listen = [    'App\Events\OrderShipped' => [        'App\Listeners\SendShipmentNotification',    ],];

也可以在 App\Providers\EventServiceProvider 類的 boot 方法中註冊基於事件的閉包。

/** * 註冊應用程式中的任何其他事件。 * * @return void */public function boot(){    parent::boot();    Event::listen('event.name', function ($foo, $bar) {        //    });}

可以看到\App\Providers\EventProvider類的主要工作就是註冊應用中的事件,這個註冊類的主要作用是事件系統的啟動,這個類繼承自 \Illuminate\Foundation\Support\Providers\EventServiceProvide

我們在將服務提供器的時候說過,Laravel應用在註冊完所有的服務後會通過\Illuminate\Foundation\Bootstrap\BootProviders調用所有Provider的boot方法來啟動這些服務,所以Laravel應用中事件和監聽器的註冊就發生在 \Illuminate\Foundation\Support\Providers\EventServiceProvide類的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);    }}

可以看到事件系統的啟動是通過events服務的監聽和訂閱者法來建立事件與對應的監聽器還有系統裡的事件訂閱者。

namespace Illuminate\Events;class Dispatcher implements DispatcherContract{    public function listen($events, $listener)    {        foreach ((array) $events as $event) {            if (Str::contains($event, '*')) {                $this->setupWildcardListen($event, $listener);            } else {                $this->listeners[$event][] = $this->makeListener($listener);            }        }    }        protected function setupWildcardListen($event, $listener)    {        $this->wildcards[$event][] = $this->makeListener($listener, true);    }}

對於包含萬用字元的事件名,會被統一放入 wildcards 數組中,makeListener是用來建立事件對應的listener的:

class Dispatcher implements DispatcherContract{    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);            } else {                return $listener(...array_values($payload));            }        };    }}

建立listener的時候,會判斷監聽對象是監聽類還是閉包函數。

對於閉包監聽來說,makeListener 會再封裝一層返回一個閉包函數作為事件的監聽者。

對於監聽類來說,會繼續通過 createClassListener 來建立監聽者

class Dispatcher implements DispatcherContract{    public function createClassListener($listener, $wildcard = false)    {        return function ($event, $payload) use ($listener, $wildcard) {            if ($wildcard) {                return call_user_func($this->createClassCallable($listener), $event, $payload);            } else {                return call_user_func_array(                    $this->createClassCallable($listener), $payload                );            }        };    }    protected function createClassCallable($listener)    {        list($class, $method) = $this->parseClassCallable($listener);        if ($this->handlerShouldBeQueued($class)) {            //如果當前監聽類是隊列的話,會將任務推送給隊列            return $this->createQueuedHandlerCallable($class, $method);        } else {            return [$this->container->make($class), $method];        }    }}

對於通過監聽類的字串來建立監聽者也是返回的一個閉包,如果當前監聽類是要執行隊列任務的話,返回的閉包是在執行後會將任務推送給隊列,如果是普通監聽類返回的閉包中會將監聽對象make出來,執行對象的handle方法。 所以監聽者返回閉包都是為了封裝好事件註冊時的上下文,等待事件觸發的時候調用閉包來執行任務。

建立完listener後就會把它放到listener數組中以對應的事件名稱為鍵的數組裡,在listener數組中一個事件名稱對應的數組裡可以有多個listener, 就像我們之前講觀察者模式時Subject類中的observers數組一樣,只不過Laravel比那個複雜一些,它的listener數組裡會記錄多個Subject和對應觀察者的對應關係。

觸發事件

可以用事件名或者事件類別來觸發事件,觸發事件時用的是Event::fire(new OrdershipmentNotification), 同樣它也來自events服務

public function fire($event, $payload = [], $halt = false){    return $this->dispatch($event, $payload, $halt);}public function dispatch($event, $payload = [], $halt = false){    //如果參數$event事件對象,那麼就將對象的類名作為事件名稱,對象本身作為攜帶資料的荷載通過`listener`方法    //的$payload參數的實參傳遞給listener    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);        //如果觸發事件時傳遞了halt參數,並且listener返回了值,那麼就不會再去呼叫事件剩下的listener        //否則就將傳回值加入到傳回值列表中,等所有listener執行完了一併返回        if ($halt && ! is_null($response)) {            return $response;        }        //如果一個listener返回了false, 那麼將不會再呼叫事件剩下的listener        if ($response === false) {            break;        }        $responses[] = $response;    }    return $halt ? null : $responses;}protected function parseEventAndPayload($event, $payload){    if (is_object($event)) {        list($payload, $event) = [[$event], get_class($event)];    }    return [$event, Arr::wrap($payload)];}//擷取事件名對應的所有listenerpublic function getListeners($eventName){    $listeners = isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [];    $listeners = array_merge(        $listeners, $this->getWildcardListeners($eventName)    );    return class_exists($eventName, false)                ? $this->addInterfaceListeners($eventName, $listeners)                : $listeners;}

事件觸發後,會從之前註冊事件產生的listeners中找到事件名稱對應的所有listener閉包,然後調用這些閉包來執行監聽器中的任務,需要注意的是:

  • 如果事件名參數事件對象,那麼會用事件對象的類名作為事件名,其本身會作為時間參數傳遞給listener。

  • 如果觸發事件時傳遞了halt參數,在listener返回非false後那麼事件就不會往下繼續傳播給剩餘的listener了,否則所有listener的傳回值會在所有listener執行往後作為一個數組統一返回。

  • 如果一個listener返回了布爾值false那麼事件會立即停止向剩餘的listener傳播。

Laravel的事件系統原理還是跟之前講的觀察者模式一樣,不過架構的作者功力深厚,巧妙的結合應用了閉包來實現了事件系統,還有針對需要隊列處理的事件,應用事件在一些比較複雜的業務情境中能利用關注點分散原則有效地解耦應用中的代碼邏輯,當然也不是什麼情況下都能適合應用事件來編寫代碼,我之前寫過一篇文章事件驅動編程來說明事件的應用情境,感興趣的可以去看看。

以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.