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