Zend_Controller-Exceptions.xml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <sect1 id="zend.controller.exceptions">
  2. <title>MVC 异常</title>
  3. <sect2 id="zend.controller.exceptions.introduction">
  4. <title>介绍</title>
  5. <para>
  6. Zend Framework 中的MVC元件利用了一个前端控制器,这意味着到一个站点的所有请求都将通过单一入口。因此,所有的异常最终将起泡到前端控制器,开发人员可在一个位置处理这些异常。
  7. </para>
  8. <para>
  9. 但是,异常消息以及回溯信息可能含有敏感的系统信息,比如SQL语句,文件位置等等。为了保护站点,<code>Zend_Controller_Front</code> 默认将捕捉所有异常并注册到响应对象,响应对象默认不会显示异常消息。
  10. </para>
  11. </sect2>
  12. <sect2 id="zend.controller.exceptions.handling">
  13. <title>如何处理异常?</title>
  14. <para>
  15. MVC元件已经建立了几种机制来处理异常。
  16. </para>
  17. <itemizedlist>
  18. <listitem>
  19. <para>
  20. 默认地,<link linkend="zend.controller.plugins.standard.errorhandler">错误处理器插件(error handler plugin)</link> 将会被注册并激活。这个插件可以处理:
  21. </para>
  22. <itemizedlist>
  23. <listitem><para>控制器或动作缺失导致的异常</para></listitem>
  24. <listitem><para>动作控制器中发生的异常</para></listitem>
  25. </itemizedlist>
  26. <para>
  27. 它作为一个<code>postDispatch()</code>插件,检查分发器、动作控制器或者其他的异常是否发生。如果发生异常,它将转向一个错误处理控制器。
  28. </para>
  29. <para>
  30. 该处理器会涵盖大多数异常情况,并能够优美的处理控制器或者动作缺失异常。
  31. </para>
  32. </listitem>
  33. <listitem>
  34. <para><code>Zend_Controller_Front::throwExceptions()</code></para>
  35. <para>
  36. 通过向该方法传入一个true值,可以通知前端控制器,由开发人员来处理异常,而不是让响应对象收集或者使用错误处理器插件。例如:
  37. </para>
  38. <programlisting role="php"><![CDATA[
  39. $front->throwExceptions(true);
  40. try {
  41. $front->dispatch();
  42. } catch (Exception $e) {
  43. // handle exceptions yourself
  44. }
  45. ]]>
  46. </programlisting>
  47. <para>
  48. 这是向前端控制器中加入定制处理所有可能异常的最简单方式。</para>
  49. </listitem>
  50. <listitem>
  51. <para><code>Zend_Controller_Response_Abstract::renderExceptions()</code></para>
  52. <para>
  53. 通过向该方法中传入一个true值,可以让响应对象渲染(render)异常消息,当渲染响应对象时追踪异常(backtrace)。这种情况下,将会显示程序中引发的所有异常。推荐只在非生产(non-production)环境中使用。
  54. </para>
  55. </listitem>
  56. <listitem>
  57. <para>
  58. <code>Zend_Controller_Front::returnResponse()</code> 和
  59. <code>Zend_Controller_Response_Abstract::isException()</code>.
  60. </para>
  61. <para>
  62. 向<code>Zend_Controller_Front::returnResponse()</code>传入一个true值, <code>Zend_Controller_Front::dispatch()</code> 将不渲染响应对象,而是将其返回。获得响应对象后,可通过<code>isException()</code>测试是否捕捉到异常,然后通过<code>getException()</code>获取异常。例如:
  63. </para>
  64. <programlisting role="php"><![CDATA[
  65. $front->returnResponse(true);
  66. $response = $front->dispatch();
  67. if ($response->isException()) {
  68. $exceptions = $response->getException();
  69. // handle exceptions ...
  70. } else {
  71. $response->sendHeaders();
  72. $response->outputBody();
  73. }
  74. ]]>
  75. </programlisting>
  76. <para>
  77. 这种方式相对于<code>Zend_Controller_Front::throwExceptions()</code>的主要优点在于,可以在异常处理后有条件的渲染响应对象。不像错误处理器插件,该方法能够捕捉到控制器链中的任何异常。
  78. </para>
  79. </listitem>
  80. </itemizedlist>
  81. </sect2>
  82. <sect2 id="zend.controller.exceptions.internal">
  83. <title>可能遭遇的MVC异常</title>
  84. <para>
  85. 各种MVC元件--请求,路由器,分发器,动作控制器,响应对象--在同一事件中可能每一个都会抛出异常。一些异常可能根据情况被忽略,其他的则提示开发人员考虑程序的结构。
  86. </para>
  87. <para>比如:</para>
  88. <itemizedlist>
  89. <listitem>
  90. <para>
  91. 如果请求一个无效的控制器,<code>Zend_Controller_Dispatcher::dispatch()</code> 默认会抛出一个异常。推荐采用两种方式来处理。
  92. </para>
  93. <itemizedlist>
  94. <listitem>
  95. <para>设置<code>useDefaultControllerAlways</code>参数。</para>
  96. <para>
  97. 在前端控制器或者分发器中,加入下列代码:
  98. </para>
  99. <programlisting role="php"><![CDATA[
  100. $front->setParam('useDefaultControllerAlways', true);
  101. // or
  102. $dispatcher->setParam('useDefaultControllerAlways', true);
  103. ]]>
  104. </programlisting>
  105. <para>
  106. 设置了这个标志,分发器将调用默认的控制器和动作,而不是抛出异常。该方法的缺点是用户访问站点时的URL拼写错误,依然会被解析并显示默认页,这将严重破坏搜索引擎的优化。
  107. </para>
  108. </listitem>
  109. <listitem>
  110. <para>
  111. <code>dispatch()</code>抛出的异常是一个包含文本'Invalid controller specified'的<code>Zend_Controller_Dispatcher_Exception</code>。使用<link linkend="zend.controller.exceptions.handling">前一节</link>描述的方法捕捉异常,然后重定向到一个一般性的错误页面或者主页。
  112. </para>
  113. </listitem>
  114. </itemizedlist>
  115. </listitem>
  116. <listitem>
  117. <para>
  118. 如果由于动作不存在而无法分发,<code>Zend_Controller_Action::__call()</code> 将会抛出一个<code>Zend_Controller_Action_Exception</code>异常。很有可能,像这些例子一样,你会在控制器中调用默认的动作。这些方法包括:
  119. </para>
  120. <itemizedlist>
  121. <listitem>
  122. <para>
  123. 子类化<code>Zend_Controller_Action</code>并重写<code>__call()</code> 方法。例如:
  124. </para>
  125. <programlisting role="php"><![CDATA[
  126. class My_Controller_Action extends Zend_Controller_Action
  127. {
  128. public function __call($method, $args)
  129. {
  130. if ('Action' == substr($method, -6)) {
  131. $controller = $this->getRequest()->getControllerName();
  132. $url = '/' . $controller . '/index';
  133. return $this->_redirect($url);
  134. }
  135. throw new Exception('Invalid method');
  136. }
  137. }
  138. ]]>
  139. </programlisting>
  140. <para>
  141. 上面的例子拦截所有未定义的动作调用,并重定向到控制器中的默认动作。
  142. </para>
  143. </listitem>
  144. <listitem>
  145. <para>
  146. 子类化<code>Zend_Controller_Dispatcher</code> 并重写<code>getAction()</code> 方法来验证动作的存在。例如:
  147. </para>
  148. <programlisting role="php"><![CDATA[
  149. class My_Controller_Dispatcher extends Zend_Controller_Dispatcher
  150. {
  151. public function getAction($request)
  152. {
  153. $action = $request->getActionName();
  154. if (empty($action)) {
  155. $action = $this->getDefaultAction();
  156. $request->setActionName($action);
  157. $action = $this->formatActionName($action);
  158. } else {
  159. $controller = $this->getController();
  160. $action = $this->formatActionName($action);
  161. if (!method_exists($controller, $action)) {
  162. $action = $this->getDefaultAction();
  163. $request->setActionName($action);
  164. $action = $this->formatActionName($action);
  165. }
  166. }
  167. return $action;
  168. }
  169. }
  170. ]]>
  171. </programlisting>
  172. <para>
  173. 上面的代码检查请求的动作在控制类中是否存在,不存在的话,将动作重置为默认动作。
  174. </para>
  175. <para>
  176. 这个方法好在你可以在最终分发前透明的改变动作。然而,同样意味着URL中的拼写错误会导致不正确的分发,这对搜索引擎的优化很不利。
  177. </para>
  178. </listitem>
  179. <listitem>
  180. <para>
  181. 使用<code>Zend_Controller_Action::preDispatch()</code>或者<code>Zend_Controller_Plugin_Abstract::preDispatch()</code>来识别无效的动作。
  182. </para>
  183. <para>
  184. 通过子类化<code>Zend_Controller_Action</code>并修改<code>preDispatch()</code>方法,你可以修改所有的控制器转向另一个动作,或者在实际分发动作之前重定向。代码看起来与上面重写<code>__call()</code>方法类似。
  185. </para>
  186. <para>
  187. 也可以选择在一个全局插件中检查该信息。其优点在于保持动作控制器的独立性;如果程序由大量的动作控制器组成,并且不是所有的动作控制器都从同一类继承,这种方法可以统一的控制各个类。
  188. </para>
  189. <para>
  190. 例如:
  191. </para>
  192. <programlisting role="php"><![CDATA[
  193. class My_Controller_PreDispatchPlugin extends Zend_Controller_Plugin_Abstract
  194. {
  195. public function preDispatch(Zend_Controller_Request_Abstract $request)
  196. {
  197. $dispatcher =
  198. Zend_Controller_Front::getInstance()->getDispatcher();
  199. $controller = $dispatcher->getController($request);
  200. if (!$controller) {
  201. $controller =
  202. $dispatcher->getDefaultControllerName($request);
  203. }
  204. $action = $dispatcher->getAction($request);
  205. if (!method_exists($controller, $action)) {
  206. $defaultAction = $dispatcher->getDefaultAction();
  207. $controllerName = $request->getControllerName();
  208. $response =
  209. Zend_Controller_Front::getInstance()->getResponse();
  210. $response->setRedirect('/' . $controllerName .
  211. '/' . $defaultAction);
  212. $response->sendHeaders();
  213. exit;
  214. }
  215. }
  216. }
  217. ]]>
  218. </programlisting>
  219. <para>
  220. 这个例子中,先检查请求的动作在控制器中是否有效。如果无效,重定向到控制器默认的动作,并立即退出脚本的执行。
  221. </para>
  222. </listitem>
  223. </itemizedlist>
  224. </listitem>
  225. </itemizedlist>
  226. </sect2>
  227. </sect1>
  228. <!--
  229. vim:se ts=4 sw=4 et:
  230. -->