Phalcon自己有支持建立多種形式的Web應用項目以應對不一樣場景,包括迷你應用、單模塊標準應用、以及較複雜的多模塊應用php
Phalcon環境配置安裝後,能夠經過命令行生成一個標準的Phalcon多模塊應用html
phalcon project eva --type modules
入口文件爲public/index.php
,簡化後一共5行,包含了整個Phalcon的啓動流程,如下將按順序說明git
require __DIR__ . '/../config/services.php'; $application = new Phalcon\Mvc\Application(); $application->setDI($di); require __DIR__ . '/../config/modules.php'; echo $application->handle()->getContent();
Phalcon的全部組件服務都是經過DI(依賴注入)進行組織的,這也是目前大部分主流框架所使用的方法。經過DI,能夠靈活的控制框架中的服務:哪些須要啓用,哪些不啓用,組件的內部細節等等,所以Phalcon是一個鬆耦合可替換的框架,徹底能夠經過DI替換MVC中任何一個組件。github
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還註冊瞭如下服務:sql
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(); c#
$application->setDI($di);api
與DI同樣,Phalcon建議經過引入一個獨立文件的方式註冊全部須要的模塊:cookie
require __DIR__ . '/../config/modules.php';
這個文件的內容以下
$application->registerModules(array(
'base' => array(
'className' => 'Cn\Liuxue\Site\Base\Module',
'path' => __DIR__ . '/../apps/base/Module.php'
),
//前臺
'front' => array(
'className' => 'Cn\Liuxue\Site\Front\Module',
'path' => __DIR__ . '/../apps/www/Module.php'
),
//後臺
'backend' => array(
'className' => 'Cn\Liuxue\Site\Backend\Module',
'path' => __DIR__ . '/../apps/admin/Module.php'
),
//CMS
'cms' => array(
'className' => 'Cn\Liuxue\Site\Cms\Module',
'path' => __DIR__ . '/../apps/cms/Module.php'
),
));
能夠看到Phalcon所謂的模塊註冊,其實只是告訴框架MVC模塊的引導文件Module.php
所在位置及類名是什麼。
$application->handle()
是整個MVC的核心,這個函數中處理了路由、模塊、分發等MVC的所有流程,處理過程當中在關鍵位置會經過事件驅動觸發一系列application:
事件,方便外部注入邏輯,最終返回一個Phalcon\Http\Response
。整個handle
方法的過程並不複雜,下面按順序介紹:$application->handle()application:Phalcon\Http\Responsehandle
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
事件,進入分發階段
分發過程由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對整個分發過程進行了進一步細分,而且在分發的過程當中會按順序觸發很是多的分發事件,能夠經過這些分發事件進行更加細緻的流程控制。部分事件提供了可中斷的機制,只要返回false
就能夠跳過Dispatcher的分發過程。
因爲分發中可使用Phalcon\Mvc\Dispatcher->forward()
來實現Action的複用,所以分發在內部會經過循環實現,經過檢測一個全局的finished
標記來決定是否繼續分發。當如下幾種狀況時,分發纔會結束:
forward
層數達到最大(256次)分發結束後會觸發application:afterHandleRequest
,接下來經過Phalcon\Mvc\Dispatcher->getReturnedValue()
取得分發過程返回的結果並進行處理。
因爲Action的邏輯在框架外,Action的返回值是沒法預期的,所以這裏根據返回值是否實現Phalcon\Http\ResponseInterface
接口進行區分處理。
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。
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();
爲了方便查找,將整個流程整理爲一個樹形清單以下:
$di = new FactoryDefault();
$di['router'] = function () {}
$di['url'] = function () {}
$di['session'] = function () {}
$application = new Application();
$application->setDI($di);
$application->registerModules()
$application->handle()
application:boot
$di['router']->handle()
$moduleName = $di['router']->getModuleName()
,若是沒有則從 $application->getDefaultModule
獲取application:beforeStartModule
registerAutoloaders()
以及 registerServices()
application:afterStartModule
View->start()
開啓緩衝區application:beforeHandleRequest
Dispatcher->dispatch()
dispatch:beforeDispatchLoop
dispatch:beforeDispatch
dispatch:beforeException
dispatch:beforeExecuteRoute
Controller->beforeExecuteRoute()
Controller->initialize()
dispatch:afterInitialize
dispatch:afterExecuteRoute
dispatch:afterDispatch
dispatch:afterDispatchLoop
$dispatcher->getReturnedValue()
application:afterHandleRequest
分發結束Phalcon\Http\ResponseInterface
類型的返回,則渲染直接結束
application:viewRender
分發結束Phalcon\Mvc\View->render()
,入口參數爲Dispatcher的 ControllerName / ActionName / ParamsPhalcon\Mvc\View->finish()
結束緩衝區的接收Phalcon\Mvc\View->getContent()
經過Phalcon\Http\Response->setContent()
放入Responseapplication:beforeSendResponse
Phalcon\Http\Response->sendHeaders()
發送頭部Phalcon\Http\Response->sendCookies()
發送Cookie$application->handle()
的返回值返回echo $application->handle()->getContent();
Phalcon做爲C擴展型的框架,其優點就在於高性能,雖然咱們能夠經過上一種方法本身實現整個啓動,但更好的方式仍然是避免替換框架自己的內容,而使用事件驅動。
下面梳理了整個MVC流程中所涉及的可被監聽的事件,能夠根據不一樣需求選擇對應事件做爲切入點:
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
最終響應發送前