Zend_Controller_Action的實(shí)現(xiàn)
Zend Framework的動(dòng)作控制器需要繼承Zend_Controller_Action,Zend_Controller_Action提供了動(dòng)作控制器的基本功能,具體參考如下代碼:
Zend_Controller_Action_Interface
<?phpinterface Zend_Controller_Action_Interface{ /** * Class constructor * * The request and response objects should be registered with the * controller, as should be any additional optional arguments; these will be * available via {@link getRequest()}, {@link getResponse()}, and * {@link getInvokeArgs()}, respectively. * * When overriding the constructor, please consider this usage as a best * practice and ensure that each is registered appropriately; the easiest * way to do so is to simply call parent::__construct($request, $response, * $invokeArgs). * * After the request, response, and invokeArgs are set, the * {@link $_helper helper broker} is initialized. * * Finally, {@link init()} is called as the final action of * instantiation, and may be safely overridden to perform initialization * tasks; as a general rule, override {@link init()} instead of the * constructor to customize an action controller's instantiation. * * @param Zend_Controller_Request_Abstract $request * @param Zend_Controller_Response_Abstract $response * @param array $invokeArgs Any additional invocation arguments * @return void */ public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $invokeArgs = array()); /** * Dispatch the requested action * * @param string $action Method name of action * @return void */ public function dispatch($action);}
<?php require_once 'Zend/Controller/Action/HelperBroker.php'; require_once 'Zend/Controller/Action/Interface.php'; require_once 'Zend/Controller/Front.php'; abstract class Zend_Controller_Action implements Zend_Controller_Action_Interface{ protected $_classMethods; protected $_delimiters; protected $_invokeArgs = array(); protected $_frontController; protected $_request = null; protected $_response = null; public $viewSuffix = 'phtml'; public $view; protected $_helper = null; public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $invokeArgs = array()) { $this->setRequest($request) ->setResponse($response) ->_setInvokeArgs($invokeArgs); $this->_helper = new Zend_Controller_Action_HelperBroker($this); $this->init(); } public function init() { } public function initView() { if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { return $this->view; } require_once 'Zend/View/Interface.php'; if (isset($this->view) && ($this->view instanceof Zend_View_Interface)) { return $this->view; } $request = $this->getRequest(); $module = $request->getModuleName(); $dirs = $this->getFrontController()->getControllerDirectory(); if (empty($module) || !isset($dirs[$module])) { $module = $this->getFrontController()->getDispatcher()->getDefaultModule(); } $baseDir = dirname($dirs[$module]) . DIRECTORY_SEPARATOR . 'views'; if (!file_exists($baseDir) || !is_dir($baseDir)) { require_once 'Zend/Controller/Exception.php'; throw new Zend_Controller_Exception('Missing base view directory ("' . $baseDir . '")'); } require_once 'Zend/View.php'; $this->view = new Zend_View(array('basePath' => $baseDir)); return $this->view; } public function render($action = null, $name = null, $noController = false) { if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { return $this->_helper->viewRenderer->render($action, $name, $noController); } $view = $this->initView(); $script = $this->getViewScript($action, $noController); $this->getResponse()->appendBody( $view->render($script), $name ); } public function renderScript($script, $name = null) { if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { return $this->_helper->viewRenderer->renderScript($script, $name); } $view = $this->initView(); $this->getResponse()->appendBody( $view->render($script), $name ); } public function getViewScript($action = null, $noController = null) { if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) { $viewRenderer = $this->_helper->getHelper('viewRenderer'); if (null !== $noController) { $viewRenderer->setNoController($noController); } return $viewRenderer->getViewScript($action); } $request = $this->getRequest(); if (null === $action) { $action = $request->getActionName(); } elseif (!is_string($action)) { require_once 'Zend/Controller/Exception.php'; throw new Zend_Controller_Exception('Invalid action specifier for view render'); } if (null === $this->_delimiters) { $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher(); $wordDelimiters = $dispatcher->getWordDelimiter(); $pathDelimiters = $dispatcher->getPathDelimiter(); $this->_delimiters = array_unique(array_merge($wordDelimiters, (array) $pathDelimiters)); } $action = str_replace($this->_delimiters, '-', $action); $script = $action . '.' . $this->viewSuffix; if (!$noController) { $controller = $request->getControllerName(); $controller = str_replace($this->_delimiters, '-', $controller); $script = $controller . DIRECTORY_SEPARATOR . $script; } return $script; } public function getRequest() { return $this->_request; } public function setRequest(Zend_Controller_Request_Abstract $request) { $this->_request = $request; return $this; } public function getResponse() { return $this->_response; } public function setResponse(Zend_Controller_Response_Abstract $response) { $this->_response = $response; return $this; } protected function _setInvokeArgs(array $args = array()) { $this->_invokeArgs = $args; return $this; } public function getInvokeArgs() { return $this->_invokeArgs; } public function getInvokeArg($key) { if (isset($this->_invokeArgs[$key])) { return $this->_invokeArgs[$key]; } return null; } public function getHelper($helperName) { return $this->_helper->{$helperName}; } public function getHelperCopy($helperName) { return clone $this->_helper->{$helperName}; } public function setFrontController(Zend_Controller_Front $front) { $this->_frontController = $front; return $this; } public function getFrontController() { // Used cache version if found if (null !== $this->_frontController) { return $this->_frontController; } // Grab singleton instance, if class has been loaded if (class_exists('Zend_Controller_Front')) { $this->_frontController = Zend_Controller_Front::getInstance(); return $this->_frontController; } // Throw exception in all other cases require_once 'Zend/Controller/Exception.php'; throw new Zend_Controller_Exception('Front controller class has not been loaded'); } public function preDispatch() { } public function postDispatch() { } public function __call($methodName, $args) { require_once 'Zend/Controller/Action/Exception.php'; if ('Action' == substr($methodName, -6)) { $action = substr($methodName, 0, strlen($methodName) - 6); throw new Zend_Controller_Action_Exception(sprintf('Action "%s" does not exist and was not trapped in __call()', $action), 404); } throw new Zend_Controller_Action_Exception(sprintf('Method "%s" does not exist and was not trapped in __call()', $methodName), 500); } public function dispatch($action) { // Notify helpers of action preDispatch state $this->_helper->notifyPreDispatch(); $this->preDispatch(); if ($this->getRequest()->isDispatched()) { if (null === $this->_classMethods) { $this->_classMethods = get_class_methods($this); } // If pre-dispatch hooks introduced a redirect then stop dispatch // @see ZF-7496 if (!($this->getResponse()->isRedirect())) { // preDispatch() didn't change the action, so we can continue if ($this->getInvokeArg('useCaseSensitiveActions') || in_array($action, $this->_classMethods)) { if ($this->getInvokeArg('useCaseSensitiveActions')) { trigger_error('Using case sensitive actions without word separators is deprecated; please do not rely on this "feature"'); } $this->$action(); } else { $this->__call($action, array()); } } $this->postDispatch(); } // whats actually important here is that this action controller is // shutting down, regardless of dispatching; notify the helpers of this // state $this->_helper->notifyPostDispatch(); } public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null) { if (null !== $request) { $this->setRequest($request); } else { $request = $this->getRequest(); } if (null !== $response) { $this->setResponse($response); } $action = $request->getActionName(); if (empty($action)) { $action = 'index'; } $action = $action . 'Action'; $request->setDispatched(true); $this->dispatch($action); return $this->getResponse(); } protected function _getParam($paramName, $default = null) { $value = $this->getRequest()->getParam($paramName); if ((null === $value || '' === $value) && (null !== $default)) { $value = $default; } return $value; } protected function _setParam($paramName, $value) { $this->getRequest()->setParam($paramName, $value); return $this; } protected function _hasParam($paramName) { return null !== $this->getRequest()->getParam($paramName); } protected function _getAllParams() { return $this->getRequest()->getParams(); } final protected function _forward($action, $controller = null, $module = null, array $params = null) { $request = $this->getRequest(); if (null !== $params) { $request->setParams($params); } if (null !== $controller) { $request->setControllerName($controller); // Module should only be reset if controller has been specified if (null !== $module) { $request->setModuleName($module); } } $request->setActionName($action) ->setDispatched(false); } protected function _redirect($url, array $options = array()) { $this->_helper->redirector->gotoUrl($url, $options); }}
Zend_Controller_Action提供了動(dòng)作和視圖的render功能,以及注冊(cè)請(qǐng)求和響應(yīng)對(duì)象,常用助手等等。
動(dòng)作控制器的常用方法
在動(dòng)作控制器中常用的方法和屬性如下:
$this->_helper主要完成助手的相關(guān)操作例如:
// 只是局部控制器;當(dāng)初始化加載時(shí),對(duì)這個(gè)控制器的所有動(dòng)作有效:
$this->_helper->viewRenderer->setNoRender(true);
// 全局:
$this->_helper->removeHelper('viewRenderer');
// 也是全局,但需要和本地版本協(xié)作,以便繁殖這個(gè)控制器:
Zend_Controller_Front::getInstance()->setParam('noViewRenderer', true);
通過(guò)設(shè)置ViewRenderer的noRender標(biāo)記,可以簡(jiǎn)單地為一個(gè)獨(dú)立的視圖禁止解析(rendering):
class FooController extends Zend_Controller_Action
{
public function barAction()
{
// disable autorendering for this action only:
$this->_helper->viewRenderer->setNoRender();
}
}
禁止ViewRenderer的主要原因是如果你不需要視圖對(duì)象或者如果你不通過(guò)視圖腳本(例如,當(dāng)使用動(dòng)作控制器來(lái)司服網(wǎng)站服務(wù)協(xié)議如SOAP,XML-RPC或REST)來(lái)解析。大多數(shù)情況下,你不需要全局地禁止ViewRenderer,只選擇性地在個(gè)別控制器或動(dòng)作里禁止它。
請(qǐng)求對(duì)象和響應(yīng)對(duì)象的相關(guān)操作
無(wú)數(shù)的對(duì)象和變量與對(duì)象一起注冊(cè),并且每個(gè)都有訪問(wèn)器方法。
請(qǐng)求對(duì)象:getRequest()可用來(lái)讀取調(diào)用動(dòng)作請(qǐng)求對(duì)象。
響應(yīng)對(duì)象: getResponse()可用來(lái)讀取收集最終響應(yīng)的響應(yīng)對(duì)象。一些典型的調(diào)用看起來(lái)象這樣:
$this->getResponse()->setHeader('Content-Type', 'text/xml');
$this->getResponse()->appendBody($content);
調(diào)用參數(shù):前端控制器可能把參數(shù)傳給路由器、派遣器和動(dòng)作控制器。為了讀取這些參數(shù),可使用getInvokeArg($key);另外,用getInvokeArgs()讀取整個(gè)參數(shù)列表。
請(qǐng)求參數(shù):請(qǐng)求對(duì)象手機(jī)請(qǐng)求參數(shù),如任何_GET 或 _POST 參數(shù),或者指定在URL的路徑信息里的用戶參數(shù)。為了讀取這些參數(shù),可使用_getParam($key)或_getAllParams()。也可以用_setParam()來(lái)設(shè)置請(qǐng)求參數(shù);當(dāng)轉(zhuǎn)發(fā)到另外的動(dòng)作時(shí)這很有用。
用_hasParam($key)來(lái)測(cè)試是否一個(gè)參數(shù)存在(對(duì)邏輯分支有用)。
Note: _getParam()可帶有一個(gè)可選的第二個(gè)參數(shù),如果它不是空的,就包含一個(gè)缺省的值。用它在讀取值之前來(lái)消除對(duì)_hasParam() 的調(diào)用:
// Use default value of 1 if id is not set
$id = $this->_getParam('id', 1);
// Instead of:
if ($this->_hasParam('id') {
$id = $this->_getParam('id');
} else {
$id = 1;
}
視圖的相關(guān)操作
Zend_Controller_Action為視圖繼承提供了一個(gè)初步的靈活的機(jī)制。有兩個(gè)方法來(lái)完成這個(gè):initView() 和 render();前者松散地加載$view public 屬性,后者基于當(dāng)前請(qǐng)求的動(dòng)作來(lái)解析視圖,它們使用目錄層次來(lái)決定腳本路徑。
視圖初始化
initView()初始化視圖對(duì)象。為了讀取視圖對(duì)象,render()調(diào)用initView(),但它可以在任何時(shí)候被初始化;缺省地,它用Zend_View對(duì)象來(lái)組裝$view屬性,但任何實(shí)現(xiàn)Zend_View_Interface的類可以使用。如果$view已經(jīng)被初始化,它就簡(jiǎn)單地返回屬性。
缺省的實(shí)現(xiàn)使用下面假設(shè)的目錄結(jié)構(gòu):
applicationOrModule/
controllers/
IndexController.php
views/
scripts/
index/
index.phtml
helpers/
filters/
換句話說(shuō),視圖腳本假定放在views/scripts/子目錄,同時(shí)假定 views子目錄還包含兄弟功能(助手和過(guò)濾器)。確定視圖腳本名稱和路徑時(shí),先以 views/scripts/作為基路徑,然后加上以視圖腳本對(duì)應(yīng)控制器命名的目錄。
解析(Rendering)視圖
render() 有下列特征:has the following signature:
string render(string $action = null,
string $name = null,
bool $noController = false);
render()解析視圖腳本。如果沒(méi)有傳遞參數(shù),它假定請(qǐng)求的腳本是[controller]/[action].phtml (.phtml是$viewSuffix屬性的值)。為$action傳遞一個(gè)值將解析在[controller]子目錄中的模板。為用[controller]重寫(xiě),傳遞一個(gè)true值給$noController。最后,模板被解析到響應(yīng)對(duì)象;如果你希望解析到一個(gè)在響應(yīng)對(duì)象里指定的named segment,傳遞一個(gè)值給$name。
Note: 因?yàn)榭刂破骱蛣?dòng)作名字里可能包含分隔符如'_'、 '.' 和 '-',當(dāng)決定視圖名字時(shí),render()把它們規(guī)格化成 '-'.在內(nèi)部,它使用派遣器的字和路徑分隔符來(lái)做規(guī)格化。這樣,對(duì)/foo.bar/baz-bat的請(qǐng)求將解析腳本foo-bar/baz-bat.phtml。如果動(dòng)作方法包含camelCasing,記住當(dāng)決定視圖腳本文件名的時(shí)候,這將變成由'-'分隔的字。
一些例子:
class MyController extends Zend_Controller_Action
{
public function fooAction()
{
// Renders my/foo.phtml
$this->render();
// Renders my/bar.phtml
$this->render('bar');
// Renders baz.phtml
$this->render('baz', null, true);
// Renders my/login.phtml to the 'form' segment of the
// response object
$this->render('login', 'form');
// Renders site.phtml to the 'page' segment of the response
// object; does not use the 'my/' subirectory
$this->render('site', 'page', true);
}
public function bazBatAction()
{
// Renders my/baz-bat.phtml
$this->render();
}
}
其它
_forward($action, $controller = null, $module = null, array $params = null) :執(zhí)行另外一個(gè)動(dòng)作。如果在preDispatch()里調(diào)用,當(dāng)前請(qǐng)求的動(dòng)作將被跳過(guò)來(lái)支持新的動(dòng)作。否則,在當(dāng)前動(dòng)作被處理之后,在_forward()請(qǐng)求的動(dòng)作將被執(zhí)行。
_redirect($url, array $options = array()):重定向到另外一個(gè)地方。這個(gè)方法用URL和一組可選的選項(xiàng)。缺省地,它執(zhí)行HTTP 302 重定向。
選項(xiàng)可包括一個(gè)或多個(gè)下面這些:
exit:是否立即退出。如果被請(qǐng)求,它將干凈地關(guān)閉任何打開(kāi)的會(huì)話和執(zhí)行重定向。
可以用setRedirectExit()訪問(wèn)器在控制器里全局地設(shè)置這個(gè)選項(xiàng)。
prependBase:是否預(yù)先考慮基礎(chǔ)URL和URL提供的請(qǐng)求對(duì)象一起注冊(cè)。
使用setRedirectPrependBase()訪問(wèn)器,在控制器里全局地設(shè)置這個(gè)選項(xiàng)。
code:在重定向時(shí)要用什么HTTP代碼。缺省使用302;可以用從301到306之間的任何代碼。
使用setRedirectCode()訪問(wèn)器,在控制器里全局地設(shè)置這個(gè)選項(xiàng)。
擴(kuò)展自定義Zend_Controller_Action
為了創(chuàng)建動(dòng)作控制器,設(shè)計(jì)上,Zend_Controller_Action 必須被繼承。至少,需要定義控制器可能調(diào)用的動(dòng)作方法。
除了為web應(yīng)用程序創(chuàng)建有用的函數(shù)外,你可能發(fā)現(xiàn)在不同的控制器里重復(fù)同樣的設(shè)置和實(shí)用方法;如果這樣,創(chuàng)建一個(gè)繼承(extends)Zend_Controller_Action 的基礎(chǔ)類可能會(huì)解決問(wèn)題。
Example #1 如何處理不存在的動(dòng)作
如果控制器的請(qǐng)求包括一個(gè)未定義的動(dòng)作方法,Zend_Controller_Action::__call()將被調(diào)用。__call()當(dāng)然是PHP中用來(lái)重載方法的魔術(shù)方法。
缺省地,這個(gè)方法拋出一個(gè)Zend_Controller_Action_Exception 來(lái)表明在控制器里沒(méi)有發(fā)現(xiàn)要求的方法。如果要求的方法以'Action'結(jié)尾,就假設(shè)一個(gè)動(dòng)作被請(qǐng)求并且不存在;這樣的錯(cuò)誤導(dǎo)致帶有代碼為 404 的異常。所有其它方法導(dǎo)致帶有代碼為 500 的異常。這使你很容易地在錯(cuò)誤句柄里區(qū)分是頁(yè)面沒(méi)有發(fā)現(xiàn)還是程序錯(cuò)誤。
如果想執(zhí)行其它操作,你應(yīng)該重寫(xiě)這個(gè)函數(shù)。例如,如果你想顯示錯(cuò)誤信息,可以象下面這樣來(lái)寫(xiě):
class MyController extends Zend_Controller_Action
{
public function __call($method, $args)
{
if ('Action' == substr($method, -6)) {
// If the action method was not found, render the error
// template
return $this->render('error');
}
// all other methods throw an exception
throw new Exception('Invalid method "'
. $method
. '" called',
500);
}
}
另外的可能性就是你可能想轉(zhuǎn)發(fā)到缺省控制頁(yè)面:
class MyController extends Zend_Controller_Action
{
public function indexAction()
{
$this->render();
}
public function __call($method, $args)
{
if ('Action' == substr($method, -6)) {
// If the action method was not found, forward to the
// index action
return $this->_forward('index');
}
// all other methods throw an exception
throw new Exception('Invalid method "'
. $method
. '" called',
500);
}
}
為了定制控制器,除了重寫(xiě)__call()以外,本章前面說(shuō)涉及的初始化、實(shí)用程序、訪問(wèn)器、視圖和派遣鉤子等方法都可以被重寫(xiě)。作為例子,如果把視圖對(duì)象保存到注冊(cè)表里,你可能想用象下面的代碼來(lái)修改initView():
abstract class My_Base_Controller extends Zend_Controller_Action
{
public function initView()
{
if (null === $this->view) {
if (Zend_Registry::isRegistered('view')) {
$this->view = Zend_Registry::get('view');
} else {
$this->view = new Zend_View();
$this->view->setBasePath(dirname(__FILE__) . '/../views');
}
}
return $this->view;
}
}