Phalcon Framework的MVC結構及啟動流程分析
目前的專案中選擇了Phalcon Framework作為未來一段時間的核心框架。技術選型的原因會單開一篇Blog另說,本次優先對Phalcon的MVC架構與啟動流程進行分析說明,如有遺漏還望指出。
Phalcon本身支援建立多種形式的Web應用專案以應對不同場景,包括迷你應用、單模組標準應用、以及較複雜的多模組應用。
本次以最複雜的多模組應用為例,Phalcon版本為1.3.2,用一個Phalcon所建立的標準專案來分析。
建立專案
Phalcon環境配置安裝後,可以通過命令列生成一個標準的Phalcon多模組應用:
phalcon project eva --type modules
入口檔案為public/index.php
,簡化後一共5行,包含了整個Phalcon的啟動流程,以下將按順序說明。
require __DIR__ . '/../config/services.php';
$application = new Phalcon\Mvc\Application();
$application->setDI($di);
require __DIR__ . '/../config/modules.php';
echo $application->handle()->getContent();
DI註冊階段
Phalcon的所有元件服務都是通過DI(依賴注入)進行組織的,這也是目前大部分主流框架所使用的方法。通過DI,可以靈活的控制框架中的服務:哪些需要啟用,哪些不啟用,元件的內部細節等等。因此Phalcon是一個鬆耦合可替換的框架,完全可以通過DI替換MVC中任何一個元件。
require __DIR__ . '/../config/services.php';
這個檔案中預設註冊了Phalcon\Mvc\Router
(路由)、Phalcon\Mvc\Url
(Url)、Phalcon\Session\Adapter\Files
(Session)三個最基本的元件。同時當MVC啟動後,DI中預設註冊的服務還有很多,可以通過DI得到所有當前已經註冊的服務:
$services = $application->getDI()->getServices();
foreach($services as $key => $service) {
var_dump($key);
var_dump(get_class($application->getDI()->get($key)));
}
列印看到Phalcon還註冊了以下服務:
dispatcher
:Phalcon\Mvc\Dispatcher
分發服務,將路由命中的結果分發到對應的ControllermodelsManager
:Phalcon\Mvc\Model\Manager
Model管理modelsMetadata
:Phalcon\Mvc\Model\MetaData\Memory
ORM表結構response
:Phalcon\Http\Response
響應cookies
:Phalcon\Http\Response\Cookies
Cookiesrequest
:Phalcon\Http\Request
請求filter
:Phalcon\Filter
可對使用者提交資料進行過濾escaper
:Phalcon\Escaper
轉義工具security
:Phalcon\Security
密碼Hash、防止CSRF等crypt
:Phalcon\Crypt
加密演算法annotations
:Phalcon\Annotations\Adapter\Memory
註解分析flash
:Phalcon\Flash\Direct
提示資訊輸出flashSession
:Phalcon\Flash\Session
提示資訊通過Session延遲輸出tag
:Phalcon\Tag
View的常用Helper
而每一個服務都可以通過DI進行替換。接下來例項化一個標準的MVC應用,然後將我們定義好的DI注入進去。
$application = new Phalcon\Mvc\Application();
$application->setDI($di);
模組註冊階段
與DI一樣,Phalcon建議通過引入一個獨立檔案的方式註冊所有需要的模組:
require __DIR__ . '/../config/modules.php';
這個檔案的內容如下:
$application->registerModules(array(
'frontend' => array(
'className' => 'Eva\Frontend\Module',
'path' => __DIR__ . '/../apps/frontend/Module.php'
)
));
可以看到Phalcon所謂的模組註冊,其實只是告訴框架MVC模組的引導檔案Module.php
所在位置及類名是什麼。
MVC階段
$application->handle()
是整個MVC的核心,這個函式中處理了路由、模組、分發等MVC的全部流程,處理過程中在關鍵位置會通過事件驅動觸發一系列application:
事件,方便外部注入邏輯,最終返回一個Phalcon\Http\Response
。整個handle
方法的過程並不複雜,下面按順序介紹:
基礎檢查
首先檢查DI,如果沒有任何DI注入,會丟擲錯誤:
A dependency injection object is required to access internal services
然後從DI啟動EventsManager,並且通過EventsManager觸發事件application:boot
。
路由階段
接下來進入路由階段,從DI中獲得路由服務router
,將uri傳入路由並呼叫路由的handle()
方法。
路由的handle方法負責將一個uri根據路由配置,轉換為相應的Module、Controller、Action等,這一階段接下來會檢查路由是否命中了某個模組,並通過Router->getModuleName()
獲得模組名。
如果模組存在,則進入模組啟動階段,否則直接進入分發階段。
注意到了麼,在Phalcon中,模組啟動是後於路由的,這意味著Phalcon的模組功能比較弱,我們無法在某個未啟動的模組中註冊全域性服務,甚至無法簡單的在當前模組中呼叫另一個未啟動模組。這可能是Phalcon模組功能設計中最大的問題,解決方法暫時不在本文的討論範圍內,以後會另開文章介紹。
模組啟動
模組啟動時首先會觸發application:beforeStartModule
事件。事件觸發後檢查模組的正確性,根據modules.php
中定義的className
、path
等,將模組引導檔案載入進來,並呼叫模組引導檔案中必須存在的方法。
Phalcon\Mvc\ModuleDefinitionInterface->registerAutoloaders ()
Phalcon\Mvc\ModuleDefinitionInterface->registerServices (Phalcon\DiInterface $dependencyInjector)
registerAutoloaders()
用於註冊模組內的名稱空間實現自動載入。registerServices ()
用於註冊模組內服務,在官方示例中registerServices ()
註冊並定義了view
服務以及模板的路徑,並且註冊了資料庫連線服務db
並設定資料庫的連線資訊。
模組啟動完成後觸發 application:afterStartModule
事件,進入分發階段。
分發階段(Dispatch)
分發過程由Phalcon\Mvc\Dispatcher
(分發器)來完成,所謂分發,在Phalcon裡本質上是分發器根據路由命中的結果,呼叫對應的Controller/Action,最終獲得Action返回的結果。
分發開始前首先會準備View,雖然View理論上位於MVC的最後一環,但是如果在分發過程中出現任何問題,通常都需要將問題顯示出來,因此View必須在這個環節就提前啟動。Phalcon沒有準備預設的View服務,需要從外部注入,在多模組demo中,View的注入官方推薦在模組啟動階段完成的。如果是單模組應用,則可以在最開始的DI階段注入。
如果始終沒有View注入,會丟擲錯誤:
Service 'view' was not found in the dependency injection container
導致分發過程直接中斷。
分發需要Dispatcher,Dispatcher同樣從DI中取得。然後將router中得到的引數(NamespaceName / ModuleName / ControllerName / ActionName / Params),全部複製到Dispatcher中。
分發開始前,會呼叫View的start()
方法。具體可以參考View相關文件,其實Phalcon\Mvc\View->start()
就是PHP的輸出緩衝函式ob_start
的一個簡單封裝,分發過程中所有輸出都會被暫存到緩衝區。
分發開始前還會觸發事件application:beforeHandleRequest
。
正式開始分發會呼叫Phalcon\Mvc\Dispatcher->dispatch()
。
Dispatcher內的分發處理
進入Dispatcher後會發現Dispatcher對整個分發過程進行了進一步細分,並且在分發的過程中會按順序觸發非常多的分發事件,可以通過這些分發事件進行更加細緻的流程控制。部分事件提供了可中斷的機制,只要返回false
就可以跳過Dispatcher的分發過程。
由於分發中可以使用Phalcon\Mvc\Dispatcher->forward()
來實現Action的複用,因此分發在內部會通過迴圈實現,通過檢測一個全域性的finished
標記來決定是否繼續分發。當以下幾種情況時,分發才會結束:
- Controller丟擲異常
forward
層數達到最大(256次)- 所有的Action呼叫完畢
渲染階段 View Render
分發結束後會觸發application:afterHandleRequest
,接下來通過Phalcon\Mvc\Dispatcher->getReturnedValue()
取得分發過程返回的結果並進行處理。
由於Action的邏輯在框架外,Action的返回值是無法預期的,因此這裡根據返回值是否實現Phalcon\Http\ResponseInterface
介面進行區分處理。
當Action返回一個非Phalcon\Http\ResponseInterface
型別
此時認為返回值無效,由View自己重新排程Render過程,會觸發application:viewRender
事件,同時從Dispatcher中取得ControllerName / ActionName / Params作為Phalcon\Mvc\View->render()
的入口引數。
Render完畢後呼叫Phalcon\Mvc\View->finish()
結束緩衝區的接收。
接下來從DI獲得resonse服務,將Phalcon\Mvc\View->getContent()
獲得的內容置入response。
當Action返回一個Phalcon\Http\ResponseInterface
型別
此時會將Action返回的Response作為最終的響應,不會重新構建新的Response。
返回響應
通過前面的流程,無論中間經歷了多少分支,最終都會彙總為唯一的響應。此時會觸發application:beforeSendResponse
,並呼叫
Phalcon\Http\Response->sendHeaders()
Phalcon\Http\Response->sendCookies()
將http的頭部資訊先行傳送。至此,Application->handle()
對於請求的處理過程全部結束,對外返回一個Phalcon\Http\Response
響應。
傳送響應
HTTP頭部傳送後一般把響應的內容也傳送出去:
echo $application->handle()->getContent();
這就是Phalcon Framework的完整MVC流程。
流程控制
分析MVC的啟動流程,無疑是希望對流程有更好的把握和控制,方法有兩種:
自定義啟動
按照上面的流程,我們其實完全可以自己實現$application->handle()->getContent()
這一流程,下面就是一個簡單的替代方案,程式碼中暫時沒有考慮事件的觸發。
//Roter
$router = $di['router'];
$router->handle();
//Module handle
$modules = $application->getModules();
$routeModule = $router->getModuleName();
if (isset($modules[$routeModule])) {
$moduleClass = new $modules[$routeModule]['className']();
$moduleClass->registerAutoloaders();
$moduleClass->registerServices($di);
}
//dispatch
$dispatcher = $di['dispatcher'];
$dispatcher->setModuleName($router->getModuleName());
$dispatcher->setControllerName($router->getControllerName());
$dispatcher->setActionName($router->getActionName());
$dispatcher->setParams($router->getParams());
//view
$view = $di['view'];
$view->start();
$controller = $dispatcher->dispatch();
//Not able to call render in controller or else will repeat output
$view->render(
$dispatcher->getControllerName(),
$dispatcher->getActionName(),
$dispatcher->getParams()
);
$view->finish();
$response = $di['response'];
$response->setContent($view->getContent());
$response->sendHeaders();
echo $response->getContent();
MVC事件
Phalcon作為C擴充套件型的框架,其優勢就在於高效能,雖然我們可以通過上一種方法自己實現整個啟動,但更好的方式仍然是避免替換框架本身的內容,而使用事件驅動。
下面梳理了整個MVC流程中所涉及的可被監聽的事件,可以根據不同需求選擇對應事件作為切入點:
- DI注入
application:boot
應用啟動
- 路由階段
- 模組啟動
application:beforeStartModule
模組啟動前application:afterStartModule
模組啟動後
- 分發階段
application:beforeHandleRequest
進入分發器前- 開始分發
dispatch:beforeDispatchLoop
分發迴圈開始前dispatch:beforeDispatch
單次分發開始前dispatch:beforeExecuteRoute
Action執行前dispatch:afterExecuteRoute
Action執行後dispatch:beforeNotFoundAction
找不到Actiondispatch:beforeException
丟擲異常前dispatch:afterDispatch
單次分發結束dispatch:afterDispatchLoop
分發迴圈結束
application:afterHandleRequest
分發結束
- 渲染階段
application:viewRender
渲染開始前
- 傳送響應
application:beforeSendResponse
最終響應傳送前
相關文章
- Phalcon Framework的Mvc結構及啟動流程(部分原始碼分析)FrameworkMVC原始碼
- framework——ATMS啟動流程Framework
- Phalcon的MVC框架解析MVC框架
- framework——應用程式啟動流程Framework
- Netty啟動流程及原始碼分析Netty原始碼
- Activity啟動流程分析
- activity 啟動流程分析
- Unbound啟動流程分析
- Activiti 流程啟動及節點流轉原始碼分析原始碼
- FlutterApp啟動流程分析FlutterAPP
- nodejs啟動流程分析NodeJS
- Flutter啟動流程原始碼分析Flutter原始碼
- Linux:uboot啟動流程分析Linuxboot
- apiserver原始碼分析——啟動流程APIServer原始碼
- Activity啟動流程原始碼分析原始碼
- Android應用啟動流程分析Android
- Flutter系列三:Flutter啟動流程分析Flutter
- Tomcat原始碼分析--啟動流程Tomcat原始碼
- JobTracker啟動流程原始碼級分析原始碼
- [心得]結構化互動流程
- Spring MVC 啟動過程原始碼分析SpringMVC原始碼
- SpringBoot啟動流程總結Spring Boot
- DNS分層結構及DNS解析流程DNS
- etcd MVCC 儲存結構及流程MVC
- 以太坊原始碼分析(39)geth啟動流程分析原始碼
- Android Activity啟動流程原始碼分析Android原始碼
- SpringBoot啟動流程分析原理(一)Spring Boot
- Android原始碼分析:Activity啟動流程Android原始碼
- [譯]Android Application 啟動流程分析AndroidAPP
- Android 7.0 應用啟動流程分析Android
- Laravel 的啟動流程Laravel
- centos系列的啟動流程及基礎知識點CentOS
- Uboot連結指令碼與啟動流程boot指令碼
- Category-載入流程、底層結構分析Go
- Spring Boot 應用程式啟動流程分析Spring Boot
- Apache Flink原始碼分析---JobManager啟動流程Apache原始碼
- Activity啟動流程分析記錄(Api26)API
- spark core原始碼分析2 master啟動流程Spark原始碼AST