Phalcon Framework的MVC結(jié)構(gòu)及啟動流程分析

目前的項目中選擇了Phalcon Framework作為未來一段時間的核心框架。技術(shù)選型的原因會單開一篇Blog另說,本次優(yōu)先對Phalcon的MVC架構(gòu)與啟動流程進行分析說明,如有遺漏還望指出。

Phalcon本身支持創(chuàng)建多種形式的Web應(yīng)用項目以應(yīng)對不同場景,包括迷你應(yīng)用單模塊標(biāo)準(zhǔn)應(yīng)用、以及較復(fù)雜的多模塊應(yīng)用

本次以最復(fù)雜的多模塊應(yīng)用為例,Phalcon版本為1.3.2,用一個Phalcon所創(chuàng)建的標(biāo)準(zhǔn)項目來分析。

創(chuàng)建項目

Phalcon環(huán)境配置安裝后,可以通過命令行生成一個標(biāo)準(zhǔn)的Phalcon多模塊應(yīng)用:

 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的所有組件服務(wù)都是通過DI(依賴注入)進行組織的,這也是目前大部分主流框架所使用的方法。通過DI,可以靈活的控制框架中的服務(wù):哪些需要啟用,哪些不啟用,組件的內(nèi)部細(xì)節(jié)等等。因此Phalcon是一個松耦合可替換的框架,完全可以通過DI替換MVC中任何一個組件。

require __DIR__ . '/../config/services.php';

這個文件中默認(rèn)注冊了Phalcon\Mvc\Router(路由)、Phalcon\Mvc\Url(Url)、Phalcon\Session\Adapter\Files(Session)三個最基本的組件。同時當(dāng)MVC啟動后,DI中默認(rèn)注冊的服務(wù)還有很多,可以通過DI得到所有當(dāng)前已經(jīng)注冊的服務(wù):

$services = $application->getDI()->getServices();
foreach($services as $key => $service) {
        var_dump($key);
        var_dump(get_class($application->getDI()->get($key)));
}

打印看到Phalcon還注冊了以下服務(wù):

  • dispatcher : Phalcon\Mvc\Dispatcher 分發(fā)服務(wù),將路由命中的結(jié)果分發(fā)到對應(yīng)的Controller
  • modelsManager : Phalcon\Mvc\Model\Manager Model管理
  • modelsMetadata : Phalcon\Mvc\Model\MetaData\Memory ORM表結(jié)構(gòu)
  • response : Phalcon\Http\Response 響應(yīng)
  • cookies : Phalcon\Http\Response\Cookies Cookies
  • request : Phalcon\Http\Request 請求
  • filter : Phalcon\Filter 可對用戶提交數(shù)據(jù)進行過濾
  • escaper : Phalcon\Escaper 轉(zhuǎn)義工具
  • 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

而每一個服務(wù)都可以通過DI進行替換。接下來實例化一個標(biāo)準(zhǔn)的MVC應(yīng)用,然后將我們定義好的DI注入進去。

$application = new Phalcon\Mvc\Application();
$application->setDI($di);

模塊注冊階段

與DI一樣,Phalcon建議通過引入一個獨立文件的方式注冊所有需要的模塊:

require __DIR__ . '/../config/modules.php';

這個文件的內(nèi)容如下:

$application->registerModules(array(
    'frontend' => array(
        'className' => 'Eva\Frontend\Module',
        'path' => __DIR__ . '/../apps/frontend/Module.php'
    )
));

可以看到Phalcon所謂的模塊注冊,其實只是告訴框架MVC模塊的引導(dǎo)文件Module.php所在位置及類名是什么。

MVC階段

$application->handle()是整個MVC的核心,這個函數(shù)中處理了路由、模塊、分發(fā)等MVC的全部流程,處理過程中在關(guān)鍵位置會通過事件驅(qū)動觸發(fā)一系列application:事件,方便外部注入邏輯,最終返回一個Phalcon\Http\Response。整個handle方法的過程并不復(fù)雜,下面按順序介紹:

基礎(chǔ)檢查

首先檢查DI,如果沒有任何DI注入,會拋出錯誤:

A dependency injection object is required to access internal services

然后從DI啟動EventsManager,并且通過EventsManager觸發(fā)事件application:boot

路由階段

接下來進入路由階段,從DI中獲得路由服務(wù)router,將uri傳入路由并調(diào)用路由的handle()方法

路由的handle方法負(fù)責(zé)將一個uri根據(jù)路由配置,轉(zhuǎn)換為相應(yīng)的Module、Controller、Action等,這一階段接下來會檢查路由是否命中了某個模塊,并通過Router->getModuleName()獲得模塊名。

如果模塊存在,則進入模塊啟動階段,否則直接進入分發(fā)階段。

注意到了么,在Phalcon中,模塊啟動是后于路由的,這意味著Phalcon的模塊功能比較弱,我們無法在某個未啟動的模塊中注冊全局服務(wù),甚至無法簡單的在當(dāng)前模塊中調(diào)用另一個未啟動模塊。這可能是Phalcon模塊功能設(shè)計中最大的問題,解決方法暫時不在本文的討論范圍內(nèi),以后會另開文章介紹。

模塊啟動

模塊啟動時首先會觸發(fā)application:beforeStartModule事件。事件觸發(fā)后檢查模塊的正確性,根據(jù)modules.php中定義的classNamepath等,將模塊引導(dǎo)文件加載進來,并調(diào)用模塊引導(dǎo)文件中必須存在的方法。

  • Phalcon\Mvc\ModuleDefinitionInterface->registerAutoloaders ()
  • Phalcon\Mvc\ModuleDefinitionInterface->registerServices (Phalcon\DiInterface $dependencyInjector)

registerAutoloaders()用于注冊模塊內(nèi)的命名空間實現(xiàn)自動加載。registerServices ()用于注冊模塊內(nèi)服務(wù),在官方示例中registerServices ()注冊并定義了view服務(wù)以及模板的路徑,并且注冊了數(shù)據(jù)庫連接服務(wù)db并設(shè)置數(shù)據(jù)庫的連接信息。

模塊啟動完成后觸發(fā) application:afterStartModule事件,進入分發(fā)階段。

分發(fā)階段(Dispatch)

分發(fā)過程由Phalcon\Mvc\Dispatcher(分發(fā)器)來完成,所謂分發(fā),在Phalcon里本質(zhì)上是分發(fā)器根據(jù)路由命中的結(jié)果,調(diào)用對應(yīng)的Controller/Action,最終獲得Action返回的結(jié)果。

分發(fā)開始前首先會準(zhǔn)備View,雖然View理論上位于MVC的最后一環(huán),但是如果在分發(fā)過程中出現(xiàn)任何問題,通常都需要將問題顯示出來,因此View必須在這個環(huán)節(jié)就提前啟動。Phalcon沒有準(zhǔn)備默認(rèn)的View服務(wù),需要從外部注入,在多模塊demo中,View的注入官方推薦在模塊啟動階段完成的。如果是單模塊應(yīng)用,則可以在最開始的DI階段注入。

如果始終沒有View注入,會拋出錯誤:

Service 'view' was not found in the dependency injection container

導(dǎo)致分發(fā)過程直接中斷。

分發(fā)需要Dispatcher,Dispatcher同樣從DI中取得。然后將router中得到的參數(shù)(NamespaceName / ModuleName / ControllerName / ActionName / Params),全部復(fù)制到Dispatcher中。

分發(fā)開始前,會調(diào)用View的start()方法。具體可以參考View相關(guān)文檔,其實Phalcon\Mvc\View->start()就是PHP的輸出緩沖函數(shù)ob_start的一個簡單封裝,分發(fā)過程中所有輸出都會被暫存到緩沖區(qū)。

分發(fā)開始前還會觸發(fā)事件application:beforeHandleRequest

正式開始分發(fā)會調(diào)用Phalcon\Mvc\Dispatcher->dispatch()

Dispatcher內(nèi)的分發(fā)處理

進入Dispatcher后會發(fā)現(xiàn)Dispatcher對整個分發(fā)過程進行了進一步細(xì)分,并且在分發(fā)的過程中會按順序觸發(fā)非常多的分發(fā)事件,可以通過這些分發(fā)事件進行更加細(xì)致的流程控制。部分事件提供了可中斷的機制,只要返回false就可以跳過Dispatcher的分發(fā)過程。

由于分發(fā)中可以使用Phalcon\Mvc\Dispatcher->forward()來實現(xiàn)Action的復(fù)用,因此分發(fā)在內(nèi)部會通過循環(huán)實現(xiàn),通過檢測一個全局的finished標(biāo)記來決定是否繼續(xù)分發(fā)。當(dāng)以下幾種情況時,分發(fā)才會結(jié)束:

  • Controller拋出異常
  • forward層數(shù)達到最大(256次)
  • 所有的Action調(diào)用完畢

渲染階段 View Render

分發(fā)結(jié)束后會觸發(fā)application:afterHandleRequest,接下來通過Phalcon\Mvc\Dispatcher->getReturnedValue()取得分發(fā)過程返回的結(jié)果并進行處理。

由于Action的邏輯在框架外,Action的返回值是無法預(yù)期的,因此這里根據(jù)返回值是否實現(xiàn)Phalcon\Http\ResponseInterface接口進行區(qū)分處理。

當(dāng)Action返回一個非Phalcon\Http\ResponseInterface類型

此時認(rèn)為返回值無效,由View自己重新調(diào)度Render過程,會觸發(fā)application:viewRender事件,同時從Dispatcher中取得ControllerName / ActionName / Params作為Phalcon\Mvc\View->render()的入口參數(shù)。

Render完畢后調(diào)用Phalcon\Mvc\View->finish()結(jié)束緩沖區(qū)的接收。

接下來從DI獲得resonse服務(wù),將Phalcon\Mvc\View->getContent()獲得的內(nèi)容置入response。

當(dāng)Action返回一個Phalcon\Http\ResponseInterface類型

此時會將Action返回的Response作為最終的響應(yīng),不會重新構(gòu)建新的Response。

返回響應(yīng)

通過前面的流程,無論中間經(jīng)歷了多少分支,最終都會匯總為唯一的響應(yīng)。此時會觸發(fā)application:beforeSendResponse,并調(diào)用

  • Phalcon\Http\Response->sendHeaders()
  • Phalcon\Http\Response->sendCookies()

將http的頭部信息先行發(fā)送。至此,Application->handle()對于請求的處理過程全部結(jié)束,對外返回一個Phalcon\Http\Response響應(yīng)。

發(fā)送響應(yīng)

HTTP頭部發(fā)送后一般把響應(yīng)的內(nèi)容也發(fā)送出去:

echo $application->handle()->getContent();

這就是Phalcon Framework的完整MVC流程。

流程控制

分析MVC的啟動流程,無疑是希望對流程有更好的把握和控制,方法有兩種:

自定義啟動

按照上面的流程,我們其實完全可以自己實現(xiàn)$application->handle()->getContent()這一流程,下面就是一個簡單的替代方案,代碼中暫時沒有考慮事件的觸發(fā)。

//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擴展型的框架,其優(yōu)勢就在于高性能,雖然我們可以通過上一種方法自己實現(xiàn)整個啟動,但更好的方式仍然是避免替換框架本身的內(nèi)容,而使用事件驅(qū)動。

下面梳理了整個MVC流程中所涉及的可被監(jiān)聽的事件,可以根據(jù)不同需求選擇對應(yīng)事件作為切入點:

  • DI注入
    • application:boot 應(yīng)用啟動
  • 路由階段
    • 模塊啟動
    • application:beforeStartModule 模塊啟動前
    • application:afterStartModule 模塊啟動后
  • 分發(fā)階段
    • application:beforeHandleRequest進入分發(fā)器前
    • 開始分發(fā)
      • dispatch:beforeDispatchLoop 分發(fā)循環(huán)開始前
      • dispatch:beforeDispatch 單次分發(fā)開始前
      • dispatch:beforeExecuteRoute Action執(zhí)行前
      • dispatch:afterExecuteRoute Action執(zhí)行后
      • dispatch:beforeNotFoundAction 找不到Action
      • dispatch:beforeException 拋出異常前
      • dispatch:afterDispatch 單次分發(fā)結(jié)束
      • dispatch:afterDispatchLoop 分發(fā)循環(huán)結(jié)束
    • application:afterHandleRequest 分發(fā)結(jié)束
  • 渲染階段
    • application:viewRender 渲染開始前
  • 發(fā)送響應(yīng)
    • application:beforeSendResponse 最終響應(yīng)發(fā)送前
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,890評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,155評論 25 708
  • 先說幾句廢話,調(diào)和氣氛。事情的起由來自客戶需求頻繁變更,偉大的師傅決定橫刀立馬的改革使用新的框架(created ...
    wsdadan閱讀 3,076評論 0 12
  • 安裝完 Phalcon 后,接下來就是如何搭建自己的應(yīng)用了。這里介紹下最簡單的應(yīng)用搭建。 一、單一模塊簡單應(yīng)用 首...
    野塵lxw閱讀 907評論 0 1
  • 看完了《驢得水》,真的是一部好電影。 節(jié)奏明快,人物鮮明,這樣的電影,現(xiàn)在不多見。 看完電影,你仍然會有很深刻的印...
    女孩為何不扎馬尾閱讀 315評論 2 1