Zend_Controller-Exceptions.xml 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!-- EN-Revision: 24249 -->
  3. <!-- Reviewed: no -->
  4. <sect1 id="zend.controller.exceptions">
  5. <title>Exceptions avec MVC</title>
  6. <sect2 id="zend.controller.exceptions.introduction">
  7. <title>Introduction</title>
  8. <para>
  9. Les composants <acronym>MVC</acronym> de Zend Framework utilisent un contrôleur frontal, ce qui veut
  10. dire que toute requête envoyée à l'application entre par ce point unique. Ainsi, toutes
  11. les exceptions sont encapsulées dans le contrôleur frontal, ceci vous permet de toutes
  12. les traiter dans un seul endroit.
  13. </para>
  14. <para>
  15. Cependant, les exceptions peuvent contenir des messages ou des traces plutôt
  16. sensibles pour le système, comme des requêtes <acronym>SQL</acronym>, l'emplacement de certains fichiers
  17. ... Pour vous aider à protéger votre site, par défaut,
  18. <classname>Zend_Controller_Front</classname> attrape toutes les exceptions et les
  19. enregistre dans l'objet de réponse ; et bien entendu, par défaut, cet objet de réponse
  20. n'affiche pas ces exceptions.
  21. </para>
  22. </sect2>
  23. <sect2 id="zend.controller.exceptions.handling">
  24. <title>Gestion des exceptions</title>
  25. <para>
  26. Plusieurs mécanismes vont vous permettre de traiter les exceptions dans le modèle
  27. <acronym>MVC</acronym> de Zend Framework.
  28. </para>
  29. <itemizedlist>
  30. <listitem>
  31. <para>
  32. Par défaut, le plugin
  33. <link linkend="zend.controller.plugins.standard.errorhandler">error
  34. handler</link>est présent, et activé. Ce plugin a été conçu pour gérer&#160;:
  35. </para>
  36. <itemizedlist>
  37. <listitem>
  38. <para>Les erreurs d'absence de contrôleurs ou d'actions</para>
  39. </listitem>
  40. <listitem>
  41. <para>Erreurs survenant dans un contrôleur</para>
  42. </listitem>
  43. </itemizedlist>
  44. <para>
  45. <code>ErrorHandler</code> agit dans le <methodname>postDispatch()</methodname>, et
  46. analyse si une exception a été levée (en gérant son type). Si c'est le cas,
  47. alors le plugin renvoie un jeton vers un contrôleur de gestion des
  48. erreurs.
  49. </para>
  50. <para>
  51. Ce contrôleur couvrira la majorité des cas d'utilisation. Il parvient à
  52. gérer les cas "contrôleur absent", "action absente", ou "autre cas".
  53. </para>
  54. </listitem>
  55. <listitem>
  56. <para><methodname>Zend_Controller_Front::throwExceptions()</methodname></para>
  57. <para>
  58. En passant la valeur <constant>TRUE</constant> à cette méthode, vous indiquez au
  59. contrôleur frontal que vous souhaitez qu'il vous retourne les exceptions qu'il
  60. rencontre. Ainsi, il ne les ajoutera pas à la réponse, et il ne fera pas
  61. intervenir le plugin "<code>Error handler</code>". Exemple&#160;:
  62. </para>
  63. <programlisting language="php"><![CDATA[
  64. $front->throwExceptions(true);
  65. try {
  66. $front->dispatch();
  67. } catch (Exception $e) {
  68. // A vous de gérer ici
  69. }
  70. ]]></programlisting>
  71. <para>
  72. Cette méthode vous permet d'utiliser une gestion personnalisée des
  73. exceptions dans votre application, de manière simple.
  74. </para>
  75. </listitem>
  76. <listitem>
  77. <para>
  78. <methodname>Zend_Controller_Response_Abstract::renderExceptions()</methodname>
  79. </para>
  80. <para>
  81. En passant un paramètre <constant>TRUE</constant> à cette méthode, vous indiquez
  82. à la réponse d'afficher les exceptions qu'elle reçoit (du contrôleur frontal,
  83. ou du plugin "<code>Error handler</code>", par exemple), lorsque son rendu est
  84. appelé. Ceci ne devrait être activé qu'en environnement de développement.
  85. </para>
  86. </listitem>
  87. <listitem>
  88. <para>
  89. <methodname>Zend_Controller_Front::returnResponse()</methodname> et
  90. <methodname>Zend_Controller_Response_Abstract::isException()</methodname>.
  91. </para>
  92. <para>
  93. En passant le booléen <constant>TRUE</constant> à
  94. <methodname>Zend_Controller_Front::returnResponse()</methodname>,
  95. <methodname>Zend_Controller_Front::dispatch()</methodname> ne commandera pas
  96. l'affichage de la réponse automatiquement. Au lieu de cela, l'objet de réponse
  97. sera retourné. Vous pouvez alors tester celui-ci pour voir s'il contient des
  98. exceptions, ceci grâce à <methodname>isException()</methodname> et
  99. <methodname>getException()</methodname>. Voyez&#160;:
  100. </para>
  101. <programlisting language="php"><![CDATA[
  102. $front->returnResponse(true);
  103. $response = $front->dispatch();
  104. if ($response->isException()) {
  105. $exceptions = $response->getException();
  106. // Gestion des exceptions ici
  107. } else {
  108. $response->sendHeaders();
  109. $response->outputBody();
  110. }
  111. ]]></programlisting>
  112. <para>
  113. Par rapport à
  114. <methodname>Zend_Controller_Front::throwExceptions()</methodname>, cette
  115. utilisation vous permet de ne rendre la réponse que lorsque vous le décidez,
  116. selon la présence de telle ou telle exception, ou pas.
  117. </para>
  118. </listitem>
  119. </itemizedlist>
  120. </sect2>
  121. <sect2 id="zend.controller.exceptions.internal">
  122. <title>Différents types d'exceptions que vous pouvez rencontrer</title>
  123. <para>
  124. Les composants <acronym>MVC</acronym> sont nombreux, - requête, routeur, distributeur, contrôleur,
  125. et réponse - chaque objet risque de renvoyer une exception qui lui est propre.
  126. Certaines peuvent être créées ou dérivées, d'autres par défaut indiquent un problème de
  127. l'application.
  128. </para>
  129. <para>Comme exemples :</para>
  130. <itemizedlist>
  131. <listitem>
  132. <para>
  133. <methodname>Zend_Controller_Dispatcher::dispatch()</methodname> va envoyer
  134. une exception, par défaut, si un contrôleur invalide est demandé. Vous pouvez
  135. jouer sur ce paramètre&#160;:
  136. </para>
  137. <itemizedlist>
  138. <listitem>
  139. <para>
  140. Initialisez le paramètre
  141. <code>useDefaultControllerAlways</code>
  142. </para>
  143. <para>
  144. Dans votre contrôleur frontal, ou distributeur, ajoutez la
  145. directive suivante&#160;:
  146. </para>
  147. <programlisting language="php"><![CDATA[
  148. $front->setParam('useDefaultControllerAlways', true);
  149. // ou
  150. $dispatcher->setParam('useDefaultControllerAlways', true);
  151. ]]></programlisting>
  152. <para>
  153. Lorsque ceci est injecté, le distributeur utilisera le contrôleur
  154. par défaut s'il s'aperçoit qu'il ne peut distribuer un contrôleur
  155. spécifique, plutôt que de renvoyer une exception. Méfiez vous des
  156. moteurs de recherche qui n'aiment pas que plusieurs <acronym>URI</acronym> pointent sur un
  157. même contenu. En effet, avec ce paramètre activé, les utilisateurs
  158. orthographiant mal votre site, seront redirigés vers la page d'accueil
  159. de celui-ci, ce qui peut aboutir à du "duplicate content" (contenu
  160. dupliqué).
  161. </para>
  162. </listitem>
  163. <listitem>
  164. <para>
  165. L'exception envoyée par <methodname>dispatch()</methodname> est de type
  166. <classname>Zend_Controller_Dispatcher_Exception</classname> et contient
  167. le message "Invalid controller specified". Utilisez une méthode comme
  168. vu dans la
  169. <link linkend="zend.controller.exceptions.handling">section
  170. précédente</link>pour attraper celle-ci et rediriger vers une page
  171. d'erreur générique.
  172. </para>
  173. </listitem>
  174. </itemizedlist>
  175. </listitem>
  176. <listitem>
  177. <para>
  178. <methodname>Zend_Controller_Action::__call()</methodname> enverra une
  179. <classname>Zend_Controller_Action_Exception</classname> s'il n'est pas possible
  180. de distribuer l'action demandée. Il est facile de changer ce
  181. comportement&#160;:
  182. </para>
  183. <itemizedlist>
  184. <listitem>
  185. <para>
  186. Dérivez la classe <classname>Zend_Controller_Action</classname>
  187. en redéfinissant sa méthode <methodname>__call()</methodname>, voyez plutôt&#160;:
  188. </para>
  189. <programlisting language="php"><![CDATA[
  190. class My_Controller_Action extends Zend_Controller_Action
  191. {
  192. public function __call($method, $args)
  193. {
  194. if ('Action' == substr($method, -6)) {
  195. $controller = $this->getRequest()->getControllerName();
  196. $url = '/' . $controller . '/index';
  197. return $this->_redirect($url);
  198. }
  199. throw new Exception('Invalid method');
  200. }
  201. }
  202. ]]></programlisting>
  203. <para>
  204. Cet exemple intercepte les actions non existantes, et redirige
  205. vers l'action principale du contrôleur actuel.
  206. </para>
  207. </listitem>
  208. <listitem>
  209. <para>
  210. Dérivez <classname>Zend_Controller_Dispatcher</classname> et
  211. redéfinissez <methodname>getAction()</methodname> pour vérifier si l'action existe
  212. bien&#160;:
  213. </para>
  214. <programlisting language="php"><![CDATA[
  215. class My_Controller_Dispatcher extends Zend_Controller_Dispatcher
  216. {
  217. public function getAction($request)
  218. {
  219. $action = $request->getActionName();
  220. if (empty($action)) {
  221. $action = $this->getDefaultAction();
  222. $request->setActionName($action);
  223. $action = $this->formatActionName($action);
  224. } else {
  225. $controller = $this->getController();
  226. $action = $this->formatActionName($action);
  227. if (!method_exists($controller, $action)) {
  228. $action = $this->getDefaultAction();
  229. $request->setActionName($action);
  230. $action = $this->formatActionName($action);
  231. }
  232. }
  233. return $action;
  234. }
  235. }
  236. ]]></programlisting>
  237. <para>
  238. L'exemple précédant vérifie si l'action existe dans le contrôleur
  239. demandé. Si ce n'est pas le cas, il redéfinit l'action en spécifiant
  240. celle par défaut.
  241. </para>
  242. <para>
  243. Cette méthode permet de changer l'action avant la distribution.
  244. Attention une fois encore aux erreurs de syntaxes dans l'URL, qui
  245. devraient mener vers une page d'erreur quelconque.
  246. </para>
  247. </listitem>
  248. <listitem>
  249. <para>
  250. Utilisez
  251. <methodname>Zend_Controller_Action::preDispatch()</methodname> ou
  252. <methodname>Zend_Controller_Plugin_Abstract::preDispatch()</methodname>
  253. pour identifier les actions invalides.
  254. </para>
  255. <para>
  256. En dérivant <classname>Zend_Controller_Action</classname> pour y
  257. modifier <methodname>preDispatch()</methodname>, vous agissez sur la globalité de
  258. vos contrôleurs, avant même la distribution de l'action
  259. demandée.
  260. </para>
  261. <para>
  262. L'utilisation d'un plugin offre une flexibilité supplémentaire :
  263. Si tous vos contrôleurs n'héritent pas de la même classe, plutôt que de
  264. dupliquer du code, un plugin va agir indépendamment de vos contrôleurs.
  265. En <methodname>preDispatch()</methodname>, il agit avant ceux-ci.
  266. </para>
  267. <para>Par exemple :</para>
  268. <programlisting language="php"><![CDATA[
  269. class My_Controller_PreDispatchPlugin
  270. extends Zend_Controller_Plugin_Abstract
  271. {
  272. public function preDispatch(Zend_Controller_Request_Abstract $request)
  273. {
  274. $front = Zend_Controller_Front::getInstance();
  275. $dispatcher = $front->getDispatcher();
  276. $class = $dispatcher->getControllerClass($request);
  277. if (!$class) {
  278. $class = $dispatcher->getDefaultControllerClass($request);
  279. }
  280. $r = new ReflectionClass($class);
  281. $action = $dispatcher->getActionMethod($request);
  282. if (!$r->hasMethod($action)) {
  283. $defaultAction = $dispatcher->getDefaultAction();
  284. $controllerName = $request->getControllerName();
  285. $response = $front->getResponse();
  286. $response->setRedirect('/' . $controllerName
  287. . '/' . $defaultAction);
  288. $response->sendHeaders();
  289. exit;
  290. }
  291. }
  292. }
  293. ]]></programlisting>
  294. <para>
  295. Dans cet exemple, nous vérifions si l'action demandée existe dans
  296. le contrôleur distribué. Si ce n'est pas le cas, nous exécutons une
  297. redirection immédiate.
  298. </para>
  299. </listitem>
  300. </itemizedlist>
  301. </listitem>
  302. </itemizedlist>
  303. </sect2>
  304. </sect1>