1 <?xml version="1.0" encoding="UTF-8"?>
3 <sect1 id="zend.controller.exceptions">
4 <title>MVC Exceptions</title>
6 <sect2 id="zend.controller.exceptions.introduction">
7 <title>Introduction</title>
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
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.
27 <sect2 id="zend.controller.exceptions.handling">
28 <title>Handling Exceptions</title>
31 Several mechanisms are built in to the <acronym>MVC</acronym> components already to
32 allow you to handle exceptions.
39 linkend="zend.controller.plugins.standard.errorhandler">error
40 handler plugin</link> is registered and active. This plugin
41 was designed to handle:
45 <listitem><para>Errors due to missing controllers or actions</para></listitem>
46 <listitem><para>Errors occurring within action controllers</para></listitem>
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
57 This handler will cover most exceptional situations, and
58 handle missing controllers and actions gracefully.
63 <para><methodname>Zend_Controller_Front::throwExceptions()</methodname></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:
72 <programlisting language="php"><![CDATA[
73 $front->throwExceptions(true);
76 } catch (Exception $e) {
77 // handle exceptions yourself
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.
90 <methodname>Zend_Controller_Response_Abstract::renderExceptions()</methodname>
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.
104 <methodname>Zend_Controller_Front::returnResponse()</methodname> and
105 <methodname>Zend_Controller_Response_Abstract::isException()</methodname>.
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
119 <programlisting language="php"><![CDATA[
120 $front->returnResponse(true);
121 $response = $front->dispatch();
122 if ($response->isException()) {
123 $exceptions = $response->getException();
124 // handle exceptions ...
126 $response->sendHeaders();
127 $response->outputBody();
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.
142 <sect2 id="zend.controller.exceptions.internal">
143 <title>MVC Exceptions You May Encounter</title>
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.
153 <para>As some examples:</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
167 Set the <property>useDefaultControllerAlways</property> parameter.
171 In your front controller, or your dispatcher, add
172 the following directive:
175 <programlisting language="php"><![CDATA[
176 $front->setParam('useDefaultControllerAlways', true);
180 $dispatcher->setParam('useDefaultControllerAlways', true);
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.
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
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:
220 Subclass <classname>Zend_Controller_Action</classname> and
221 override the <methodname>__call()</methodname> method. As an
225 <programlisting language="php"><![CDATA[
226 class My_Controller_Action extends Zend_Controller_Action
228 public function __call($method, $args)
230 if ('Action' == substr($method, -6)) {
231 $controller = $this->getRequest()->getControllerName();
232 $url = '/' . $controller . '/index';
233 return $this->_redirect($url);
236 throw new Exception('Invalid method');
242 The example above intercepts any undefined action
243 method called and redirects it to the default action
250 Subclass <classname>Zend_Controller_Dispatcher</classname>
251 and override the <methodname>getAction()</methodname> method to
252 verify the action exists. As an example:
255 <programlisting language="php"><![CDATA[
256 class My_Controller_Dispatcher extends Zend_Controller_Dispatcher
258 public function getAction($request)
260 $action = $request->getActionName();
261 if (empty($action)) {
262 $action = $this->getDefaultAction();
263 $request->setActionName($action);
264 $action = $this->formatActionName($action);
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);
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.
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
298 <methodname>Zend_Controller_Action::preDispatch()</methodname>
300 <methodname>Zend_Controller_Plugin_Abstract::preDispatch()</methodname>
301 to identify invalid actions.
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.
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
328 <programlisting language="php"><![CDATA[
329 class My_Controller_PreDispatchPlugin extends Zend_Controller_Plugin_Abstract
331 public function preDispatch(Zend_Controller_Request_Abstract $request)
333 $front = Zend_Controller_Front::getInstance();
334 $dispatcher = $front->getDispatcher();
335 $class = $dispatcher->getControllerClass($request);
337 $class = $dispatcher->getDefaultControllerClass($request);
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();
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.