| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- Reviewed: no -->
- <sect1 id="zend.controller.action">
- <title>Action Controllers</title>
- <sect2 id="zend.controller.action.introduction">
- <title>Introduction</title>
- <para>
- <classname>Zend_Controller_Action</classname> is an abstract class you may use
- for implementing Action Controllers for use with the Front
- Controller when building a website based on the
- Model-View-Controller (<acronym>MVC</acronym>) pattern.
- </para>
- <para>
- To use <classname>Zend_Controller_Action</classname>, you will need to
- subclass it in your actual action controller classes (or subclass it
- to create your own base class for action controllers). The most
- basic operation is to subclass it, and create action methods that
- correspond to the various actions you wish the controller to handle
- for your site. <classname>Zend_Controller</classname>'s routing and dispatch handling
- will autodiscover any methods ending in 'Action' in your class as
- potential controller actions.
- </para>
- <para>
- For example, let's say your class is defined as follows:
- </para>
- <programlisting language="php"><![CDATA[
- class FooController extends Zend_Controller_Action
- {
- public function barAction()
- {
- // do something
- }
- public function bazAction()
- {
- // do something
- }
- }
- ]]></programlisting>
- <para>
- The above <emphasis>FooController</emphasis> class (controller
- <emphasis>foo</emphasis>) defines two actions, <emphasis>bar</emphasis> and
- <emphasis>baz</emphasis>.
- </para>
- <para>
- There's much more that can be accomplished than this, such as custom
- initialization actions, default actions to call should no action (or
- an invalid action) be specified, pre- and post-dispatch hooks, and a
- variety of helper methods. This chapter serves as an overview of the
- action controller functionality
- </para>
- <note>
- <title>Default Behaviour</title>
- <para>
- By default, the <link linkend="zend.controller.front">front
- controller</link> enables the <link
- linkend="zend.controller.actionhelpers.viewrenderer">ViewRenderer</link>
- action helper. This helper takes care of injecting the view
- object into the controller, as well as automatically rendering
- views. You may disable it within your action controller via one
- of the following methods:
- </para>
- <programlisting language="php"><![CDATA[
- class FooController extends Zend_Controller_Action
- {
- public function init()
- {
- // Local to this controller only; affects all actions,
- // as loaded in init:
- $this->_helper->viewRenderer->setNoRender(true);
- // Globally:
- $this->_helper->removeHelper('viewRenderer');
- // Also globally, but would need to be in conjunction with the
- // local version in order to propagate for this controller:
- Zend_Controller_Front::getInstance()
- ->setParam('noViewRenderer', true);
- }
- }
- ]]></programlisting>
- <para>
- <methodname>initView()</methodname>, <methodname>getViewScript()</methodname>,
- <methodname>render()</methodname>, and <methodname>renderScript()</methodname> each
- proxy to the <emphasis>ViewRenderer</emphasis> unless the helper is not
- in the helper broker or the <emphasis>noViewRenderer</emphasis> flag has
- been set.
- </para>
- <para>
- You can also simply disable rendering for an individual view by
- setting the <emphasis>ViewRenderer</emphasis>'s <emphasis>noRender</emphasis>
- flag:
- </para>
- <programlisting language="php"><![CDATA[
- class FooController extends Zend_Controller_Action
- {
- public function barAction()
- {
- // disable autorendering for this action only:
- $this->_helper->viewRenderer->setNoRender();
- }
- }
- ]]></programlisting>
- <para>
- The primary reasons to disable the <emphasis>ViewRenderer</emphasis> are
- if you simply do not need a view object or if you are not
- rendering via view scripts (for instance, when using an action
- controller to serve web service protocols such as <acronym>SOAP</acronym>,
- <acronym>XML-RPC</acronym>, or <acronym>REST</acronym>). In most cases, you will
- never need to globally disable the <emphasis>ViewRenderer</emphasis>, only
- selectively within individual controllers or actions.
- </para>
- </note>
- </sect2>
- <sect2 id="zend.controller.action.initialization">
- <title>Object Initialization</title>
- <para>
- While you can always override the action controller's constructor, we
- do not recommend this. <methodname>Zend_Controller_Action::__construct()</methodname>
- performs some important tasks, such as registering the request and
- response objects, as well as any custom invocation arguments passed
- in from the front controller. If you must override the constructor,
- be sure to call <methodname>parent::__construct($request, $response,
- $invokeArgs)</methodname>.
- </para>
- <para>
- The more appropriate way to customize instantiation is to use the
- <methodname>init()</methodname> method, which is called as the last task of
- <methodname>__construct()</methodname>. For example, if you want to connect to
- a database at instantiation:
- </para>
- <programlisting language="php"><![CDATA[
- class FooController extends Zend_Controller_Action
- {
- public function init()
- {
- $this->db = Zend_Db::factory('Pdo_Mysql', array(
- 'host' => 'myhost',
- 'username' => 'user',
- 'password' => 'XXXXXXX',
- 'dbname' => 'website'
- ));
- }
- }
- ]]></programlisting>
- </sect2>
- <sect2 id="zend.controller.action.prepostdispatch">
- <title>Pre- and Post-Dispatch Hooks</title>
- <para>
- <classname>Zend_Controller_Action</classname> specifies two methods that may
- be called to bookend a requested action, <methodname>preDispatch()</methodname>
- and <methodname>postDispatch()</methodname>. These can be useful in a variety of
- ways: verifying authentication and <acronym>ACL</acronym>'s prior to running an action
- (by calling <methodname>_forward()</methodname> in
- <methodname>preDispatch()</methodname>, the action will be skipped), for instance, or
- placing generated content in a sitewide template
- (<methodname>postDispatch()</methodname>).
- </para>
- <note>
- <title>Usage of init() vs. preDispatch()</title>
- <para>
- In the <link linkend="zend.controller.action.initialization">previous
- section</link>, we introduced the <methodname>init()</methodname> method, and
- in this section, the <methodname>preDispatch()</methodname> method. What is the
- difference between them, and what actions would you take in each?
- </para>
- <para>
- The <methodname>init()</methodname> method is primarily intended for extending the
- constructor. Typically, your constructor should simply set object state, and not
- perform much logic. This might include initializing resources used in the controller
- (such as models, configuration objects, etc.), or assigning values retrieved from
- the front controller, bootstrap, or a registry.
- </para>
- <para>
- The <methodname>preDispatch()</methodname> method can also be used to set object
- or environmental (e.g., view, action helper, etc.) state, but its primary purpose
- is to make decisions about whether or not the requested action should be dispatched.
- If not, you should then <methodname>_forward()</methodname> to another action, or
- throw an exception.
- </para>
- <para>
- Note: <methodname>_forward()</methodname> actually will not work correctly when
- executed from <methodname>init()</methodname>, which is a formalization of the
- intentions of the two methods.
- </para>
- </note>
- </sect2>
- <sect2 id="zend.controller.action.accessors">
- <title>Accessors</title>
- <para>
- A number of objects and variables are registered with the object,
- and each has accessor methods.
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <emphasis>Request Object</emphasis>: <methodname>getRequest()</methodname>
- may be used to retrieve the request object used to call the action.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>Response Object</emphasis>:
- <methodname>getResponse()</methodname> may be used to retrieve the
- response object aggregating the final response. Some typical
- calls might look like:
- </para>
- <programlisting language="php"><![CDATA[
- $this->getResponse()->setHeader('Content-Type', 'text/xml');
- $this->getResponse()->appendBody($content);
- ]]></programlisting>
- </listitem>
- <listitem>
- <para>
- <emphasis>Invocation Arguments</emphasis>: the front
- controller may push parameters into the router, dispatcher,
- and action controller. To retrieve these, use
- <methodname>getInvokeArg($key)</methodname>; alternatively, fetch the
- entire list using <methodname>getInvokeArgs()</methodname>.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>Request parameters</emphasis>: The request object
- aggregates request parameters, such as any <constant>_GET</constant> or
- <constant>_POST</constant> parameters, or user parameters specified in the
- <acronym>URL</acronym>'s path information. To retrieve these, use
- <methodname>_getParam($key)</methodname> or
- <methodname>_getAllParams()</methodname>. You may also set request
- parameters using <methodname>_setParam()</methodname>; this is useful
- when forwarding to additional actions.
- </para>
- <para>
- To test whether or not a parameter exists (useful for
- logical branching), use <methodname>_hasParam($key)</methodname>.
- </para>
- <note>
- <para>
- <methodname>_getParam()</methodname> may take an optional second
- argument containing a default value to use if the
- parameter is not set or is empty. Using it eliminates
- the need to call <methodname>_hasParam()</methodname> prior to
- retrieving a value:
- </para>
- <programlisting language="php"><![CDATA[
- // Use default value of 1 if id is not set
- $id = $this->_getParam('id', 1);
- // Instead of:
- if ($this->_hasParam('id') {
- $id = $this->_getParam('id');
- } else {
- $id = 1;
- }
- ]]></programlisting>
- </note>
- </listitem>
- </itemizedlist>
- </sect2>
- <sect2 id="zend.controller.action.viewintegration">
- <title>View Integration</title>
- <note id="zend.controller.action.viewintegration.viewrenderer">
- <title>Default View Integration is Via the ViewRenderer</title>
- <para>
- The content in this section is only valid when you have explicitly disabled the
- <link linkend="zend.controller.actionhelpers.viewrenderer">ViewRenderer</link>.
- Otherwise, you can safely skip over this section.
- </para>
- </note>
- <para>
- <classname>Zend_Controller_Action</classname> provides a rudimentary and
- flexible mechanism for view integration. Two methods accomplish
- this, <methodname>initView()</methodname> and <methodname>render()</methodname>; the
- former method lazy-loads the <varname>$view</varname> public property, and the
- latter renders a view based on the current requested action, using
- the directory hierarchy to determine the script path.
- </para>
- <sect3 id="zend.controller.action.viewintegration.initview">
- <title>View Initialization</title>
- <para>
- <methodname>initView()</methodname> initializes the view object.
- <methodname>render()</methodname> calls <methodname>initView()</methodname> in
- order to retrieve the view object, but it may be initialized at any time;
- by default it populates the <varname>$view</varname> property with a
- <classname>Zend_View</classname> object, but any class implementing
- <classname>Zend_View_Interface</classname> may be used. If
- <varname>$view</varname> is already initialized, it simply returns
- that property.
- </para>
- <para>
- The default implementation makes the following assumption of
- the directory structure:
- </para>
- <programlisting language="php"><![CDATA[
- applicationOrModule/
- controllers/
- IndexController.php
- views/
- scripts/
- index/
- index.phtml
- helpers/
- filters/
- ]]></programlisting>
- <para>
- In other words, view scripts are assumed to be in the
- <filename>/views/scripts/</filename> subdirectory, and the
- <filename>/views/</filename> subdirectory is assumed to contain sibling
- functionality (helpers, filters). When determining the view
- script name and path, the <filename>/views/scripts/</filename> directory
- will be used as the base path, with directories named after the
- individual controllers providing a hierarchy of view scripts.
- </para>
- </sect3>
- <sect3 id="zend.controller.action.viewintegration.render">
- <title>Rendering Views</title>
- <para>
- <methodname>render()</methodname> has the following signature:
- </para>
- <programlisting language="php"><![CDATA[
- string render(string $action = null,
- string $name = null,
- bool $noController = false);
- ]]></programlisting>
- <para>
- <methodname>render()</methodname> renders a view script. If no arguments are
- passed, it assumes that the script requested is
- <filename>[controller]/[action].phtml</filename> (where
- <filename>.phtml</filename> is the value of the <varname>$viewSuffix</varname>
- property). Passing a value for <varname>$action</varname> will render
- that template in the <filename>/[controller]/</filename> subdirectory. To
- override using the <filename>/[controller]/</filename> subdirectory, pass
- a <constant>TRUE</constant> value for <varname>$noController</varname>. Finally,
- templates are rendered into the response object; if you wish to render to
- a specific <link
- linkend="zend.controller.response.namedsegments">named
- segment</link> in the response object, pass a value to
- <varname>$name</varname>.
- </para>
- <note>
- <para>
- Since controller and action names may contain word delimiter
- characters such as '_', '.', and '-', <methodname>render()</methodname>
- normalizes these to '-' when determining the script name. Internally,
- it uses the dispatcher's word and path delimiters to do this
- normalization. Thus, a request to
- <filename>/foo.bar/baz-bat</filename> will render the script
- <filename>foo-bar/baz-bat.phtml</filename>. If your action method
- contains camelCasing, please remember that this will result
- in '-' separated words when determining the view script
- file name.
- </para>
- </note>
- <para>
- Some examples:
- </para>
- <programlisting language="php"><![CDATA[
- class MyController extends Zend_Controller_Action
- {
- public function fooAction()
- {
- // Renders my/foo.phtml
- $this->render();
- // Renders my/bar.phtml
- $this->render('bar');
- // Renders baz.phtml
- $this->render('baz', null, true);
- // Renders my/login.phtml to the 'form' segment of the
- // response object
- $this->render('login', 'form');
- // Renders site.phtml to the 'page' segment of the response
- // object; does not use the 'my/' subirectory
- $this->render('site', 'page', true);
- }
- public function bazBatAction()
- {
- // Renders my/baz-bat.phtml
- $this->render();
- }
- }
- ]]></programlisting>
- </sect3>
- </sect2>
- <sect2 id="zend.controller.action.utilmethods">
- <title>Utility Methods</title>
- <para>
- Besides the accessors and view integration methods,
- <classname>Zend_Controller_Action</classname> has several utility methods for
- performing common tasks from within your action methods (or from
- pre- and post-dispatch).
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <methodname>_forward($action, $controller = null, $module = null,
- array $params = null)</methodname>: perform another action. If
- called in <methodname>preDispatch()</methodname>, the currently
- requested action will be skipped in favor of the new one.
- Otherwise, after the current action is processed, the action
- requested in <methodname>_forward()</methodname> will be executed.
- </para>
- </listitem>
- <listitem>
- <para>
- <methodname>_redirect($url, array $options =
- array())</methodname>: redirect to another location. This
- method takes a <acronym>URL</acronym> and an optional set of options. By
- default, it performs an <acronym>HTTP</acronym> 302 redirect.
- </para>
- <para>
- The options may include one or more of the following:
- </para>
- <itemizedlist>
- <listitem>
- <para>
- <emphasis>exit:</emphasis> whether or not to exit
- immediately. If requested, it will cleanly close any
- open sessions and perform the redirect.
- </para>
- <para>
- You may set this option globally within the
- controller using the <methodname>setRedirectExit()</methodname>
- accessor.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>prependBase:</emphasis> whether or not to
- prepend the base <acronym>URL</acronym> registered with the request
- object to the <acronym>URL</acronym> provided.
- </para>
- <para>
- You may set this option globally within the
- controller using the
- <methodname>setRedirectPrependBase()</methodname> accessor.
- </para>
- </listitem>
- <listitem>
- <para>
- <emphasis>code:</emphasis> what <acronym>HTTP</acronym> code to utilize
- in the redirect. By default, an <acronym>HTTP</acronym> 302 is
- utilized; any code between 301 and 306 may be used.
- </para>
- <para>
- You may set this option globally within the
- controller using the
- <methodname>setRedirectCode()</methodname> accessor.
- </para>
- </listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </sect2>
- <sect2 id="zend.controller.action.subclassing">
- <title>Subclassing the Action Controller</title>
- <para>
- By design, <classname>Zend_Controller_Action</classname> must be subclassed
- in order to create an action controller. At the minimum, you will
- need to define action methods that the controller may call.
- </para>
- <para>
- Besides creating useful functionality for your web applications, you
- may also find that you're repeating much of the same setup or
- utility methods in your various controllers; if so, creating a
- common base controller class that extends
- <classname>Zend_Controller_Action</classname> could solve such redundancy.
- </para>
- <example id="zend.controller.action.subclassing.example-call">
- <title>Handling Non-Existent Actions</title>
- <para>
- If a request to a controller is made that includes an undefined
- action method, <methodname>Zend_Controller_Action::__call()</methodname>
- will be invoked. <methodname>__call()</methodname> is, of course,
- <acronym>PHP</acronym>'s magic method for method overloading.
- </para>
- <para>
- By default, this method throws a
- <classname>Zend_Controller_Action_Exception</classname> indicating the
- requested method was not found in the controller. If the method
- requested ends in 'Action', the assumption is that an action was
- requested and does not exist; such errors result in an exception
- with a code of 404. All other methods result in an exception
- with a code of 500. This allows you to easily differentiate
- between page not found and application errors in your error
- handler.
- </para>
- <para>
- You should override this functionality if you wish to perform
- other operations. For instance, if you wish to display an error
- message, you might write something like this:
- </para>
- <programlisting language="php"><![CDATA[
- class MyController extends Zend_Controller_Action
- {
- public function __call($method, $args)
- {
- if ('Action' == substr($method, -6)) {
- // If the action method was not found, render the error
- // template
- return $this->render('error');
- }
- // all other methods throw an exception
- throw new Exception('Invalid method "'
- . $method
- . '" called',
- 500);
- }
- }
- ]]></programlisting>
- <para>
- Another possibility is that you may want to forward on to a
- default controller page:
- </para>
- <programlisting language="php"><![CDATA[
- class MyController extends Zend_Controller_Action
- {
- public function indexAction()
- {
- $this->render();
- }
- public function __call($method, $args)
- {
- if ('Action' == substr($method, -6)) {
- // If the action method was not found, forward to the
- // index action
- return $this->_forward('index');
- }
- // all other methods throw an exception
- throw new Exception('Invalid method "'
- . $method
- . '" called',
- 500);
- }
- }
- ]]></programlisting>
- </example>
- <para>
- Besides overriding <methodname>__call()</methodname>, each of the
- initialization, utility, accessor, view, and dispatch hook methods
- mentioned previously in this chapter may be overridden in order to
- customize your controllers. As an example, if you are storing your
- view object in a registry, you may want to modify your
- <methodname>initView()</methodname> method with code resembling the following:
- </para>
- <programlisting language="php"><![CDATA[
- abstract class My_Base_Controller extends Zend_Controller_Action
- {
- public function initView()
- {
- if (null === $this->view) {
- if (Zend_Registry::isRegistered('view')) {
- $this->view = Zend_Registry::get('view');
- } else {
- $this->view = new Zend_View();
- $this->view->setBasePath(dirname(__FILE__) . '/../views');
- }
- }
- return $this->view;
- }
- }
- ]]></programlisting>
- <para>
- Hopefully, from the information in this chapter, you can see the
- flexibility of this particular component and how you can shape it to
- your application's or site's needs.
- </para>
- </sect2>
- </sect1>
- <!--
- vim:se ts=4 sw=4 et:
- -->
|