控制器(Controller)結(jié)構(gòu)包含一個(gè)可以在控制器周期內(nèi)確定事件發(fā)生時(shí)調(diào)用用戶代碼的插件系統(tǒng)。 前端控制器(Front controller)使用插件 broker 作為用戶插件注冊(cè),同時(shí)插件 broker 確保前端控制器中注冊(cè)的每個(gè)插件都在事件發(fā)生時(shí)調(diào)用相應(yīng)的事件方法。
事件方法定義在虛類 Zend_Controller_Plugin_Abstract
,用戶插件應(yīng)當(dāng)從這個(gè)類繼承:
routeStartup()
在 Zend_Controller_Front
向注冊(cè)的 路由器 發(fā)送請(qǐng)求前被調(diào)用。
routeShutdown()
在 路由器 完成請(qǐng)求的路由后被調(diào)用。
dispatchLoopStartup()
在 Zend_Controller_Front
進(jìn)入其分發(fā)循環(huán)(dispatch loop)前被調(diào)用。
preDispatch()
在動(dòng)作由 分發(fā)器 分發(fā)前被調(diào)用。該回調(diào)方法允許代理或者過濾行為。通過修改請(qǐng)求和重設(shè)分發(fā)標(biāo)志位(利用 Zend_Controller_Request_Abstract::setDispatched(false)
)當(dāng)前動(dòng)作可以跳過或者被替換。
postDispatch()
在動(dòng)作由 分發(fā)器 分發(fā)后被調(diào)用。該回調(diào)方法允許代理或者過濾行為。通過修改請(qǐng)求和重設(shè)分發(fā)標(biāo)志位(利用 Zend_Controller_Request_Abstract::setDispatched(false)
)可以指定新動(dòng)作進(jìn)行分發(fā)。
dispatchLoopShutdown()
在 Zend_Controller_Front
推出其分發(fā)循環(huán)后調(diào)用。
只需要包含并擴(kuò)展抽象類 Zend_Controller_Plugin_Abstract
即可編寫插件類。
class MyPlugin extends Zend_Controller_Plugin_Abstract{ // ...}
Zend_Controller_Plugin_Abstract
的全部方法都不是抽象的, 這意味著插件類并不是一定要去實(shí)現(xiàn)前面列出的每一個(gè)事件方法。 插件的開發(fā)者只要實(shí)現(xiàn)需要用到的方法即可。
Zend_Controller_Plugin_Abstract
也可以通過調(diào)用 getRequest()
和 getResponse()
方法從控制器中分別獲取 request 對(duì)象和 response 對(duì)象.
可以使用 Zend_Controller_Front::registerPlugin()
在任何時(shí)候注冊(cè)插件類。 下面的代碼片段說明了如何在控制器鏈條中使用插件。
class MyPlugin extends Zend_Controller_Plugin_Abstract{ public function routeStartup(Zend_Controller_Request_Abstract $request) { $this->getResponse()->appendBody("<p>routeStartup() called</p>"); } public function routeShutdown(Zend_Controller_Request_Abstract $request) { $this->getResponse()->appendBody("<p>routeShutdown() called</p>"); } public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request) { $this->getResponse()->appendBody("<p>dispatchLoopStartup() called</p>"); } public function preDispatch(Zend_Controller_Request_Abstract $request) { $this->getResponse()->appendBody("<p>preDispatch() called</p>"); } public function postDispatch(Zend_Controller_Request_Abstract $request) { $this->getResponse()->appendBody("<p>postDispatch() called</p>"); } public function dispatchLoopShutdown() { $this->getResponse()->appendBody("<p>dispatchLoopShutdown() called</p>"); }}$front = Zend_Controller_Front::getInstance();$front->setControllerDirectory('/path/to/controllers') ->setRouter(new Zend_Controller_Router_Rewrite()) ->registerPlugin(new MyPlugin());$front->dispatch();
假設(shè)沒有動(dòng)作產(chǎn)生任何輸出,而只有一個(gè)動(dòng)作被調(diào)用,前面演示的插件仍然會(huì)產(chǎn)生下面的輸出:
<p>routeStartup() called</p><p>routeShutdown() called</p><p>dispatchLoopStartup() called</p><p>preDispatch() called</p><p>postDispatch() called</p><p>dispatchLoopShutdown() called</p>
Note: 插件可以在前端控制器(Front controller)執(zhí)行的任何時(shí)候被被注冊(cè), 如果一個(gè)事件在注冊(cè)時(shí)已經(jīng)完成,則這個(gè)事件對(duì)應(yīng)的事件方法不會(huì)被觸發(fā)。
有時(shí),可能需要取消注冊(cè)或者獲取一個(gè)插件。下面列出的前端控制器中的方法可以實(shí)現(xiàn)這個(gè)功能:
getPlugin($class)
允許獲取指定類名的一個(gè)插件。 如果沒有插件匹配,將返回 false。如果有多個(gè)指定類的插件被注冊(cè),則返回一個(gè)數(shù)組。
getPlugins()
返回全部插件。
unregisterPlugin($plugin)
允許從插件列表中移除一個(gè)插件。 傳遞一個(gè)插件件對(duì)象,或者需要移除的插件的類名。如果傳遞類名,任何該類的插件都將被移除。
Zend Framework 在其標(biāo)準(zhǔn)發(fā)行包中包含錯(cuò)誤處理插件。
動(dòng)作堆棧
插件可以管理一個(gè)請(qǐng)求堆棧,其操作象postDispatch
插件。如果一個(gè)轉(zhuǎn)發(fā)(例如,對(duì)另一個(gè)動(dòng)作的調(diào)用)在當(dāng)前請(qǐng)求對(duì)象中已經(jīng)檢測(cè)到,它不做任何事情。然而,如果沒有檢測(cè)到,它就檢查堆棧并把最上面的一個(gè)取出,然后轉(zhuǎn)發(fā)給由那個(gè)請(qǐng)求指定的動(dòng)作。堆棧按照LIFO(后進(jìn)先出)順序處理。
你可以在任何時(shí)候用 Zend_Controller_Front::getPlugin('Zend_Controller_Plugin_ActionStack')
從前端控制器獲取插件。一旦你有插件對(duì)象,有很多機(jī)制你可以用來操作。
getRegistry()
和 setRegistry()
。在內(nèi)部, 動(dòng)作堆棧
使用一個(gè)Zend_Registry
實(shí)例來存儲(chǔ)堆棧。你可以用不同的注冊(cè)表實(shí)例來代替或在這些訪問器里獲取。
getRegistryKey()
和 setRegistryKey()
。當(dāng)彈出堆棧時(shí),這些可以用來識(shí)別使用哪個(gè)注冊(cè)表鍵。缺省地值是'Zend_Controller_Plugin_ActionStack'。
getStack()
允許你全面地獲取動(dòng)作堆棧。
pushStack()
和 popStack()
分別允許你彈出和壓棧。pushStack()
接受請(qǐng)求對(duì)象。
一個(gè)附加的方法,forward()
,準(zhǔn)備一個(gè)請(qǐng)求對(duì)象,并在前端控制器設(shè)置當(dāng)前請(qǐng)求狀態(tài)給提供的請(qǐng)求對(duì)象的狀態(tài),使它不可派遣(強(qiáng)制另一個(gè)派遣循環(huán)迭代)。
Zend_Controller_Plugin_ErrorHandler
提供了一個(gè)活動(dòng)的插件,用來處理從程序拋出的異常,包括那些從缺控制器或動(dòng)作的來的結(jié)果;它是一個(gè)列在MVC Exceptions section里的方法的一個(gè)替代。
插件的基本目標(biāo)是:
監(jiān)視由于缺失控制器或動(dòng)作方法而產(chǎn)生的異常
監(jiān)視動(dòng)作控制器里產(chǎn)生的異常
換句話說,ErrorHandler
插件設(shè)計(jì)用來處理HTTP 404 類型的錯(cuò)誤(找不到頁面)和 500 類型錯(cuò)誤(內(nèi)部錯(cuò)誤)。它不打算抓取有其它插件或路由產(chǎn)生的異常。
缺省地,在缺省模塊中,Zend_Controller_Plugin_ErrorHandler
將轉(zhuǎn)發(fā)給ErrorController::errorAction()
。你可以通過使用在插件中不同的訪問器給它們?cè)O(shè)置替代的值:
setErrorHandlerModule()
設(shè)置控制器模塊來使用。
setErrorHandlerController()
設(shè)置控制器來用。
setErrorHandlerAction()
設(shè)置控制器動(dòng)作來用。
setErrorHandler()
接受聯(lián)合數(shù)組,它可以包含任何鍵,如'module'、 'controller' 或 'action',以及要給它們?cè)O(shè)置的合適的值。
另外,你可以傳遞一個(gè)可選的聯(lián)合數(shù)組給可以代理setErrorHandler()
的構(gòu)造函數(shù)。
Zend_Controller_Plugin_ErrorHandler
注冊(cè)一個(gè)postDispatch()
鉤子和檢查注冊(cè)在響應(yīng)對(duì)象里的異常。如果發(fā)現(xiàn)有異常,它試圖轉(zhuǎn)發(fā)給注冊(cè)的錯(cuò)誤處理器(handler)動(dòng)作。
如果在派遣錯(cuò)誤處理器時(shí)發(fā)生異常,這插件將告訴前端控制器拋出異常,并重新拋出和帶響應(yīng)對(duì)象注冊(cè)的最后一個(gè)異常。
因?yàn)?code class="code">ErrorHandler插件不僅抓取程序錯(cuò)誤,而且也抓取在控制器鏈里的由于缺失控制器類和/或動(dòng)作方法而產(chǎn)生的錯(cuò)誤,它可以用作一個(gè)404處理器。這樣做,需要讓錯(cuò)誤控制器檢查異常類型。
異常的抓取被記錄在一個(gè)對(duì)象里,這個(gè)對(duì)象注冊(cè)在請(qǐng)求里。使用Zend_Controller_Action::_getParam('error_handler')
來讀取它:
class ErrorController extends Zend_Controller_Action{ public function errorAction() { $errors = $this->_getParam('error_handler'); }}
一旦有錯(cuò)誤對(duì)象,可通過$errors->type
來獲得類型。它將是下面其中之一:
Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER
,指示控制器沒有被發(fā)現(xiàn)。
Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION
,指示請(qǐng)求動(dòng)作沒有被發(fā)現(xiàn)。
Zend_Controller_Plugin_ErrorHandler::EXCEPTION_OTHER
,指示其它異常。
然后可以測(cè)試頭兩個(gè)類型中的任意一個(gè),并且,如果這樣,顯示一個(gè)404頁面:
class ErrorController extends Zend_Controller_Action{ public function errorAction() { $errors = $this->_getParam('error_handler'); switch ($errors->type) { case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: // 404 error -- controller or action not found $this->getResponse() ->setRawHeader('HTTP/1.1 404 Not Found'); // ... get some output to display... break; default: // application error; display error page, but don't // change status code break; } }}
最后,你可以讀取異常,這個(gè)異常由錯(cuò)誤管理器通過抓取error_handler
對(duì)象的exception
屬性來觸發(fā)的:
public function errorAction(){ $errors = $this->_getParam('error_handler'); switch ($errors->type) { case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: // 404 error -- controller or action not found $this->getResponse() ->setRawHeader('HTTP/1.1 404 Not Found'); // ... get some output to display... break; default: // application error; display error page, but don't change // status code // ... // Log the exception: $exception = $errors->exception; $log = new Zend_Log( new Zend_Log_Writer_Stream( '/tmp/applicationException.log' ) ); $log->debug($exception->getMessage() . "" . $exception->getTraceAsString()); break; }}
如果你在一個(gè)請(qǐng)求里派遣多個(gè)動(dòng)作,或者你的動(dòng)作對(duì)render()
做多次調(diào)用,很可能響應(yīng)對(duì)象已經(jīng)有存儲(chǔ)在它里面的內(nèi)容。這可以導(dǎo)致呈顯期望的內(nèi)容和錯(cuò)誤的內(nèi)容的混合體。
如果你希望呈現(xiàn)錯(cuò)誤內(nèi)嵌到這樣的頁面,不需要修改。如果你不希望呈現(xiàn)這樣的內(nèi)容,你應(yīng)該在呈現(xiàn)任何視圖之前清除響應(yīng)體:
$this->getResponse()->clearBody();
Example #1 Standard usage
$front = Zend_Controller_Front::getInstance();$front->registerPlugin(new Zend_Controller_Plugin_ErrorHandler());
Example #2 Setting a different error handler
$front = Zend_Controller_Front::getInstance();$front->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(array( 'module' => 'mystuff', 'controller' => 'static', 'action' => 'error')));
Example #3 Using accessors
$plugin = new Zend_Controller_Plugin_ErrorHandler();$plugin->setErrorHandlerModule('mystuff') ->setErrorHandlerController('static') ->setErrorHandlerAction('error');$front = Zend_Controller_Front::getInstance();$front->registerPlugin($plugin);
為了使用錯(cuò)誤處理器插件,你需要錯(cuò)誤控制器。下面是個(gè)簡(jiǎn)單的例子。
class ErrorController extends Zend_Controller_Action{ public function errorAction() { $errors = $this->_getParam('error_handler'); switch ($errors->type) { case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: // 404 error -- controller or action not found $this->getResponse()->setRawHeader('HTTP/1.1 404 Not Found'); $content =<<<EOH<h1>Error!</h1><p>The page you requested was not found.</p>EOH; break; default: // application error $content =<<<EOH<h1>Error!</h1><p>An unexpected error occurred. Please try again later.</p>EOH; break; } // Clear previous content $this->getResponse()->clearBody(); $this->view->content = $content; }}