Laravel事件系統的解讀

不言
發布: 2023-04-02 17:00:01
原創
1976 人瀏覽過

這篇文章主要介紹了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)];
}

//获取事件名对应的所有listener
public 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的事件系統原理還是跟之前講的觀察者模式一樣,不過框架的作者功力深厚,巧妙的結合應用了閉包來實現了事件系統,還有針對需要佇列處理的事件,應用事件在一些比較複雜的業務場景中能利用關注點分散原則有效地解耦應用中的程式碼邏輯,當然也不是什麼情況下都能適合應用事件來編寫程式碼,我之前寫過一篇文章事件驅動程式來說明事件的應用場景,有興趣的可以去看看。

以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP中文網!

相關推薦:

Laravel用戶認證系統(基礎介紹)

#Laravel5.5以上版本多環境. env配置讀取

以上是Laravel事件系統的解讀的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!