[ZF-10089] Zend_Log
[zend.git] / documentation / manual / en / module_specs / Zend_Controller-Exceptions.xml
blobf8ff24ef9f729b72db4875ac8b7a224acdc2d019
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- Reviewed: no -->
3 <sect1 id="zend.controller.exceptions">
4     <title>MVC Exceptions</title>
6     <sect2 id="zend.controller.exceptions.introduction">
7         <title>Introduction</title>
9         <para>
10             The <acronym>MVC</acronym> components in Zend Framework utilize a Front Controller,
11             which means that all requests to a given site will go through a
12             single entry point. As a result, all exceptions bubble up to the
13             Front Controller eventually, allowing the developer to handle them
14             in a single location.
15         </para>
17         <para>
18             However, exception messages and backtrace information often contain
19             sensitive system information, such as <acronym>SQL</acronym> statements, file
20             locations, and more. To help protect your site, by default
21             <classname>Zend_Controller_Front</classname> catches all exceptions and
22             registers them with the response object; in turn, by default, the
23             response object does not display exception messages.
24         </para>
25     </sect2>
27     <sect2 id="zend.controller.exceptions.handling">
28         <title>Handling Exceptions</title>
30         <para>
31             Several mechanisms are built in to the <acronym>MVC</acronym> components already to
32             allow you to handle exceptions.
33         </para>
35         <itemizedlist>
36             <listitem>
37                 <para>
38                     By default, the <link
39                         linkend="zend.controller.plugins.standard.errorhandler">error
40                     handler plugin</link> is registered and active. This plugin
41                     was designed to handle:
42                 </para>
44                 <itemizedlist>
45                     <listitem><para>Errors due to missing controllers or actions</para></listitem>
46                     <listitem><para>Errors occurring within action controllers</para></listitem>
47                 </itemizedlist>
49                 <para>
50                     It operates as a <methodname>postDispatch()</methodname> plugin, and
51                     checks to see if a dispatcher, action controller, or
52                     other exception has occurred. If so, it forwards to an error
53                     handler controller.
54                 </para>
56                 <para>
57                     This handler will cover most exceptional situations, and
58                     handle missing controllers and actions gracefully.
59                 </para>
60             </listitem>
62             <listitem>
63                 <para><methodname>Zend_Controller_Front::throwExceptions()</methodname></para>
65                 <para>
66                     By passing a boolean <constant>TRUE</constant> value to this method, you can
67                     tell the front controller that instead of aggregating exceptions
68                     in the response object or using the error handler plugin,
69                     you'd rather handle them yourself. As an example:
70                 </para>
72                 <programlisting language="php"><![CDATA[
73 $front->throwExceptions(true);
74 try {
75     $front->dispatch();
76 } catch (Exception $e) {
77     // handle exceptions yourself
79 ]]></programlisting>
81                 <para>
82                     This method is probably the easiest way to add custom
83                     exception handling covering the full range of possible
84                     exceptions to your front controller application.
85                 </para>
86             </listitem>
88             <listitem>
89                 <para>
90                     <methodname>Zend_Controller_Response_Abstract::renderExceptions()</methodname>
91                 </para>
93                 <para>
94                     By passing a boolean <constant>TRUE</constant> value to this method, you tell
95                     the response object that it should render an exception message
96                     and backtrace when rendering itself. In this scenario, any
97                     exception raised by your application will be displayed. This
98                     is only recommended for non-production environments.
99                 </para>
100             </listitem>
102             <listitem>
103                 <para>
104                     <methodname>Zend_Controller_Front::returnResponse()</methodname> and
105                     <methodname>Zend_Controller_Response_Abstract::isException()</methodname>.
106                 </para>
108                 <para>
109                     By passing a boolean <constant>TRUE</constant> to
110                     <methodname>Zend_Controller_Front::returnResponse()</methodname>,
111                     <methodname>Zend_Controller_Front::dispatch()</methodname> will not render the
112                     response, but instead return it. Once you have the response,
113                     you may then test to see if any exceptions were trapped using
114                     its <methodname>isException()</methodname> method, and retrieving the
115                     exceptions via the <methodname>getException()</methodname> method. As an
116                     example:
117                 </para>
119                 <programlisting language="php"><![CDATA[
120 $front->returnResponse(true);
121 $response = $front->dispatch();
122 if ($response->isException()) {
123     $exceptions = $response->getException();
124     // handle exceptions ...
125 } else {
126     $response->sendHeaders();
127     $response->outputBody();
129 ]]></programlisting>
131                 <para>
132                     The primary advantage this method offers over
133                     <methodname>Zend_Controller_Front::throwExceptions()</methodname> is to
134                     allow you to conditionally render the response after
135                     handling the exception. This will catch any exception in the
136                     controller chain, unlike the error handler plugin.
137                 </para>
138             </listitem>
139         </itemizedlist>
140     </sect2>
142     <sect2 id="zend.controller.exceptions.internal">
143         <title>MVC Exceptions You May Encounter</title>
145         <para>
146             The various <acronym>MVC</acronym> components -- request, router, dispatcher, action
147             controller, and response objects -- may each throw exceptions on
148             occasion. Some exceptions may be conditionally overridden, and
149             others are used to indicate the developer may need to consider
150             their application structure.
151         </para>
153         <para>As some examples:</para>
155         <itemizedlist>
156             <listitem>
157                 <para>
158                     <methodname>Zend_Controller_Dispatcher::dispatch()</methodname> will,
159                     by default, throw an exception if an invalid controller is
160                     requested. There are two recommended ways to deal with
161                     this.
162                 </para>
164                 <itemizedlist>
165                     <listitem>
166                         <para>
167                             Set the <property>useDefaultControllerAlways</property> parameter.
168                         </para>
170                         <para>
171                             In your front controller, or your dispatcher, add
172                             the following directive:
173                         </para>
175                         <programlisting language="php"><![CDATA[
176 $front->setParam('useDefaultControllerAlways', true);
178 // or
180 $dispatcher->setParam('useDefaultControllerAlways', true);
181 ]]></programlisting>
183                         <para>
184                             When this flag is set, the dispatcher will use the
185                             default controller and action instead of throwing an
186                             exception. The disadvantage to this method is that
187                             any typos a user makes when accessing your site will
188                             still resolve and display your home page, which can
189                             wreak havoc with search engine optimization.
190                         </para>
191                     </listitem>
193                     <listitem>
194                         <para>
195                             The exception thrown by <methodname>dispatch()</methodname> is
196                             a <classname>Zend_Controller_Dispatcher_Exception</classname>
197                             containing the text 'Invalid controller specified'.
198                             Use one of the methods outlined in <link
199                                 linkend="zend.controller.exceptions.handling">the
200                                 previous section</link> to catch the exception,
201                             and then redirect to a generic error page or the
202                             home page.
203                         </para>
204                     </listitem>
205                 </itemizedlist>
206             </listitem>
208             <listitem>
209                 <para>
210                     <methodname>Zend_Controller_Action::__call()</methodname> will throw a
211                     <classname>Zend_Controller_Action_Exception</classname> if it cannot
212                     dispatch a non-existent action to a method. Most likely,
213                     you will want to use some default action in the controller
214                     in cases like this. Ways to achieve this include:
215                 </para>
217                 <itemizedlist>
218                     <listitem>
219                         <para>
220                             Subclass <classname>Zend_Controller_Action</classname> and
221                             override the <methodname>__call()</methodname> method. As an
222                             example:
223                         </para>
225                         <programlisting language="php"><![CDATA[
226 class My_Controller_Action extends Zend_Controller_Action
228     public function __call($method, $args)
229     {
230         if ('Action' == substr($method, -6)) {
231             $controller = $this->getRequest()->getControllerName();
232             $url = '/' . $controller . '/index';
233             return $this->_redirect($url);
234         }
236         throw new Exception('Invalid method');
237     }
239 ]]></programlisting>
241                         <para>
242                             The example above intercepts any undefined action
243                             method called and redirects it to the default action
244                             in the controller.
245                         </para>
246                     </listitem>
248                     <listitem>
249                         <para>
250                             Subclass <classname>Zend_Controller_Dispatcher</classname>
251                             and override the <methodname>getAction()</methodname> method to
252                             verify the action exists. As an example:
253                         </para>
255                         <programlisting language="php"><![CDATA[
256 class My_Controller_Dispatcher extends Zend_Controller_Dispatcher
258     public function getAction($request)
259     {
260         $action = $request->getActionName();
261         if (empty($action)) {
262             $action = $this->getDefaultAction();
263             $request->setActionName($action);
264             $action = $this->formatActionName($action);
265         } else {
266             $controller = $this->getController();
267             $action     = $this->formatActionName($action);
268             if (!method_exists($controller, $action)) {
269                 $action = $this->getDefaultAction();
270                 $request->setActionName($action);
271                 $action = $this->formatActionName($action);
272             }
273         }
275         return $action;
276     }
278 ]]></programlisting>
280                         <para>
281                             The above code checks to see that the requested
282                             action exists in the controller class; if not, it
283                             resets the action to the default action.
284                         </para>
286                         <para>
287                             This method is nice because you can transparently
288                             alter the action prior to final dispatch. However,
289                             it also means that typos in the <acronym>URL</acronym> may still
290                             dispatch correctly, which is not great for search
291                             engine optimization.
292                         </para>
293                     </listitem>
295                     <listitem>
296                         <para>
297                             Use
298                             <methodname>Zend_Controller_Action::preDispatch()</methodname>
299                             or
300                             <methodname>Zend_Controller_Plugin_Abstract::preDispatch()</methodname>
301                             to identify invalid actions.
302                         </para>
304                         <para>
305                             By subclassing <classname>Zend_Controller_Action</classname>
306                             and modifying <methodname>preDispatch()</methodname>, you can
307                             modify all of your controllers to forward to
308                             another action or redirect prior to actually
309                             dispatching the action. The code for this will look
310                             similar to the code for overriding
311                             <methodname>__call()</methodname>, above.
312                         </para>
314                         <para>
315                             Alternatively, you can check this information in a
316                             global plugin. This has the advantage of being
317                             action controller independent; if your application
318                             consists of a variety of action controllers, and not
319                             all of them inherit from the same class, this method
320                             can add consistency in handling your various
321                             classes.
322                         </para>
324                         <para>
325                             As an example:
326                         </para>
328                         <programlisting language="php"><![CDATA[
329 class My_Controller_PreDispatchPlugin extends Zend_Controller_Plugin_Abstract
331     public function preDispatch(Zend_Controller_Request_Abstract $request)
332     {
333         $front      = Zend_Controller_Front::getInstance();
334         $dispatcher = $front->getDispatcher();
335         $class      = $dispatcher->getControllerClass($request);
336         if (!$class) {
337             $class = $dispatcher->getDefaultControllerClass($request);
338         }
340         $r      = new ReflectionClass($class);
341         $action = $dispatcher->getActionMethod($request);
343         if (!$r->hasMethod($action)) {
344             $defaultAction  = $dispatcher->getDefaultAction();
345             $controllerName = $request->getControllerName();
346             $response       = $front->getResponse();
347             $response->setRedirect('/' . $controllerName
348                                   . '/' . $defaultAction);
349             $response->sendHeaders();
350             exit;
351         }
352     }
354 ]]></programlisting>
356                         <para>
357                             In this example, we check to see if the action
358                             requested is available in the controller. If not, we
359                             redirect to the default action in the controller,
360                             and exit script execution immediately.
361                         </para>
362                     </listitem>
363                 </itemizedlist>
364             </listitem>
365         </itemizedlist>
366     </sect2>
367 </sect1>
368 <!--
369 vim:se ts=4 sw=4 et: