摘要:對于包含通配符的事件名,會被統一放入數組中,是用來創建事件對應的的如果是監聽器是類,去創建監聽類創建的時候,會判斷監聽對象是監聽類還是閉包函數。對于閉包監聽來說,會再包裝一層返回一個閉包函數作為事件的監聽者。
事件系統
Laravel 的事件提供了一個簡單的觀察者實現,能夠訂閱和監聽應用中發生的各種事件。事件機制是一種很好的應用解耦方式,因為一個事件可以擁有多個互不依賴的監聽器。laravel?中事件系統由兩部分構成,一個是事件的名稱,事件的名稱可以是個字符串,例如?event.email,也可以是一個事件類,例如?AppEventsOrderShipped;另一個是事件的?監聽器listener,可以是一個閉包,還可以是監聽類,例如?AppListenersSendShipmentNotification。
我們還是通過官方文檔里給出的這個例子來向下分析事件系統的源碼實現,不過在應用注冊事件和監聽器之前,Laravel在應用啟動時會先注冊處理事件用的events服務。
Laravel注冊事件服務Laravel應用在創建時注冊的基礎服務里就有Event服務
namespace IlluminateFoundation; 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); }); }); }
IlluminateEventsDispatcher?就是?events服務真正的實現類,而Event門面時events服務的靜態代理,事件系統相關的方法都是由IlluminateEventsDispatcher來提供的。
應用中注冊事件和監聽我們還是通過官方文檔里給出的這個例子來向下分析事件系統的源碼實現,注冊事件和監聽器有兩種方法,AppProvidersEventServiceProvider?有個?listen?數組包含所有的事件(鍵)以及事件對應的監聽器(值)來注冊所有的事件監聽器,可以靈活地根據需求來添加事件。
/** * 應用程序的事件監聽器映射。 * * @var array */ protected $listen = [ "AppEventsOrderShipped" => [ "AppListenersSendShipmentNotification", ], ];
也可以在 AppProvidersEventServiceProvider 類的 boot 方法中注冊基于事件的閉包。
/** * 注冊應用程序中的任何其他事件。 * * @return void */ public function boot() { parent::boot(); Event::listen("event.name", function ($foo, $bar) { // }); }
可以看到AppProvidersEventProvider類的主要工作就是注冊應用中的事件,這個注冊類的主要作用是事件系統的啟動,這個類繼承自?IlluminateFoundationSupportProvidersEventServiceProvide。
我們在將服務提供器的時候說過,Laravel應用在注冊完所有的服務后會通過IlluminateFoundationBootstrapBootProviders調用所有Provider的boot方法來啟動這些服務,所以Laravel應用中事件和監聽器的注冊就發生在?IlluminateFoundationSupportProvidersEventServiceProvide類的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 IlluminateEvents; 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的事件系統原理還是跟之前講的觀察者模式一樣,不過框架的作者功力深厚,巧妙的結合應用了閉包來實現了事件系統,還有針對需要隊列處理的事件,應用事件在一些比較復雜的業務場景中能利用關注點分散原則有效地解耦應用中的代碼邏輯,當然也不是什么情況下都能適合應用事件來編寫代碼,我之前寫過一篇文章事件驅動編程來說明事件的應用場景,感興趣的可以去看看。
本文已經收錄在系列文章Laravel源碼學習里,歡迎訪問閱讀。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28805.html
摘要:模式定義觀察者模式定義對象間的一種一對多依賴關系,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知并被自動更新。 觀察者模式 Laravel的Event事件系統提供了一個簡單的觀察者模式實現,能夠訂閱和監聽應用中發生的各種事件,在PHP的標準庫(SPL)里甚至提供了三個接口SplSubject, SplObserver, SplObjectStorage來讓開發者更容易地實現觀...
摘要:過去一年時間寫了多篇文章來探討了我認為的框架最核心部分的設計思路代碼實現。為了大家閱讀方便,我把這些源碼學習的文章匯總到這里。數據庫算法和數據結構這些都是編程的內功,只有內功深厚了才能解決遇到的復雜問題。 過去一年時間寫了20多篇文章來探討了我認為的Larave框架最核心部分的設計思路、代碼實現。通過更新文章自己在軟件設計、文字表達方面都有所提高,在剛開始決定寫Laravel源碼分析地...
摘要:擴展用戶認證系統上一節我們介紹了系統實現的一些細節知道了是如何應用看守器和用戶提供器來進行用戶認證的,但是針對我們自己開發的項目或多或少地我們都會需要在自帶的看守器和用戶提供器基礎之上做一些定制化來適應項目,本節我會列舉一個在做項目時遇到的 擴展用戶認證系統 上一節我們介紹了Laravel Auth系統實現的一些細節知道了Laravel是如何應用看守器和用戶提供器來進行用戶認證的,但是...
摘要:通過裝載看守器和用戶提供器裝載看守器和用戶提供器用到的方法比較多,用文字描述不太清楚,我們通過注解這個過程中用到的方法來看具體的實現細節。 用戶認證系統的實現細節 上一節我們介紹來Laravel Auth系統的基礎知識,說了他的核心組件都有哪些構成,這一節我們會專注Laravel Auth系統的實現細節,主要關注Auth也就是AuthManager是如何裝載認證用的看守器(Guard)...
摘要:系統的核心是由的認證組件的看守器和提供器組成。使用的認證系統,幾乎所有東西都已經為你配置好了。其配置文件位于,其中包含了用于調整認證服務行為的注釋清晰的選項配置。 用戶認證系統(基礎介紹) 使用過Laravel的開發者都知道,Laravel自帶了一個認證系統來提供基本的用戶注冊、登錄、認證、找回密碼,如果Auth系統里提供的基礎功能不滿足需求還可以很方便的在這些基礎功能上進行擴展。這篇...
閱讀 3081·2021-09-24 10:26
閱讀 3236·2021-09-23 11:54
閱讀 4645·2021-09-22 15:33
閱讀 2243·2021-09-09 09:33
閱讀 1642·2021-09-07 10:10
閱讀 950·2019-08-30 11:09
閱讀 2840·2019-08-29 17:13
閱讀 993·2019-08-29 12:35