控制器(Controller)結構包含一個可以在控制器周期內確定事件發生時調用用戶代碼的插件系統。 前端控制器(Front controller)使用插件 broker 作為用戶插件注冊,同時插件 broker 確保前端控制器中注冊的每個插件都在事件發生時調用相應的事件方法。
事件方法定義在虛類 Zend_Controller_Plugin_Abstract
,用戶插件應當從這個類繼承:
routeStartup()
在 Zend_Controller_Front
向注冊的 路由器 發送請求前被調用。
routeShutdown()
在 路由器 完成請求的路由后被調用。
dispatchLoopStartup()
在 Zend_Controller_Front
進入其分發循環(dispatch loop)前被調用。
preDispatch()
在動作由 分發器 分發前被調用。該回調方法允許代理或者過濾行為。通過修改請求和重設分發標志位(利用 Zend_Controller_Request_Abstract::setDispatched(false)
)當前動作可以跳過或者被替換。
postDispatch()
在動作由 分發器 分發后被調用。該回調方法允許代理或者過濾行為。通過修改請求和重設分發標志位(利用 Zend_Controller_Request_Abstract::setDispatched(false)
)可以指定新動作進行分發。
dispatchLoopShutdown()
在 Zend_Controller_Front
推出其分發循環后調用。
只需要包含并擴展抽象類 Zend_Controller_Plugin_Abstract
即可編寫插件類。
class MyPlugin extends Zend_Controller_Plugin_Abstract{ // ...}
Zend_Controller_Plugin_Abstract
的全部方法都不是抽象的, 這意味著插件類并不是一定要去實現前面列出的每一個事件方法。 插件的開發者只要實現需要用到的方法即可。
Zend_Controller_Plugin_Abstract
也可以通過調用 getRequest()
和 getResponse()
方法從控制器中分別獲取 request 對象和 response 對象.
可以使用 Zend_Controller_Front::registerPlugin()
在任何時候注冊插件類。 下面的代碼片段說明了如何在控制器鏈條中使用插件。
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();
假設沒有動作產生任何輸出,而只有一個動作被調用,前面演示的插件仍然會產生下面的輸出:
<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)執行的任何時候被被注冊, 如果一個事件在注冊時已經完成,則這個事件對應的事件方法不會被觸發。
有時,可能需要取消注冊或者獲取一個插件。下面列出的前端控制器中的方法可以實現這個功能:
getPlugin($class)
允許獲取指定類名的一個插件。 如果沒有插件匹配,將返回 false。如果有多個指定類的插件被注冊,則返回一個數組。
getPlugins()
返回全部插件。
unregisterPlugin($plugin)
允許從插件列表中移除一個插件。 傳遞一個插件件對象,或者需要移除的插件的類名。如果傳遞類名,任何該類的插件都將被移除。
Zend Framework 在其標準發行包中包含錯誤處理插件。
動作堆棧
插件可以管理一個請求堆棧,其操作象postDispatch
插件。如果一個轉發(例如,對另一個動作的調用)在當前請求對象中已經檢測到,它不做任何事情。然而,如果沒有檢測到,它就檢查堆棧并把最上面的一個取出,然后轉發給由那個請求指定的動作。堆棧按照LIFO(后進先出)順序處理。
你可以在任何時候用 Zend_Controller_Front::getPlugin('Zend_Controller_Plugin_ActionStack')
從前端控制器獲取插件。一旦你有插件對象,有很多機制你可以用來操作。
getRegistry()
和 setRegistry()
。在內部, 動作堆棧
使用一個Zend_Registry
實例來存儲堆棧。你可以用不同的注冊表實例來代替或在這些訪問器里獲取。
getRegistryKey()
和 setRegistryKey()
。當彈出堆棧時,這些可以用來識別使用哪個注冊表鍵。缺省地值是'Zend_Controller_Plugin_ActionStack'。
getStack()
允許你全面地獲取動作堆棧。
pushStack()
和 popStack()
分別允許你彈出和壓棧。pushStack()
接受請求對象。
一個附加的方法,forward()
,準備一個請求對象,并在前端控制器設置當前請求狀態給提供的請求對象的狀態,使它不可派遣(強制另一個派遣循環迭代)。
Zend_Controller_Plugin_ErrorHandler
提供了一個活動的插件,用來處理從程序拋出的異常,包括那些從缺控制器或動作的來的結果;它是一個列在MVC Exceptions section里的方法的一個替代。
插件的基本目標是:
監視由于缺失控制器或動作方法而產生的異常
監視動作控制器里產生的異常
換句話說,ErrorHandler
插件設計用來處理HTTP 404 類型的錯誤(找不到頁面)和 500 類型錯誤(內部錯誤)。它不打算抓取有其它插件或路由產生的異常。
缺省地,在缺省模塊中,Zend_Controller_Plugin_ErrorHandler
將轉發給ErrorController::errorAction()
。你可以通過使用在插件中不同的訪問器給它們設置替代的值:
setErrorHandlerModule()
設置控制器模塊來使用。
setErrorHandlerController()
設置控制器來用。
setErrorHandlerAction()
設置控制器動作來用。
setErrorHandler()
接受聯合數組,它可以包含任何鍵,如'module'、 'controller' 或 'action',以及要給它們設置的合適的值。
另外,你可以傳遞一個可選的聯合數組給可以代理setErrorHandler()
的構造函數。
Zend_Controller_Plugin_ErrorHandler
注冊一個postDispatch()
鉤子和檢查注冊在響應對象里的異常。如果發現有異常,它試圖轉發給注冊的錯誤處理器(handler)動作。
如果在派遣錯誤處理器時發生異常,這插件將告訴前端控制器拋出異常,并重新拋出和帶響應對象注冊的最后一個異常。
因為ErrorHandler
插件不僅抓取程序錯誤,而且也抓取在控制器鏈里的由于缺失控制器類和/或動作方法而產生的錯誤,它可以用作一個404處理器。這樣做,需要讓錯誤控制器檢查異常類型。
異常的抓取被記錄在一個對象里,這個對象注冊在請求里。使用Zend_Controller_Action::_getParam('error_handler')
來讀取它:
class ErrorController extends Zend_Controller_Action{ public function errorAction() { $errors = $this->_getParam('error_handler'); }}
一旦有錯誤對象,可通過$errors->type
來獲得類型。它將是下面其中之一:
Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER
,指示控制器沒有被發現。
Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION
,指示請求動作沒有被發現。
Zend_Controller_Plugin_ErrorHandler::EXCEPTION_OTHER
,指示其它異常。
然后可以測試頭兩個類型中的任意一個,并且,如果這樣,顯示一個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; } }}
最后,你可以讀取異常,這個異常由錯誤管理器通過抓取error_handler
對象的exception
屬性來觸發的:
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; }}
如果你在一個請求里派遣多個動作,或者你的動作對render()
做多次調用,很可能響應對象已經有存儲在它里面的內容。這可以導致呈顯期望的內容和錯誤的內容的混合體。
如果你希望呈現錯誤內嵌到這樣的頁面,不需要修改。如果你不希望呈現這樣的內容,你應該在呈現任何視圖之前清除響應體:
$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);
為了使用錯誤處理器插件,你需要錯誤控制器。下面是個簡單的例子。
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; }}