HttpKernel:事件驅動
Symfony2
框架層和應用層的工做都是在 HttpKernel::handle()
方法中完成,HttpKernel::handle()
的內部的實現實際上是經過調度事件(HttpKernel
內的事件監聽器)來完成的,至關於把全部組件都整合成完整的應用。
使用 HttpKernel
很簡單,只須要建立一個
EventDispatcher(事件分發器) 和
controller resolver
(Controller解析器),能夠實現更多的事件監聽器豐富應用的功能:
1)
kernel.request
Event
實現kernel.request
事件目的是爲了添加更多信息到Request對象,或者獲得返回的Response對象(例如:從緩存中獲取又或者security層拒絕訪問)
kernel.request
事件是HttpKernel::handle()
調度的第一個事件,那麼監聽該事件的多個監聽器就會被執行。
監聽kernel.request
事件的監聽器多種多樣,它們的行爲各不相同,例如security監聽器判斷用戶沒有足夠的權限的時候一個RedirectResponse對象,若是當前直接返回Response對象,那麼就會直接執行 kernel.response 事件:
其它的監聽器實現一些初始化或者添加更多的信息到Request對象。
例如路由監聽器,路由監聽器從Request對象中獲取路由相關的信息,通過進一步加工,肯定處理請求的Controller,而且把相應的信息保存到Request對象裏的‘attribute’裏,這些信息都會被Controller解析器使用的,因此能夠實現監聽器之間的解耦。
總得來講,實現kernel.request
事件的目的要麼就是直接建立和返回Response對象,要麼就是添加更多的信息到Request對象。
RouterListener是Symfony框架中實現kernel.request
事件的最重要監聽器,RouterListener在路由層中執行,返回一個包含符合當前請求的路由信息的數組,例如路由匹配模式裏面的_controller和請求的參數({name})。這些信息都會存放在Request裏的attributes數組裏面,目前只是會添加路由信息到Request對象中尚未作其它的動做,可是解析Controller的時候會被用到。
2) Resolve the Controller
假設實現kernel.request
事件的時候沒有建立和返回Response對象,那麼下一步就是肯定、解析controller和controller須要的參數。controller部分是應用層的最後一個堡壘,負責建立和返回包含一個特定頁面的Response對象。如何肯定被請求的controller徹底取決於應用程序,這個工做有controller解析器來完成——一個是實現ControllerResolverInterface的類,同時也是HttpKernel構造函數的一個參數。
實現ControllerResolverInterface的兩個方法getController和getArguments:
HttpKernel::handle()
首先調用controller resolver 的 getController()方法,並向該方法傳入Request對象,controller resolver根據Request包含的信息肯定並返回controller。
第二個方法,getArguments()會在kernel.controller事件被調度的時候執行。
解析Controller
Symfony框架使用內置的ControllerResolver(實質上是使用了一個有額外的功能的子類),該解析器利用了RouterListener保存到Request對象的attributes屬性裏信息來肯定controller。
getController
ControllerResolver
在Request對象的attributes數組中
查找 _controller 鍵(這些信息其實是由RouterListener存放進Request對象中的):
a) 若是
_controller 鍵對應
AcmeDemoBundle:Default:index 這個格式的值,那麼該值就包含了類名和方法名,能夠被Symfony框架解析成爲,例如:
Acme\DemoBundle\Controller\DefaultController::indexAction,這個轉換是由Symfony框架的特定的
ControllerResolver的子類完成的。
b) 你的controller類會被實例化,並且該controller類必須包含一個無參的構造函數。
c) 若是你的controller還實現了ContainerAwareInterface,那麼setContainer方法就會被調用,container就會被注入到controller中,
這個實現也是由Symfony框架的特定的
ControllerResolver的子類完成的。
上面也有一些其餘變化過程,例如你把你的controller配置成爲service。
3) The kernel.controller
Event
kernel.controller事件是在controller被執行前初始化一些信息或者改變controller對象。javascript
被調用的controller肯定以後,HttpKernel::handle()就會調度kernel.controller事件。在系統的某部分被肯定後(例如:controller、路由信息等)可是這些部分被執行前,監聽kernel.controller事件的監聽器就會運行了。html
Symfony框架有幾個
kernel.controller事件的監聽器,多數都是處理分析數據的。
SensioFrameworkExtraBundle中一個監聽器ParamConverter容許咱們把一個對象做爲參數傳入到controller中,而不是字符串或者數值參數。
4)得到controller的參數
getAttributes()方法是返回一個參數數組,這個參數數組會被傳遞給controller,咱們也能夠自定義該方法,也可使用Symfony框架內置的。
到了這一步,kernel已經得到了controller和controller運行時須要的參數了。
Symfony框架得到controller的參數
ControllerResolver使用放射機制得到被調用的controller的方法的參數列表。遍歷該列表,使用下面的步驟來肯定參數列表中一一對應的值:
a) 使用參數做爲鍵查找Request對象中的attributes數組,若是找到,那麼相應的值會傳入到controller的方法中,例如:controller方法的第一個參數是$name,那麼在Request的attributes數組中包含$
attributes['name']的值,那麼
$
attributes['name']就會被使用。
b) 若是該該參數在Symfony配置routing的時候被指定,那麼就會跳過對該參數的查找。
5)調用controller
java
這一步,controller就會被執行。
controller會建立包含特定頁面或者json的Response對象,這也是應用層的最後一個步驟。
若是controller返回的是Response對象,那麼下一步kernel.response事件就會觸發,否者kernel.view事件就會被觸發。
controller必需要有返回值,若是返回null,程序會報錯
6) kernel.view事件json
controller返回值不是Response對象的時候被觸發。數組
若是controller返回的不是Response對象,kernel.view事件會被觸發,kernel.view事件的目的是把controller返回的值建立一個Response對象。
監聽實現kernel.view事件也是頗有好處的,若是controller返回了html頁面或者json字符串,咱們能夠經過監聽kernel.view事件,把html頁面或者json字符串封裝到Response對象,那麼Symfony框架依舊能夠正常運行。
可是若是沒有監聽器沒有設置Response對象到事件中,那麼程序就會報錯。either the controller
or one of the view listeners must always return a
Response
.
Symfony框架中實現kernel.view事件
Symfony框架中沒有缺省的監聽器實現kernel.view事件,但是,有一個核心Bundle——SensioFrameworkExtraBundle裏有個監聽改事件的監聽器。若是你的controller返回一個數組,而且在controller類的頂部有@Template的註解,那麼該監聽器就會渲染一個模板,把controller返回的數組傳入到模板中,最後利用模板返回的內容建立一個Response對象,並返回該Response對象。
除此以外,FOSRestBundle也實現了監聽該事件的監聽器,a listener on this event which aims to give you a robust view layer capable of using a single controller to return many different content-type responses (e.g. HTML, JSON, XML, etc).
7) kernel.response 事件
在發送Response對象到客戶端前修改它。
kernel的目的是把Request對象轉換成爲Response對象。Response對象多是在kernel.request事件中建立,多是由controller返回,又或者是由監聽kernel.view事件的監聽器返回。
不論是在哪個環節建立Response對象,最後kernel.response事件都會被觸發。監聽kernel.response事件的監聽器都會以某種方式修改Response對象,例如:修改Response的header部分,修改cookie,或者甚至會修改Response對象返回的內容(注入javascript到</body>標籤前等等)
kernel.response事件完成後,HttpKernel::handle()
返回最終的Response對象,調用Response::send()箱客戶端發送headers頭部和Response實體。
Symfony框架實現kernel.response事件
Symfony框架內置幾個監聽器監聽kernel.response事件,更多的能夠經過開發者社區得到。例如:在dev開發環境下WebDebugToolbarListener向頁面的底部注入javascript代碼,debug工具條就會顯示出來。還有另外一個監聽器,ContextListener序列化當前用戶的信息保存到session中,下一次請求的時候直接在session中重載用戶信息。
8) kernel.terminate事件緩存
監聽該事件的監聽器一般都是處理一些耗時的後臺程序。
HttpKernel進程的最後一個事件是kernel.terminate事件,並且該事件的觸發是在HttpKernel::handle()
方法以後,而且響應的內容已經發送給用戶。
正如上面的截圖所示,在向客戶端發送響應內容後才調用terminate方法,terminate方法內觸發kernel.terminate事件,監聽該事件的監聽器就會運行,這些監聽器都是在發送響應信息到客戶端後執行的。(例如發送郵件)
Symfony裏監聽kernel.terminate事件的監聽器
Symfony框架一個完整的工做流程
使用HttpKernel組件的時候,咱們不須要實現任何監聽器添加到內核事件中,也不須要實現controller resolver。HTTp組件自帶的監聽器和controller resolver就可以正常工做了:
子請求
除了把「main request」傳入到HttpKernel::handle以外,還能夠把所謂的「sub request」傳入HttpKernel::handle中。子請求看起來和其它的請求差很少,不一樣的是,通常的請求是渲染完整的一個頁面,而子請求渲染的是一個頁面的一部分。一般咱們都是在controller裏面建立一個子請求(或者在模板裏面建立)。
HttpKernel::handle方法運行子請求的時候,須要修改第二個參數的值:
子請求也是建立一個完整的請求——響應週期。惟一不一樣的是,有些監聽器可能只會在「main request」中運行(security)。KernelEvent的子類傳遞給監聽器,監聽器經過KernelEvent::getRequestType()判斷當前請求是「main request」仍是「sub request」。
例如一個監聽器只會在「main request」的請求下才會執行: