摘要:對(duì)于包含通配符的事件名,會(huì)被統(tǒng)一放入數(shù)組中,是用來創(chuàng)建事件對(duì)應(yīng)的的如果是監(jiān)聽器是類,去創(chuàng)建監(jiān)聽類創(chuàng)建的時(shí)候,會(huì)判斷監(jiān)聽對(duì)象是監(jiān)聽類還是閉包函數(shù)。對(duì)于閉包監(jiān)聽來說,會(huì)再包裝一層返回一個(gè)閉包函數(shù)作為事件的監(jiān)聽者。
事件系統(tǒng)
Laravel 的事件提供了一個(gè)簡(jiǎn)單的觀察者實(shí)現(xiàn),能夠訂閱和監(jiān)聽?wèi)?yīng)用中發(fā)生的各種事件。事件機(jī)制是一種很好的應(yīng)用解耦方式,因?yàn)橐粋€(gè)事件可以擁有多個(gè)互不依賴的監(jiān)聽器。laravel?中事件系統(tǒng)由兩部分構(gòu)成,一個(gè)是事件的名稱,事件的名稱可以是個(gè)字符串,例如?event.email,也可以是一個(gè)事件類,例如?AppEventsOrderShipped;另一個(gè)是事件的?監(jiān)聽器listener,可以是一個(gè)閉包,還可以是監(jiān)聽類,例如?AppListenersSendShipmentNotification。
我們還是通過官方文檔里給出的這個(gè)例子來向下分析事件系統(tǒng)的源碼實(shí)現(xiàn),不過在應(yīng)用注冊(cè)事件和監(jiān)聽器之前,Laravel在應(yīng)用啟動(dòng)時(shí)會(huì)先注冊(cè)處理事件用的events服務(wù)。
Laravel注冊(cè)事件服務(wù)Laravel應(yīng)用在創(chuàng)建時(shí)注冊(cè)的基礎(chǔ)服務(wù)里就有Event服務(wù)
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服務(wù)真正的實(shí)現(xiàn)類,而Event門面時(shí)events服務(wù)的靜態(tài)代理,事件系統(tǒng)相關(guān)的方法都是由IlluminateEventsDispatcher來提供的。
應(yīng)用中注冊(cè)事件和監(jiān)聽我們還是通過官方文檔里給出的這個(gè)例子來向下分析事件系統(tǒng)的源碼實(shí)現(xiàn),注冊(cè)事件和監(jiān)聽器有兩種方法,AppProvidersEventServiceProvider?有個(gè)?listen?數(shù)組包含所有的事件(鍵)以及事件對(duì)應(yīng)的監(jiān)聽器(值)來注冊(cè)所有的事件監(jiān)聽器,可以靈活地根據(jù)需求來添加事件。
/** * 應(yīng)用程序的事件監(jiān)聽器映射。 * * @var array */ protected $listen = [ "AppEventsOrderShipped" => [ "AppListenersSendShipmentNotification", ], ];
也可以在 AppProvidersEventServiceProvider 類的 boot 方法中注冊(cè)基于事件的閉包。
/** * 注冊(cè)應(yīng)用程序中的任何其他事件。 * * @return void */ public function boot() { parent::boot(); Event::listen("event.name", function ($foo, $bar) { // }); }
可以看到AppProvidersEventProvider類的主要工作就是注冊(cè)應(yīng)用中的事件,這個(gè)注冊(cè)類的主要作用是事件系統(tǒng)的啟動(dòng),這個(gè)類繼承自?IlluminateFoundationSupportProvidersEventServiceProvide。
我們?cè)趯⒎?wù)提供器的時(shí)候說過,Laravel應(yīng)用在注冊(cè)完所有的服務(wù)后會(huì)通過IlluminateFoundationBootstrapBootProviders調(diào)用所有Provider的boot方法來啟動(dòng)這些服務(wù),所以Laravel應(yīng)用中事件和監(jiān)聽器的注冊(cè)就發(fā)生在?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); } }
可以看到事件系統(tǒng)的啟動(dòng)是通過events服務(wù)的監(jiān)聽和訂閱方法來創(chuàng)建事件與對(duì)應(yīng)的監(jiān)聽器還有系統(tǒng)里的事件訂閱者。
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); } }
對(duì)于包含通配符的事件名,會(huì)被統(tǒng)一放入?wildcards?數(shù)組中,makeListener是用來創(chuàng)建事件對(duì)應(yīng)的listener的:
class Dispatcher implements DispatcherContract { public function makeListener($listener, $wildcard = false) { if (is_string($listener)) {//如果是監(jiān)聽器是類,去創(chuàng)建監(jiān)聽類 return $this->createClassListener($listener, $wildcard); } return function ($event, $payload) use ($listener, $wildcard) { if ($wildcard) { return $listener($event, $payload); } else { return $listener(...array_values($payload)); } }; } }
創(chuàng)建listener的時(shí)候,會(huì)判斷監(jiān)聽對(duì)象是監(jiān)聽類還是閉包函數(shù)。
對(duì)于閉包監(jiān)聽來說,makeListener 會(huì)再包裝一層返回一個(gè)閉包函數(shù)作為事件的監(jiān)聽者。
對(duì)于監(jiān)聽類來說,會(huì)繼續(xù)通過 createClassListener 來創(chuàng)建監(jiān)聽者
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)) { //如果當(dāng)前監(jiān)聽類是隊(duì)列的話,會(huì)將任務(wù)推送給隊(duì)列 return $this->createQueuedHandlerCallable($class, $method); } else { return [$this->container->make($class), $method]; } } }
對(duì)于通過監(jiān)聽類的字符串來創(chuàng)建監(jiān)聽者也是返回的一個(gè)閉包,如果當(dāng)前監(jiān)聽類是要執(zhí)行隊(duì)列任務(wù)的話,返回的閉包是在執(zhí)行后會(huì)將任務(wù)推送給隊(duì)列,如果是普通監(jiān)聽類返回的閉包中會(huì)將監(jiān)聽對(duì)象make出來,執(zhí)行對(duì)象的handle方法。 所以監(jiān)聽者返回閉包都是為了包裝好事件注冊(cè)時(shí)的上下文,等待事件觸發(fā)的時(shí)候調(diào)用閉包來執(zhí)行任務(wù)。
創(chuàng)建完listener后就會(huì)把它放到listener數(shù)組中以對(duì)應(yīng)的事件名稱為鍵的數(shù)組里,在listener數(shù)組中一個(gè)事件名稱對(duì)應(yīng)的數(shù)組里可以有多個(gè)listener, 就像我們之前講觀察者模式時(shí)Subject類中的observers數(shù)組一樣,只不過Laravel比那個(gè)復(fù)雜一些,它的listener數(shù)組里會(huì)記錄多個(gè)Subject和對(duì)應(yīng)觀察者的對(duì)應(yīng)關(guān)系。
觸發(fā)事件可以用事件名或者事件類來觸發(fā)事件,觸發(fā)事件時(shí)用的是Event::fire(new OrdershipmentNotification), 同樣它也來自events服務(wù)
public function fire($event, $payload = [], $halt = false) { return $this->dispatch($event, $payload, $halt); } public function dispatch($event, $payload = [], $halt = false) { //如果參數(shù)$event事件對(duì)象,那么就將對(duì)象的類名作為事件名稱,對(duì)象本身作為攜帶數(shù)據(jù)的荷載通過`listener`方法 //的$payload參數(shù)的實(shí)參傳遞給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); //如果觸發(fā)事件時(shí)傳遞了halt參數(shù),并且listener返回了值,那么就不會(huì)再去調(diào)用事件剩下的listener //否則就將返回值加入到返回值列表中,等所有l(wèi)istener執(zhí)行完了一并返回 if ($halt && ! is_null($response)) { return $response; } //如果一個(gè)listener返回了false, 那么將不會(huì)再調(diào)用事件剩下的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)]; } //獲取事件名對(duì)應(yīng)的所有l(wèi)istener 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; }
事件觸發(fā)后,會(huì)從之前注冊(cè)事件生成的listeners中找到事件名稱對(duì)應(yīng)的所有listener閉包,然后調(diào)用這些閉包來執(zhí)行監(jiān)聽器中的任務(wù),需要注意的是:
如果事件名參數(shù)事件對(duì)象,那么會(huì)用事件對(duì)象的類名作為事件名,其本身會(huì)作為時(shí)間參數(shù)傳遞給listener。
如果觸發(fā)事件時(shí)傳遞了halt參數(shù),在listener返回非false后那么事件就不會(huì)往下繼續(xù)傳播給剩余的listener了,否則所有l(wèi)istener的返回值會(huì)在所有l(wèi)istener執(zhí)行往后作為一個(gè)數(shù)組統(tǒng)一返回。
如果一個(gè)listener返回了布爾值false那么事件會(huì)立即停止向剩余的listener傳播。
Laravel的事件系統(tǒng)原理還是跟之前講的觀察者模式一樣,不過框架的作者功力深厚,巧妙的結(jié)合應(yīng)用了閉包來實(shí)現(xiàn)了事件系統(tǒng),還有針對(duì)需要隊(duì)列處理的事件,應(yīng)用事件在一些比較復(fù)雜的業(yè)務(wù)場(chǎng)景中能利用關(guān)注點(diǎn)分散原則有效地解耦應(yīng)用中的代碼邏輯,當(dāng)然也不是什么情況下都能適合應(yīng)用事件來編寫代碼,我之前寫過一篇文章事件驅(qū)動(dòng)編程來說明事件的應(yīng)用場(chǎng)景,感興趣的可以去看看。
本文已經(jīng)收錄在系列文章Laravel源碼學(xué)習(xí)里,歡迎訪問閱讀。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/28805.html
摘要:模式定義觀察者模式定義對(duì)象間的一種一對(duì)多依賴關(guān)系,使得每當(dāng)一個(gè)對(duì)象狀態(tài)發(fā)生改變時(shí),其相關(guān)依賴對(duì)象皆得到通知并被自動(dòng)更新。 觀察者模式 Laravel的Event事件系統(tǒng)提供了一個(gè)簡(jiǎn)單的觀察者模式實(shí)現(xiàn),能夠訂閱和監(jiān)聽?wèi)?yīng)用中發(fā)生的各種事件,在PHP的標(biāo)準(zhǔn)庫(SPL)里甚至提供了三個(gè)接口SplSubject, SplObserver, SplObjectStorage來讓開發(fā)者更容易地實(shí)現(xiàn)觀...
摘要:過去一年時(shí)間寫了多篇文章來探討了我認(rèn)為的框架最核心部分的設(shè)計(jì)思路代碼實(shí)現(xiàn)。為了大家閱讀方便,我把這些源碼學(xué)習(xí)的文章匯總到這里。數(shù)據(jù)庫算法和數(shù)據(jù)結(jié)構(gòu)這些都是編程的內(nèi)功,只有內(nèi)功深厚了才能解決遇到的復(fù)雜問題。 過去一年時(shí)間寫了20多篇文章來探討了我認(rèn)為的Larave框架最核心部分的設(shè)計(jì)思路、代碼實(shí)現(xiàn)。通過更新文章自己在軟件設(shè)計(jì)、文字表達(dá)方面都有所提高,在剛開始決定寫Laravel源碼分析地...
摘要:擴(kuò)展用戶認(rèn)證系統(tǒng)上一節(jié)我們介紹了系統(tǒng)實(shí)現(xiàn)的一些細(xì)節(jié)知道了是如何應(yīng)用看守器和用戶提供器來進(jìn)行用戶認(rèn)證的,但是針對(duì)我們自己開發(fā)的項(xiàng)目或多或少地我們都會(huì)需要在自帶的看守器和用戶提供器基礎(chǔ)之上做一些定制化來適應(yīng)項(xiàng)目,本節(jié)我會(huì)列舉一個(gè)在做項(xiàng)目時(shí)遇到的 擴(kuò)展用戶認(rèn)證系統(tǒng) 上一節(jié)我們介紹了Laravel Auth系統(tǒng)實(shí)現(xiàn)的一些細(xì)節(jié)知道了Laravel是如何應(yīng)用看守器和用戶提供器來進(jìn)行用戶認(rèn)證的,但是...
摘要:通過裝載看守器和用戶提供器裝載看守器和用戶提供器用到的方法比較多,用文字描述不太清楚,我們通過注解這個(gè)過程中用到的方法來看具體的實(shí)現(xiàn)細(xì)節(jié)。 用戶認(rèn)證系統(tǒng)的實(shí)現(xiàn)細(xì)節(jié) 上一節(jié)我們介紹來Laravel Auth系統(tǒng)的基礎(chǔ)知識(shí),說了他的核心組件都有哪些構(gòu)成,這一節(jié)我們會(huì)專注Laravel Auth系統(tǒng)的實(shí)現(xiàn)細(xì)節(jié),主要關(guān)注Auth也就是AuthManager是如何裝載認(rèn)證用的看守器(Guard)...
摘要:系統(tǒng)的核心是由的認(rèn)證組件的看守器和提供器組成。使用的認(rèn)證系統(tǒng),幾乎所有東西都已經(jīng)為你配置好了。其配置文件位于,其中包含了用于調(diào)整認(rèn)證服務(wù)行為的注釋清晰的選項(xiàng)配置。 用戶認(rèn)證系統(tǒng)(基礎(chǔ)介紹) 使用過Laravel的開發(fā)者都知道,Laravel自帶了一個(gè)認(rèn)證系統(tǒng)來提供基本的用戶注冊(cè)、登錄、認(rèn)證、找回密碼,如果Auth系統(tǒng)里提供的基礎(chǔ)功能不滿足需求還可以很方便的在這些基礎(chǔ)功能上進(jìn)行擴(kuò)展。這篇...
閱讀 3182·2021-09-24 10:26
閱讀 3450·2021-09-23 11:54
閱讀 4819·2021-09-22 15:33
閱讀 2325·2021-09-09 09:33
閱讀 1767·2021-09-07 10:10
閱讀 1024·2019-08-30 11:09
閱讀 2991·2019-08-29 17:13
閱讀 1086·2019-08-29 12:35