[022] Symfony4 事件機(jī)制 Port02

首先我們先重點(diǎn)關(guān)注 kernel.request 事件. 通常在進(jìn)行 Controller 執(zhí)行具體的業(yè)務(wù)代碼之前, 系統(tǒng)都需要做一些初始化, 身份檢查之類的工作.
這些公共的行為如果定義到每個(gè) Controller 里去是非常不方便維護(hù)的. 通常需要進(jìn)行一個(gè) Plugin 或者中間件來(lái)處理. 而這些操作通常通過(guò)一個(gè)鉤子在系統(tǒng)最初階段進(jìn)行執(zhí)行.
在 Symfony 這個(gè)框架里, Listener 可以很好的處理這部分操作.

先看一下系統(tǒng)默認(rèn)的 kernel.request 監(jiān)聽(tīng)事件處理機(jī)制.

$ bin/console debug:event-dispatcher kernel.request

可以看到系統(tǒng)已經(jīng)注冊(cè)了8個(gè)監(jiān)聽(tīng)器:

Registered Listeners for "kernel.request" Event
===============================================

 ------- ------------------------------------------------------------------------------------------------- ---------- 
  Order   Callable                                                                                          Priority  
 ------- ------------------------------------------------------------------------------------------------- ---------- 
  #1      Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure()                     2048      
  #2      Symfony\Component\HttpKernel\EventListener\ValidateRequestListener::onKernelRequest()             256       
  #3      Symfony\Component\HttpKernel\EventListener\SessionListener::onKernelRequest()                     128               
  #4      Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest()                      32        
  #5      Symfony\Bundle\FrameworkBundle\EventListener\ResolveControllerNameSubscriber::onKernelRequest()   24        
  #6      Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest()                      16        
  #7      Symfony\Component\HttpKernel\EventListener\TranslatorListener::onKernelRequest()                  10        
  #8     Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::onKernelRequest()                  8         
 ------- ------------------------------------------------------------------------------------------------- ---------- 

需要注意一下每個(gè)監(jiān)聽(tīng)器有自己的 Priority 優(yōu)先級(jí). 優(yōu)先級(jí)越高越先執(zhí)行.

我們來(lái)定義一個(gè)監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng) kernel.request 事件. 假設(shè)用戶訪問(wèn)每一個(gè)網(wǎng)頁(yè)的時(shí)候我們需要先確定提供給用戶什么語(yǔ)言界面.
我們開(kāi)始編寫(xiě)PHP代碼: src/EventListener/LangListener.php

<?php
/**
 * LangListener.php
 *
 */

namespace App\EventListener;


use Symfony\Component\HttpKernel\Event\GetResponseEvent;


class LangListener
{

    public function __invoke(GetResponseEvent $event)
    {
        if (!$event->isMasterRequest()) {
            dump( __METHOD__ . ' Is not master request, ignore it.');
            return;
        }

        $request = $event->getRequest();

        //系統(tǒng)允許的 Locale 設(shè)置.
        $allowedLocales = explode(',', getenv('APP_LOCALE'));
        dump($allowedLocales);

        //檢查當(dāng)前的 Cookie 設(shè)定
        $cookieLocale = $request->cookies->get('locale');
        dump(sprintf('Cookie 選中的 Locale: %s', $cookieLocale));
        if (empty($cookieLocale) || !in_array($cookieLocale, $allowedLocales)) {
            $acceptLang = @$request->server->getHeaders()['ACCEPT_LANGUAGE'];
            dump(sprintf('當(dāng)前瀏覽器語(yǔ)言: %s', $acceptLang));
            $locale = substr($acceptLang, 0, 2);
            if (!in_array($locale, $allowedLocales)) {
                $locale = getenv('APP_LOCALE_DEFAULT');
            }
            $request->cookies->set('locale', $locale);
            dump(sprintf('當(dāng)前設(shè)定 Locale: %s', $locale));
        }
    }
}

監(jiān)聽(tīng)器編寫(xiě)完成后. 需要配置到系統(tǒng), 讓系統(tǒng)注冊(cè)號(hào)該監(jiān)聽(tīng)器. 修改 config/service.yamlservices: 添加下面注冊(cè)信息.

# ...
services:
    # ...
    App\EventListener\LangListener:
        tags: [{name: kernel.event_listener, event: kernel.request, priority: 99}]

如此我們就給系統(tǒng)提供了一個(gè)全局的監(jiān)聽(tīng)器, 在這每個(gè) Controller 里只需要從 cookie 里提取網(wǎng)站語(yǔ)言信息即可.

除了通過(guò) services.yaml 里注冊(cè)監(jiān)聽(tīng)器, 另外一種方法在 Listener 里自己實(shí)現(xiàn)注冊(cè)監(jiān)聽(tīng)器.
我們?cè)倬帉?xiě)一個(gè) AuthListener 來(lái)檢查用戶是否有權(quán)限訪問(wèn)某個(gè)網(wǎng)頁(yè): src/EventListener/AuthListener.php

<?php
/**
 * AuthListener.php
 *
 */

namespace App\EventListener;


use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class AuthListener implements EventSubscriberInterface
{
    /**
     * 注冊(cè)監(jiān)聽(tīng)器
     *
     * @return array
     */
    public static function getSubscribedEvents()
    {
        //可聲明注冊(cè)多個(gè)類型的事件監(jiān)聽(tīng)器, 同一個(gè)事件也可以綁定多個(gè)監(jiān)聽(tīng)方法.
        return [
            KernelEvents::REQUEST => [
                ['onKernelRequest', 30], //監(jiān)聽(tīng)方法1
                //...
            ],
            //KernelEvents::CONTROLLER => [['onKernelController', 99]],
            //...
        ];
    }

    public function onKernelRequest(GetResponseEvent $event)
    {

        if (!$event->isMasterRequest()) {
            dump( __METHOD__ . ' Is not master request, ignore it.');
            return;
        }

        dump('Check user identity here');
    }

    public function onKernelController(GetResponseEvent $event)
    {
         // todo
    }
}

如此一來(lái), 我們使用命令看看監(jiān)聽(tīng)器是否已經(jīng)添加到系統(tǒng)里:

$ bin/console debug:event-dispatcher kernel.request

可以看到已經(jīng)注冊(cè)成功:

Registered Listeners for "kernel.request" Event
===============================================

 ------- ------------------------------------------------------------------------------------------------- ---------- 
  Order   Callable                                                                                          Priority  
 ------- ------------------------------------------------------------------------------------------------- ---------- 
  #1      Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure()                     2048      
  #2      Symfony\Component\HttpKernel\EventListener\ValidateRequestListener::onKernelRequest()             256       
  #3      Symfony\Component\HttpKernel\EventListener\SessionListener::onKernelRequest()                     128       
  #4      App\EventListener\LangListener::__invoke()                                                        99        
  #5      Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest()                      32        
  #6      App\EventListener\AuthListener::onKernelRequest()                                                 30        
  #7      Symfony\Bundle\FrameworkBundle\EventListener\ResolveControllerNameSubscriber::onKernelRequest()   24        
  #8      Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest()                      16        
  #9      Symfony\Component\HttpKernel\EventListener\TranslatorListener::onKernelRequest()                  10        
  #10     Symfony\Bundle\SecurityBundle\Debug\TraceableFirewallListener::onKernelRequest()                  8         
 ------- ------------------------------------------------------------------------------------------------- ---------- 

需要注意的幾點(diǎn):

  • Listener 的執(zhí)行順序是按 Priority 從高到低來(lái)執(zhí)行的. 這個(gè)非常重要.
  • 如果 Listener 的監(jiān)聽(tīng)方法返回了一個(gè) Response, 那么該事件的派遣Dispatch就會(huì)結(jié)束, 接下來(lái)會(huì)進(jìn)入到kernel.response事件處理流程中. 所以 Priority 的值就需要注意設(shè)置好.
  • services.ymal 中注冊(cè) Listener 的時(shí)候, tags 可以 method 的屬性可以指定監(jiān)聽(tīng)該事件的方法.
    • 如果配置了method, 那么調(diào)用該 Listener 就執(zhí)行指定的 method, 如果沒(méi)有配置 method.
    • 如果沒(méi)配置method屬性, 那么會(huì)優(yōu)先查找 ListeneronKernelRequest() 方法.
    • 如果既沒(méi)有指定method方法, 也未定義 onKernelRequest() 方法, 那么會(huì)嘗試執(zhí)行 __invoke() 魔術(shù)方法.
    • 如果以上三種都未定義, 那么會(huì)拋出異常.
  • kernel.request 事件機(jī)制中, 所有的 Listener 中的響應(yīng)方法接收到的參數(shù)是: GetResponseEvent, 但是在其他的事件機(jī)制中, 傳入的參數(shù)是不同的. 具體的可以參考官方文檔: https://symfony.com/doc/current/components/http_kernel.html#component-http-kernel-event-table

到此可以基本初步了解窺視一下 Symfony 的事件機(jī)制. 也可以自己定義事件加入到系統(tǒng)中, 再自主定義 Listener 來(lái)響應(yīng)事件.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市停做,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌信不,老刑警劉巖屈溉,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贱迟,死亡現(xiàn)場(chǎng)離奇詭異姐扮,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)衣吠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)溶握,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蒸播,你說(shuō)我怎么就攤上這事睡榆∑妓粒” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵胀屿,是天一觀的道長(zhǎng)塘揣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)宿崭,這世上最難降的妖魔是什么亲铡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮葡兑,結(jié)果婚禮上奖蔓,老公的妹妹穿的比我還像新娘痛侍。我一直安慰自己德澈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布好啰。 她就那樣靜靜地躺著洲守,像睡著了一般疑务。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梗醇,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天知允,我揣著相機(jī)與錄音,去河邊找鬼叙谨。 笑死温鸽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的手负。 我是一名探鬼主播涤垫,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼虫溜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起股缸,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤衡楞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后敦姻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體瘾境,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年镰惦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迷守。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旺入,死狀恐怖兑凿,靈堂內(nèi)的尸體忽然破棺而出凯力,到底是詐尸還是另有隱情,我是刑警寧澤礼华,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布咐鹤,位于F島的核電站,受9級(jí)特大地震影響圣絮,放射性物質(zhì)發(fā)生泄漏祈惶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一扮匠、第九天 我趴在偏房一處隱蔽的房頂上張望捧请。 院中可真熱鬧,春花似錦棒搜、人聲如沸疹蛉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氧吐。三九已至,卻和暖如春末盔,著一層夾襖步出監(jiān)牢的瞬間筑舅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工陨舱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翠拣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓游盲,卻偏偏與公主長(zhǎng)得像误墓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子益缎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容