摘要:有了之前的簡述的使用,大致了解了的使用。今天我們就來扒一扒的源碼。好了,整個和就關(guān)聯(lián)在一起了。注下篇文章我們再來扒一扒源碼當(dāng)把事件廣播出去后,我們就開始執(zhí)行該事件的各個監(jiān)聽了。
有了之前的《簡述 Laravel Model Events 的使用》https://mp.weixin.qq.com/s/XrhDq1S5RC9UdeULVVksoA,大致了解了 Event 的使用。
今天我們就來扒一扒 Event 的源碼。
開始之前,需要說下兩個 EventServiceProvider 的區(qū)別:
AppProvidersEventServiceProvider
IlluminateEventsEventServiceProvider
第一個 AppProvidersEventServiceProvider 主要是定義 event 和 listener 的關(guān)聯(lián);第二個 IlluminateEventsEventServiceProvider 是 Laravel 的三大基礎(chǔ) ServiceProvider 之一,主要負(fù)責(zé)「分派」工作。
好了,下面開始具體的分析工作。
AppProvidersEventServiceProvider主要是定義 event 和 listener 的關(guān)聯(lián),如:
[ "AppListenersRssPublicListener1", ], "AppEvents*Event" => [ "AppListenersRssPublicListener2", "AppListenersRssPublicListener3", ], "IlluminateContractsBroadcastingShouldBroadcast" => [ "AppListenersRssPublicListener4", "AppListenersRssPublicListener5", ], ]; /** * Register any events for your application. * * @return void */ public function boot() { parent::boot(); } }
主要繼承 IlluminateFoundationSupportProvidersEventServiceProvider:
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; } }
把定義 event 和 listener 的關(guān)聯(lián)交給用戶自己去做,然后父類 EventServiceProvider 只是做關(guān)聯(lián)工作,在 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); } }
這里主要看兩個函數(shù):
Event::listen($event, $listener);
Event::subscribe($subscriber);
就這么簡單,我們說完了第一個 EventServiceProvider ,我們開始第二個。
IlluminateEventsEventServiceProvider看過之前文章的知道,Event 有個全局函數(shù):
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); } }
而 IlluminateEventsEventServiceProvider,是 Laravel 三個基礎(chǔ) 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)); }
我們接著看 IlluminateEventsEventServiceProvider:
app->singleton("events", function ($app) { return (new Dispatcher($app))->setQueueResolver(function () use ($app) { return $app->make(QueueFactoryContract::class); }); }); } }
它注冊了單例形式,并創(chuàng)建和返回 Dispatcher 對象:
use IlluminateContractsEventsDispatcher as DispatcherContract; use IlluminateContractsBroadcastingFactory as BroadcastFactory; use IlluminateContractsContainerContainer as ContainerContract; class Dispatcher implements DispatcherContract { /** * The IoC container instance. * * @var IlluminateContractsContainerContainer */ 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; ... }
主要實現(xiàn) Dispatcher 接口:
下面我們來解說每一個函數(shù)。
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 數(shù)組中,另一個放在 listeners 數(shù)組中。接下來看函數(shù) 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 為字符串,則執(zhí)行函數(shù) 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 ); }; }先來看看函數(shù) 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]; }第一個函數(shù)還是很好理解:
public static function parseCallback($callback, $default = null) { return static::contains($callback, "@") ? explode("@", $callback, 2) : [$callback, $default]; }就看傳入的 listener 是不是 class@method 結(jié)構(gòu),如果是就用 @ 分割,否則就默認(rèn)的就是 class 類名,然后 method 就是默認(rèn)的 handle 函數(shù) —— 這也是我們創(chuàng)建 Listener 類提供的做法。
接著就看是否可以放入隊列中:
protected function handlerShouldBeQueued($class) { try { return (new ReflectionClass($class))->implementsInterface( ShouldQueue::class ); } catch (Exception $e) { return false; } }也就判斷該 listener 類是否實現(xiàn)了接口類 ShouldQueue。如果實現(xiàn)了,則可以將該類放入隊列中 (返回閉包函數(shù)):
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); }注:和隊列相關(guān)的放在之后再做分析,此處省略好了,回到開始的地方:
// createClassCallable($listener) return [$this->container->make($class), $method];到此,也就明白了,如果是 通配符 的,則對應(yīng)的執(zhí)行函數(shù) (默認(rèn)的為: handle) 傳入的參數(shù)有兩個:$event 事件對象和 $payload;否則對應(yīng)執(zhí)行函數(shù),傳入的參數(shù)就只有一個了:$payload。
同理,如果傳入的 listener 是個函數(shù)的話,返回的閉包就這樣的:
return function ($event, $payload) use ($listener, $wildcard) { if ($wildcard) { return $listener($event, $payload); } return $listener(...array_values($payload)); };整個流程就通了,listener 函數(shù)的作用就是:在 Dispatcher 中的 $listeners 和 $wildcards 的數(shù)組中,存儲 ["event" => Callback] 的結(jié)構(gòu)數(shù)組,以供執(zhí)行使用。
說完了第一個函數(shù) Event::listen(),第二個函數(shù)了:Event::subscribe(),留著之后再說。
好了,整個 event 和 listener 就關(guān)聯(lián)在一起了。接下來就開始看執(zhí)行方法了。
dispatch()Dispatch an event and call the listeners.正如上文的 helpers 定義的,所有 Event 都是通過該函數(shù)進(jìn)行「分發(fā)」事件和調(diào)用所關(guān)聯(lián)的 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; }先理解注釋的函數(shù) 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]); }就拿上文的例子來說吧:
"public_channel_".Carbon::now()->toDateTimeString()]; } }首先它實現(xiàn)接口 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; }由于本實例沒有實現(xiàn) broadcastWhen 方法,所以返回默認(rèn)值 true。
所以可以將本實例廣播出去:
/** * Broadcast the given event class. * * @param IlluminateContractsBroadcastingShouldBroadcast $event * @return void */ protected function broadcastEvent($event) { $this->container->make(BroadcastFactory::class)->queue($event); }這就交給 BroadcastManager 來處理了,此文不再繼續(xù)深挖。
注:下篇文章我們再來扒一扒 BroadcastManager 源碼當(dāng)把事件廣播出去后,我們就開始執(zhí)行該事件的各個監(jiān)聽了。通過之前的文章知道,一個 Event,不止一個 Listener 監(jiān)聽,所以需要通過一個 foreach 循環(huán)來遍歷執(zhí)行 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 是個對象的,還包括該類的所有接口關(guān)聯(lián)的 listeners 數(shù)組。
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 — 返回指定的類實現(xiàn)的所有接口。接下來就是執(zhí)行每個 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,實際上就是一個閉包函數(shù),最終的結(jié)果相當(dāng)于執(zhí)行 handle 函數(shù):
public function handle(RssPublicEvent $event) { info("listener 1"); } ... public function handle(RssPublicEvent $event, array $payload) { info("listener 2"); }寫個 demo我們寫個 demo,在 EventServiceProvider 的 listen 數(shù)組,填入這三種方式的關(guān)聯(lián)情況:
protected $listen = [ "AppEventsRssPublicEvent" => [ "AppListenersRssPublicListener1", ], "AppEvents*Event" => [ "AppListenersRssPublicListener2", "AppListenersRssPublicListener3", ], "IlluminateContractsBroadcastingShouldBroadcast" => [ "AppListenersRssPublicListener4", "AppListenersRssPublicListener5", ], ];然后在每個 RssPublicListener* 的 handle 方法輸出對應(yīng)的值,最后運行 php artisan public_echo,看結(jié)果:
[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其他函數(shù)說完了執(zhí)行函數(shù),基本上也就說完了整個 Event 事件流程了。剩下的只有一些附屬函數(shù),一看基本都理解:
/** * 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); } } }總結(jié)對 Event 做了比較詳細(xì)的梳理,大致了解了它的整個流程,下一步就是看看怎么和隊列結(jié)合在一起,和利用「觀察者模式」的那部分代碼邏輯了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/29491.html
摘要:扒一扒淘票票界面淘票票界面寫的挺美觀的,但是最近看了看淘票票的命名方式,覺得稍有不妥。命名與頁面內(nèi)容掛鉤,代碼復(fù)用性低。 BEM解析 BEM是一套CSS國際命名規(guī)范,是一個非常有用的功能強(qiáng)大且簡單的命名約定,它能使前端代碼更易讀,易于理解易于擴(kuò)展。BEM是塊(block)、元素(element)、修飾符(modifier)的縮寫。 B:Block是塊,一個獨立的組件,將所有東西都劃分...
摘要:年成立的為互聯(lián)網(wǎng)提供真正的隨機(jī)數(shù)。在年,隨機(jī)數(shù)市場發(fā)生了一個巨大的變化,在其芯片組上集成了芯片級的隨機(jī)數(shù)生成器。 作者:Alon Zakai 編譯:胡子大哈 翻譯原文:http://huziketang.com/blog/posts/detail?postId=58cfc3dda6d8a07e449fdd29 英文原文:A Brief History of Random Number...
摘要:可是并沒有統(tǒng)一的版本號管理功能,只是額外提供了內(nèi)包的依賴路徑。描述文件支持兩種格式,普通方式和方式,可以直接在其中描述依賴庫的遠(yuǎn)程地址版本號等,一個簡單的例子我這里使用普通格式然后在根目錄執(zhí)行,即可獲得相關(guān)版本的依賴包非常輕量級,非常簡潔。 與Linux、OpenStack等成熟的技術(shù)社區(qū)相比,Rancher社區(qū)還是處于初級發(fā)展階段,一個技術(shù)社區(qū)的成敗并不是單純的代碼貢獻(xiàn),而學(xué)習(xí)文檔的...
摘要:注本文首發(fā)在我的個人博客最近有個項目使用了,本來一直使用的是來進(jìn)行開發(fā),可是遇到了很多問題。此外,還有很多規(guī)范是幫助我們寫出更加優(yōu)雅而不容易出錯的代碼的。,終于基本搞定了,可以愉快地開發(fā)應(yīng)用了。 注:本文首發(fā)在 我的個人博客 最近有個項目使用了 Vue.js ,本來一直使用的是 PHPStorm 來進(jìn)行開發(fā),可是遇到了很多問題。 后來,果斷放棄收費的 PHPStorm ,改用 vsco...
閱讀 1246·2021-09-04 16:41
閱讀 2403·2021-09-02 10:18
閱讀 917·2019-08-29 16:40
閱讀 2614·2019-08-29 16:14
閱讀 898·2019-08-26 13:41
閱讀 1299·2019-08-26 12:24
閱讀 731·2019-08-26 10:24
閱讀 2869·2019-08-23 17:54