| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- Reviewed: no -->
- <sect1 id="zend.controller.exceptions">
- <title>MVC Exceptions</title>
- <sect2 id="zend.controller.exceptions.introduction">
- <title>Introduction</title>
- <para>
- The <acronym>MVC</acronym> components in Zend Framework utilize a Front Controller,
- which means that all requests to a given site will go through a
- single entry point. As a result, all exceptions bubble up to the
- Front Controller eventually, allowing the developer to handle them
- in a single location.
- </para>
- <para>
- However, exception messages and backtrace information often contain
- sensitive system information, such as <acronym>SQL</acronym> statements, file
- locations, and more. To help protect your site, by default
- <classname>Zend_Controller_Front</classname> catches all exceptions and
- registers them with the response object; in turn, by default, the
- response object does not display exception messages.
- </para>
- </sect2>
- <sect2 id="zend.controller.exceptions.handling">
- <title>Handling Exceptions</title>
- <para>
- Several mechanisms are built in to the <acronym>MVC</acronym> components already to
- allow you to handle exceptions.
- </para>
- <itemizedlist>
- <listitem>
- <para>
- By default, the <link
- linkend="zend.controller.plugins.standard.errorhandler">error
- handler plugin</link> is registered and active. This plugin
- was designed to handle:
- </para>
- <itemizedlist>
- <listitem><para>Errors due to missing controllers or actions</para></listitem>
- <listitem><para>Errors occurring within action controllers</para></listitem>
- </itemizedlist>
- <para>
- It operates as a <methodname>postDispatch()</methodname> plugin, and
- checks to see if a dispatcher, action controller, or
- other exception has occurred. If so, it forwards to an error
- handler controller.
- </para>
- <para>
- This handler will cover most exceptional situations, and
- handle missing controllers and actions gracefully.
- </para>
- </listitem>
- <listitem>
- <para><methodname>Zend_Controller_Front::throwExceptions()</methodname></para>
- <para>
- By passing a boolean <constant>TRUE</constant> value to this method, you can
- tell the front controller that instead of aggregating exceptions
- in the response object or using the error handler plugin,
- you'd rather handle them yourself. As an example:
- </para>
- <programlisting language="php"><![CDATA[
- $front->throwExceptions(true);
- try {
- $front->dispatch();
- } catch (Exception $e) {
- // handle exceptions yourself
- }
- ]]></programlisting>
- <para>
- This method is probably the easiest way to add custom
- exception handling covering the full range of possible
- exceptions to your front controller application.
- </para>
- </listitem>
- <listitem>
- <para>
- <methodname>Zend_Controller_Response_Abstract::renderExceptions()</methodname>
- </para>
- <para>
- By passing a boolean <constant>TRUE</constant> value to this method, you tell
- the response object that it should render an exception message
- and backtrace when rendering itself. In this scenario, any
- exception raised by your application will be displayed. This
- is only recommended for non-production environments.
- </para>
- </listitem>
- <listitem>
- <para>
- <methodname>Zend_Controller_Front::returnResponse()</methodname> and
- <methodname>Zend_Controller_Response_Abstract::isException()</methodname>.
- </para>
- <para>
- By passing a boolean <constant>TRUE</constant> to
- <methodname>Zend_Controller_Front::returnResponse()</methodname>,
- <methodname>Zend_Controller_Front::dispatch()</methodname> will not render the
- response, but instead return it. Once you have the response,
- you may then test to see if any exceptions were trapped using
- its <methodname>isException()</methodname> method, and retrieving the
- exceptions via the <methodname>getException()</methodname> method. As an
- example:
- </para>
- <programlisting language="php"><![CDATA[
- $front->returnResponse(true);
- $response = $front->dispatch();
- if ($response->isException()) {
- $exceptions = $response->getException();
- // handle exceptions ...
- } else {
- $response->sendHeaders();
- $response->outputBody();
- }
- ]]></programlisting>
- <para>
- The primary advantage this method offers over
- <methodname>Zend_Controller_Front::throwExceptions()</methodname> is to
- allow you to conditionally render the response after
- handling the exception. This will catch any exception in the
- controller chain, unlike the error handler plugin.
- </para>
- </listitem>
- </itemizedlist>
- </sect2>
- <sect2 id="zend.controller.exceptions.internal">
- <title>MVC Exceptions You May Encounter</title>
- <para>
- The various <acronym>MVC</acronym> components -- request, router, dispatcher, action
- controller, and response objects -- may each throw exceptions on
- occasion. Some exceptions may be conditionally overridden, and
- others are used to indicate the developer may need to consider
- their application structure.
- </para>
- <para>As some examples:</para>
- <itemizedlist>
- <listitem>
- <para>
- <methodname>Zend_Controller_Dispatcher::dispatch()</methodname> will,
- by default, throw an exception if an invalid controller is
- requested. There are two recommended ways to deal with
- this.
- </para>
- <itemizedlist>
- <listitem>
- <para>
- Set the <property>useDefaultControllerAlways</property> parameter.
- </para>
- <para>
- In your front controller, or your dispatcher, add
- the following directive:
- </para>
- <programlisting language="php"><![CDATA[
- $front->setParam('useDefaultControllerAlways', true);
- // or
- $dispatcher->setParam('useDefaultControllerAlways', true);
- ]]></programlisting>
- <para>
- When this flag is set, the dispatcher will use the
- default controller and action instead of throwing an
- exception. The disadvantage to this method is that
- any typos a user makes when accessing your site will
- still resolve and display your home page, which can
- wreak havoc with search engine optimization.
- </para>
- </listitem>
- <listitem>
- <para>
- The exception thrown by <methodname>dispatch()</methodname> is
- a <classname>Zend_Controller_Dispatcher_Exception</classname>
- containing the text 'Invalid controller specified'.
- Use one of the methods outlined in <link
- linkend="zend.controller.exceptions.handling">the
- previous section</link> to catch the exception,
- and then redirect to a generic error page or the
- home page.
- </para>
- </listitem>
- </itemizedlist>
- </listitem>
- <listitem>
- <para>
- <methodname>Zend_Controller_Action::__call()</methodname> will throw a
- <classname>Zend_Controller_Action_Exception</classname> if it cannot
- dispatch a non-existent action to a method. Most likely,
- you will want to use some default action in the controller
- in cases like this. Ways to achieve this include:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- Subclass <classname>Zend_Controller_Action</classname> and
- override the <methodname>__call()</methodname> method. As an
- example:
- </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>
- The example above intercepts any undefined action
- method called and redirects it to the default action
- in the controller.
- </para>
- </listitem>
- <listitem>
- <para>
- Subclass <classname>Zend_Controller_Dispatcher</classname>
- and override the <methodname>getAction()</methodname> method to
- verify the action exists. As an example:
- </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>
- The above code checks to see that the requested
- action exists in the controller class; if not, it
- resets the action to the default action.
- </para>
- <para>
- This method is nice because you can transparently
- alter the action prior to final dispatch. However,
- it also means that typos in the <acronym>URL</acronym> may still
- dispatch correctly, which is not great for search
- engine optimization.
- </para>
- </listitem>
- <listitem>
- <para>
- Use
- <methodname>Zend_Controller_Action::preDispatch()</methodname>
- or
- <methodname>Zend_Controller_Plugin_Abstract::preDispatch()</methodname>
- to identify invalid actions.
- </para>
- <para>
- By subclassing <classname>Zend_Controller_Action</classname>
- and modifying <methodname>preDispatch()</methodname>, you can
- modify all of your controllers to forward to
- another action or redirect prior to actually
- dispatching the action. The code for this will look
- similar to the code for overriding
- <methodname>__call()</methodname>, above.
- </para>
- <para>
- Alternatively, you can check this information in a
- global plugin. This has the advantage of being
- action controller independent; if your application
- consists of a variety of action controllers, and not
- all of them inherit from the same class, this method
- can add consistency in handling your various
- classes.
- </para>
- <para>
- As an example:
- </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>
- In this example, we check to see if the action
- requested is available in the controller. If not, we
- redirect to the default action in the controller,
- and exit script execution immediately.
- </para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </sect2>
- </sect1>
- <!--
- vim:se ts=4 sw=4 et:
- -->
|