首先我們先重點(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.yaml
在 services:
添加下面注冊(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)先查找Listener
的onKernelRequest()
方法. - 如果既沒(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)事件.