<sect1 id="zend.controller.action">
    <title>Контроллеры действий</title>

    <sect2 id="zend.controller.action.introduction">
        <title>Введение</title>
        <para>
            <code>Zend_Controller_Action</code> - абстрактный класс,
            который можно использовать для реализации контроллеров действий
            для последующего их использования с фронт-контроллером при
            разработке сайта, основанного на паттерне Model-View-Controller
            (MVC).
        </para>

        <para>
            Для того, чтобы использовать <code>Zend_Controller_Action</code>,
            нужно создать его подкласс в своей действующей директории
            контроллеров (или расширить его для создания своего базового класса
            контроллеров действий). Работа с ним в основном сводится к
            созданию его подкласса и написании методов действий, соответствующих
            различным действиям, которые должен обрабатывать этот контроллер.
            Маршрутизатор и диспетчер компоненты <code>Zend_Controller</code>
            будут считать за методы действий все методы в классе
            контроллера с именем, заканчивающимся на 'Action'.
        </para>

        <para>
            Для примера предположим, что ваш класс определен следующим образом:
        </para>

        <programlisting role="php"><![CDATA[
class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // делает что-нибудь
    }

    public function bazAction()
    {
        // делает что-нибудь
    }
}
]]>
        </programlisting>

        <para>
            Приведенный выше класс <code>FooController</code> (контроллер
            <code>foo</code>) определяет два действия - <code>bar</code> и
            <code>baz</code>.
        </para>

        <para>
            Класс может быть дополнен инициализирующим методом, методом
            действия по умолчанию (если не был вызван метод, либо
            вызван несуществующий метод), перехватчиками pre- и post-dispatch и
            различными вспомогательными методами. Этот раздел служит обзором
            функционала контроллера действий.
        </para>

        <note>
            <title>Поведение по умолчанию</title>

            <para>
                По умолчанию <link
                linkend="zend.controller.front">фронт-контроллер</link>
                активирует помощника действий <link
                linkend="zend.controller.actionhelpers.viewrenderer">ViewRenderer</link>.
                Этот помощник обеспечивает добавление объекта вида в контроллер
                и автоматический рендеринг видов. Вы можете отключить его в
                своем контроллере действия, используя один из следующих методов:
            </para>

            <programlisting role="php"><![CDATA[
class FooController extends Zend_Controller_Action
{
    public function init()
    {
        // Локально, только для данного контроллера:
        $this->_invokeArgs['noViewRenderer'] = true;

        // Глобально:
        $this->_helper->removeHelper('viewRenderer');

        // Тоже глобально, но должен использоваться вместе
        // с локальной версией для того, чтобы распространить
        // действие на данный контроллер:
        Zend_Controller_Front::getInstance()
            ->setParam('noViewRenderer', true);
    }
}
]]>
            </programlisting>

            <para>
                <code>initView()</code>, <code>getViewScript()</code>,
                <code>render()</code> и <code>renderScript()</code> служат
                посредниками для <code>ViewRenderer</code>, пока этот помощник
                находится в брокере помощников и не установлен флаг
                <code>noViewRenderer</code>.
            </para>

            <para>
                Вы можете также отключить рендеринг для отдельного вида
                посредством установки флага <code>noRender</code> в
                <code>ViewRenderer</code>:
            </para>

            <programlisting role="php"><![CDATA[
class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // отключение авторендеринга для этого действия:
        $this->_helper->viewRenderer->setNoRender();
    }
}
]]>
            </programlisting>

            <para>
                Основные причины для отключения <code>ViewRenderer</code> - вам
                просто не нужен объект вида или если вы не производите рендеринг
                через скрипты вида (например, когда используется контроллер
                действий для обслуживания протоколов веб-сервисов, таких, как
                SOAP, XML-RPC, или REST). В большинстве случаев не
                нужно будет глобально отключать <code>ViewRenderer</code>,
                только избирательно в отдельных контроллерах или действиях.
            </para>
        </note>
    </sect2>

    <sect2 id="zend.controller.action.initialization">
        <title>Инициализация объекта</title>

        <para>
            Несмотря на то, что вы всегда можете переопределить конструктор
            контроллера действий, мы не рекомендуем делать это.
            <code>Zend_Controller_Action::__construct()</code>
            выполняет некоторые важные
            задачи, такие, как регистрация объектов запроса и ответа, аргументов
            вызова, переданных из фронт-контроллера. Если необходимо
            переопределить контроллер, то всегда вызывайте конструктор
            родительского класса <code>parent::__construct($request, $response,
            $invokeArgs)</code> в конструкторе подкласса.
        </para>

        <para>
            Более подходящим способом настройки инстанцирования
            является использование метода <code>init()</code>, который
            вызывается в конце выполнения <code>__construct()</code>. Например,
            если вы хотите устанавливать соединение с БД при инстанцировании:
        </para>

        <programlisting role="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- и Post-Dispatch</title>

        <para>
            <code>Zend_Controller_Action</code> определяет два метода, которые
            вызываются до и после требуемого действия,
            <code>preDispatch()</code> и <code>postDispatch()</code>. Они
            могут быть полезны в различных случаях - например, проверка
            аутентификации и списка управления доступом до запуска действия
            (при вызове метода <code>_forward()</code> в
            <code>preDispatch()</code> текущее действие будет пропущено) или
            размещение сгенерированного содержимого в шаблоне боковой части
            сайта (метод <code>postDispatch()</code>).
        </para>
    </sect2>

    <sect2 id="zend.controller.action.accessors">
        <title>Аксессоры</title>

        <para>
            С объектом контроллера регистрируется несколько объектов
            и переменных, они имеют свои методы-аксессоры.
        </para>

        <itemizedlist>
            <listitem><para>
                <emphasis>Объект запроса</emphasis>: через метод
                <code>getRequest()</code> извлекается объект запроса, который
                использовался для вызова данного действия.
            </para></listitem>

            <listitem>
                <para>
                    <emphasis>Объект ответа</emphasis>:
                    через метод <code>getResponse()</code> извлекается объект
                    ответа, объединяющий в себе заголовки и содержимое ответа.
                    Некоторые типичные вызовы могут выглядеть следующим образом:
                </para>

                <programlisting role="php"><![CDATA[
$this->getResponse()->setHeader('Content-Type', 'text/xml');
$this->getResponse()->appendBody($content);
]]>
                </programlisting>
            </listitem>

            <listitem>
                <para>
                    <emphasis>Аргументы вызова</emphasis>: фронт-контроллер
                    может добавлять параметры в маршрутизатор, диспетчер и
                    контроллер действий. Для их получения используйте
                    <code>getInvokeArg($key)</code>, можно также извлечь весь список
                    аргументов, используя метод <code>getInvokeArgs()</code>.
                </para>
            </listitem>

            <listitem>
                <para>
                    <emphasis>Параметры запроса</emphasis>: Объект запроса
                    заключает в себе параметры запроса, такие, как значения
                    _GET, _POST, или пользовательские параметры, определенные в
                    пути URL. Для их получения используйте
                    <code>_getParam($key)</code> или
                    <code>_getAllParams()</code>. Вы можете также установить
                    параметры запроса, используя метод <code>_setParam()</code>,
                    это полезно при перенаправлении на другие действия через
                    метод <code>_forward()</code>.
                </para>

                <para>
                    Для определения того, существует ли параметр или нет
                    (полезно для логического ветвления), используйте
                    <code>_hasParam($key)</code>.
                </para>

                <note>
                    <para>
                        <code>_getParam()</code> может принимать опциональный
                        второй аргумент, содержащий значение по умолчанию,
                        которое используется, если параметр не установлен или
                        пустой. Его использование устраняет необходимость
                        вызова <code>_hasParam()</code> до получения значения:
                    </para>

                    <programlisting role="php"><![CDATA[
// Используется значение по умолчанию 1, если id не установлен
$id = $this->_getParam('id', 1);

// Вместо:
if ($this->_hasParam('id') {
    $id = $this->_getParam('id');
} else {
    $id = 1;
}
]]>
                    </programlisting>
                </note>
            </listitem>
        </itemizedlist>
    </sect2>

    <sect2 id="zend.controller.action.viewintegration">
        <title>Интеграция вида</title>

        <note id="zend.controller.action.viewintegration.viewrenderer">
            <title>По умолчанию интеграция вида производится через ViewRenderer</title>

            <para>
                Изложенное в этом разделе действительно только в том случае,
                если вы явным образом отключили
                <link linkend="zend.controller.actionhelpers.viewrenderer">ViewRenderer</link>.
                Иначе вы можете спокойно пропустить этот раздел.
            </para>
        </note>

        <para>
            <code>Zend_Controller_Action</code> предоставляет простейший и
            гибкий механизм интеграции видов. Два метода осуществляют это:
            <code>initView()</code> и <code>render()</code>. Первый метод
            выполняет отложенную загрузку открытого свойства <code>$view</code>,
            второй выполняет рендеринг вида, основываясь на запрошенном в данный
            момент действии, используя иерархию директорий для определения пути
            к скрипту.
        </para>

        <sect3 id="zend.controller.action.viewintegration.initview">
            <title>Инициализация вида</title>

            <para>
                <code>initView()</code> инициализирует объект вида.
                <code>render()</code> вызывает <code>initView()</code> для
                извлечения объекта вида, но этот объект может быть
                инициализирован в любое время. По умолчанию
                <code>initView()</code> заполняет свойство <code>$view</code>
                объектом <code>Zend_View</code>, но может также использоваться
                любой класс, реализующий интерфейс
                <code>Zend_View_Interface</code>. Если <code>$view</code> уже
                инициализирован, то просто возвращается это свойство.
            </para>

            <para>
                Реализация, используемая по умолчанию, делает следующие
                предположения по структуре директорий:
            </para>

            <programlisting role="php"><![CDATA[
applicationOrModule/
    controllers/
        IndexController.php
    views/
        scripts/
            index/
                index.phtml
        helpers/
        filters/
]]>
            </programlisting>

            <para>
                Другими словами, предполагается, что скрипты вида находятся в
                поддиректории <code>views/scripts/</code> и поддиректория
                <code>views</code> должна содержать родственный функционал того
                же уровня (это могут быть помощники, фильтры). Когда
                определяется имя и путь к скрипту вида, то в качестве базового
                пути используется директория <code>views/scripts/</code>
                с директориями, именованными в соответствии с отдельными
                контроллерами, что дает иерархию скриптов вида.
            </para>
        </sect3>

        <sect3 id="zend.controller.action.viewintegration.render">
            <title>Рендеринг видов</title>

            <para>
                <code>render()</code> имеет следующую сигнатуру:
            </para>

            <programlisting role="php"><![CDATA[
string render(string $action = null,
              string $name = null,
              bool $noController = false);
]]>
            </programlisting>

            <para>
                <code>render()</code> рендерит скрипт вида. Если не были
                переданы аргументы, то предполагается, что запрашивается скрипт
                <code>[controller]/[action].phtml</code> (где
                <code>.phtml</code> - значение свойства
                <code>$viewSuffix</code>). Передача значения для
                <code>$action</code> вызовет генерацию этого шаблона в
                поддиректории <code>[controller]</code>. Для того, чтобы
                отменить использование поддиректории <code>[controller]</code>,
                передавайте значение true для <code>$noController</code>.
                Шаблоны рендерятся в объект ответа, если же вы хотите сохранить
                результат в
                <link linkend="zend.controller.response.namedsegments">именованный
                сегмент</link> объекта ответа, то передавайте значение для
                <code>$name</code>.
            </para>

            <note><para>
                    Поскольку имена контроллера и действия могут содержать
                    символы-ограничители слов, такие, как '_', '.', и '-', то
                    <code>render()</code> нормализует их к '-', когда
                    определяет имя скрипта.
                    Внутри себя для такой нормализации он использует
                    ограничители слов и путей для диспетчера. Таким образом,
                    запрос к <code>/foo.bar/baz-bat</code> приведет к рендерингу
                    скрипта <code>foo-bar/baz-bat.phtml</code>. Если ваш метод
                    действия содержит camelCase, то следует иметь в виду, что
                    при определении имени скрипта вида результатом будут
                    разделенные '-' слова.
            </para></note>

            <para>
                Некоторые примеры:
            </para>

            <programlisting role="php"><![CDATA[
class MyController extends Zend_Controller_Action
{
    public function fooAction()
    {
        // Рендеринг my/foo.phtml
        $this->render();

        // Рендеринг my/bar.phtml
        $this->render('bar');

        // Рендеринг baz.phtml
        $this->render('baz', null, true);

        // Рендеринг my/login.phtml в сегмент 'form' объекта ответа
        $this->render('login', 'form');

        // Рендеринг site.phtml в сегмент 'page' объекта ответа,
        // при этом не используется поддиректория 'my/'
        $this->render('site', 'page', true);
    }

    public function bazBatAction()
    {
        // Рендеринг my/baz-bat.phtml
        $this->render();
    }
}
]]>
            </programlisting>
        </sect3>
    </sect2>

    <sect2 id="zend.controller.action.utilmethods">
        <title>Сервисные методы</title>

        <para>
            Кроме аксессоров и методов интеграции видов,
            <code>Zend_Controller_Action</code> имеет несколько сервисных
            методов для выполнения распространенных зачач в методах действий
            (или в методах pre- и post-dispatch).
        </para>

        <itemizedlist>
            <listitem>
                <para>
                    <code>_forward($action, $controller = null, $module = null,
                    array $params = null)</code>: выполяет другое действие.
                    Если был вызван в <code>preDispatch()</code>, то
                    запрошенноое в данный момент
                    действие будет пропущено в пользу нового. Иначе
                    действие, запрошенное в _forward(), будет выполнено после
                    того, как было выполнено текущее действие.
                </para>
            </listitem>

            <listitem>
                <para>
                    <code>_redirect($url, array $options =
                        array())</code>: производит перенаправление по другому
                    адресу. Этот метод принимает URL и опционально набор опций.
                    По умолчанию он производит перенаправление HTTP 302.
                </para>

                <para>
                    Опции могут включать в себя одну или более из следующих:
                </para>

                <itemizedlist>
                    <listitem>
                        <para>
                            <emphasis>exit:</emphasis> производить или нет выход
                            после этого. Если установлена, то будет произведены
                            надлежащее закрытие всех открытых сессий и
                            перенаправление.
                        </para>

                        <para>
                            Вы можете установить эту опцию глобально в
                            контроллере, используя аксессор
                            <code>setRedirectExit()</code>.
                        </para>
                    </listitem>

                    <listitem>
                        <para>
                            <emphasis>prependBase:</emphasis> добавлять или нет
                            базовый URL из объекта запроса в начало данного URL.
                        </para>

                        <para>
                            Вы можете установить эту опцию глобально в
                            контроллере, используя аксессор
                            <code>setRedirectPrependBase()</code>.
                        </para>
                    </listitem>

                    <listitem>
                        <para>
                            <emphasis>code:</emphasis> какой код HTTP
                            использовать при перенаправлении. По умолчанию
                            используется HTTP 302. Могут использоваться любые
                            коды от 301 до 306.
                        </para>

                        <para>
                            Вы можете установить эту опцию глобально в
                            контроллере, используя аксессор
                            <code>setRedirectCode()</code>.
                        </para>
                    </listitem>
                </itemizedlist>
            </listitem>
        </itemizedlist>
    </sect2>

    <sect2 id="zend.controller.action.subclassing">
        <title>Создание подклассов контроллера действий</title>

        <para>
            Задумано, что в порядке создания контроллеров действий должны
            создаваться подклассы от <code>Zend_Controller_Action</code>.
            Как минимум, вам нужно будет определить методы действий, которые
            может вызывать контроллер.
        </para>

        <para>
            Помимо создания полезного функционала для своих веб-приложений, вы
            можете также обнаружить, что большинство установок или сервисных
            методов повторяются в ваших различных контроллерах. В этом случае
            создание общего базового контроллера, расширяющего
            <code>Zend_Controller_Action</code>, может решить проблему
            избыточности.
        </para>

        <example id="zend.controller.action.subclassing.example-call">
            <title>Обрабаботка обращений к несуществующим действиям</title>

            <para>
                Если сделан запрос к контроллеру, который содержит в себе
                не определенный в контроллере метод действия, то вызывается метод
                <code>Zend_Controller_Action::__call()</code>.
                <code>__call()</code> является магическим методом для перегрузки
                методов в PHP.
            </para>

            <para>
                По умолчанию этот метод бросает исключение
                <code>Zend_Controller_Action_Exception</code>, означающее, что
                требуемый метод не найден в контроллере. Если требуемый метод
                заканчивается строкой 'Action', то предполагается, что было
                запрошено действие и оно не существует; такая ошибка приводит к
                исключению с кодом 404. В остальных случаях бросается исключение
                с кодом 500. Это позволяет легко дифференцировать в обработчике
                ошибок случаи, когда страница не найдена, и когда произошла
                ошибка приложения.
            </para>

            <para>
                Например, если вы хотите выводить сообщение об ошибке, то можете
                написать нечто подобное:
            </para>

            <programlisting role="php"><![CDATA[
class MyController extends Zend_Controller_Action
{
    public function __call($method, $args)
    {
        if ('Action' == substr($method, -6)) {
            // Если метод действия не найден, то рендерится шаблон ошибки
            return $this->render('error');
        }

        // все другие методы бросают исключение
        throw new Exception('Invalid method "'
                            . $method
                            . '" called',
                            500);
    }
}
]]>
            </programlisting>

            <para>
                Другая возможность состоит в том, что вы можете
                производить переход на страницу контроллера по умолчанию:
            </para>

            <programlisting role="php"><![CDATA[
class MyController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $this->render();
    }

    public function __call($method, $args)
    {
        if ('Action' == substr($method, -6)) {
            // Если метод действия не был найден, то производится
            // переход к действию index
            return $this->_forward('index');
        }

        // все другие методы бросают исключение
        throw new Exception('Invalid method "'
                            . $method
                            . '" called',
                            500);
    }
}
]]>
            </programlisting>
        </example>

        <para>
            Как и метод <code>__call()</code>, любые аксессоры,
            сервисные методы, методы инициализации, вида и перехвата, упомянутые
            ранее в этом разделе, могут быть переопределены для того, чтобы
            приспособить свои контроллеры под конкретные нужды. Например, если
            вы храните свои объекты вида в реестре, то можете модифицировать
            свой метод <code>initView()</code>:
        </para>

        <programlisting role="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>
            Надеемся, из написанного в этом разделе вы смогли увидеть, насколько
            гибка эта компонента, и как можно заточить ее под нужды своего
            приложения или сайта.
        </para>
    </sect2>
</sect1>
<!--
vim:se ts=4 sw=4 et:
-->
