5.3. Создание подклассов

5.3.1. Введение

Система Zend_Controller построена с расчетом на расширение посредством написания новых классов, которые реализуют интерфейсы Zend_Controller_Router_Interface и Zend_Controller_Dispatcher_Interface, либо расширения классов Zend_Controller_Request_Abstract, Zend_Controller_Response_Abstract и Zend_Controller_Action.

Возможные причины для создания своих классов:

  • Существующая система маршрутизации URI не подходит в некоторых ситуациях, таких, как интеграция в существующий веб-сайт, который использует свои собственные соглашения по маршрутизации, которые не совместимы с механизмом маршрутизации, предоставляемым Zend Framework.

  • Вам нужно реализовать маршрутизацию для чего-либо совершенно иного. Класс Zend_Controller_Router работает только с URI. Возможно, вы захотите использовать паттерн MVC для разработки других типов программ — таких, как консольное или CGI приложение. В случае консольного приложения специализированный объект запроса может обрабатывать аргументы в командной строке.

  • Механизм, предоставляемый Zend_Controller_Dispatcher является неподходящим. Конфигурация по умолчанию предполагает, что контроллеры являются классами, а действия — методами в этих классах. Однако есть много других стратегий для реализации этого. Примером такой стратегии может быть та, где контроллеры являются каталогами, а действия — файлами внутри этих каталогов.

  • Вы хотите обеспечить дополнительные возможности, которые будут унаследованы всеми вашими контроллерами. Например, Zend_Controller_Action по умолчанию не интегрирован с Zend_View. Однако вы можете расширить свой собственный контроллер для реализации этого, и его использование не потребует изменения находящегося в поставке Zend_Controller_Router или Zend_Controller_Dispatcher

  • Вы хотите журналировать исключения в приложении, которые пойманы и перенаправлены к общей странице ошибок. Расширяя Zend_Controller_Response_Http, вы можете переопределить метод __toString() для проверки зарегистрированных исключений, добавления их в журнал и затем перенаправления на страницу ошибки.

Пожалуйста, будьте осторожны при переписывании важных частей системы, особенно диспетчера. Одним из преимуществ Zend_Controller является то, что он устанавливает общие соглашения для разрабатываемых приложений. Если изменено слишком многое из поведения по умолчанию, некототорые из этих преимуществ будут потеряны. Тем не менее, есть много разных потребностей, и одно решение не может соответствовать им всем, поэтому некоторая свобода допустима, если нужно.

5.3.2. Соглашения

Сильно рекомендуется следование этим соглашениям по именованию и хранению файлов при создании подклассов любых классов Zend_Controller. Следование этим соглашениям будет гарантировать легкое понимание вашего проекта другим программистом, знакомым с Zend Framework.

5.3.2.1. Префикс

Классы, входящие в Zend Framework, следуют соглашению, по которому имя каждого класса начинается с "Zend_". Это префикс. Мы рекомендуем называть все свои классы аналогичным образом, т.е. если ваша компания называется Widget Inc., префиксом должен быть"Widget_".

5.3.2.2. Структура категорий

Классы Zend_Controller хранятся в каталоге библиотек, как показано ниже:

/library
  /Zend
    /Controller
      Action.php
      Dispatcher.php
      Router.php

Когда создаете наследников от классов Zend_Controller, то рекомендуется сохранять их в такой же структуре, под вашим префиксом. Это облегчит их поиск для тех, кто просматривает код вашего приложения.

Например, проект компании Widget Inc., который реализует только свой маршрутизатор, может выглядеть следующим образом:

/library
  /Zend
  /Widget
    /Controller
      Router.php
      README.txt
         

Обратите внимание, что в этом примере каталог Widget/Controller/ копирует каталог Zend/Controller/ везде, где это возможно. В данном случае это класс Widget_Controller_Router, который может быть либо подклассом Zend_Controller_Router, либо замещать его, реализуя интерфейс Zend_Controller_Router_Interface.

Еще обратите внимание на то, что в этом примере в каталоге Widget/Controller/ размещен файл README.txt. Zend сильно приветствует документирование проектов посредством отдельных тестов и документации для клиентов. Несмотря на это, рекомендуется еще размещать простой файл README.txt, в котором кратко описываются изменения и то, как они работают.

5.3.3. Абстрактный класс запроса

Абстрактный класс Zend_Controller_Request_Abstract определяет некоторые методы:

    /**
     * @return string
     */
    public function getControllerName();

    /**
     * @param string $value 
     * @return self
     */
    public function setControllerName($value);

    /**
     * @return string
     */
    public function getActionName();

    /**
     * @param string $value 
     * @return self
     */
    public function setActionName($value);

    /**
     * @return string
     */
    public function getControllerKey();

    /**
     * @param string $key 
     * @return self
     */
    public function setControllerKey($key);

    /**
     * @return string
     */
    public function getActionKey();

    /**
     * @param string $key 
     * @return self
     */
    public function setActionKey($key);

    /**
     * @param string $key 
     * @return mixed
     */
    public function getParam($key);

    /**
     * @param string $key 
     * @param mixed $value 
     * @return self
     */
    public function setParam($key, $value);

    /**
     * @return array
     */
     public function getParams();

    /**
     * @param array $array 
     * @return self
     */
    public function setParams(array $array);

    /**
     * @param boolean $flag 
     * @return self
     */
    public function setDispatched($flag = true);

    /**
     * @return boolean
     */
    public function isDispatched();
}

Объект запроса является контейнером для переменных запроса. Цепочке контроллеров нужно знать только то, как устанавливать и извлекать контроллер, действие, необязательные параметры и флаг диспетчеризации. По умолчанию запрос будет искать в своих параметрах для определения контроллера и действия, используя ключи контроллера или действия .

5.3.4. Интерфейс маршрутизатора

Интерфейс Zend_Controller_Router_Interface определяет только один метод:

<?php

  /**
   * @param  Zend_Controller_Request_Abstract $request
   * @throws Zend_Controller_Router_Exception
   * @return Zend_Controller_Request_Abstract
   */
  public function route(Zend_Controller_Request_Abstract $request);

?>   

Маршрутизация производится только один раз: когда запрос впервые получен системой. Назначением маршрутизатора является определение контроллера, действия и необязательных параметров, основываясь на переменных запроса, и установка их в запросе. Объект запроса затем передается диспетчеру. Если не найден маршрут, соответствующий запросу, то маршрутизатор ничего не должен делать с объектом запроса.

5.3.5. Интерфейс диспетчера

Zend_Controller_Front сначала вызывает маршрутизатор для получения первого доступного диспетчеризации действия в запросе. Затем он входит в цикл диспетчеризации

В этом цикле он сначала устанавливает флаг диспетчеризации в объекте запроса и затем обрабатывает запрос (инстанцирует контроллер, вызывает его метод действия) Если метод действия (или методы pre/postDispatch в установленном плагине) сбрасывают флаг диспетчеризации, то фронт-контроллер будет выполнять другую итерацию в цикле диспетчеризации с тем действием, которое установлено на данный момент в объекте запроса. Это позволяет последовательно обрабатывать действия до тех пор, пока вся работа не будет выполнена.

Интерфейс Zend_Controller_Dispatcher_Interface определяет два метода:

<?php

/**
 * @param  Zend_Controller_Request_Abstract $request
 * @return boolean
 */
public function isDispatchable(Zend_Controller_Request_Abstract $request);

?>   

isDispatchable() проверяет, является ли запрос пригодным к обработке. Если является, то возвращается TRUE, иначе FALSE. Определение того, что пригодно для обработки диспетчером, зависит от класса, реализующего интерфейс. В случае реализации по умолчанию (Zend_Controller_Dispatcher) это означает, что файл контроллера существует, класс существует в этом файле и метод, реализующий действие, существует в этом классе.

<?php

/**
 * @param  Zend_Controller_Request_Abstract $route
 * @return Zend_Controller_Request_Abstract
 */
public function dispatch(Zend_Controller_Request_Abstract $request);

?>   

dispatch() является основным рабочим методом. Этот метод должен выполнять действие контроллера. Он должен возвращать объект диспетчеризации.

5.3.6. Контроллер действий

Zend_Controller_Action осуществляет контроль за различными действиями в приложении. Абстрактный класс предоставляет следующие методы:

    /**
     * @param Zend_Controller_Request_Abstract $request Объект запроса
     * @param Zend_Controller_Response_Abstract $response Объект ответа
     * @param array $args Необязательный ассоциативный массив
     * конфигурационных переменных и переменных среды
     */
    public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $args = array());

    /**
     * @return void
     */
    public function init();

    /**
     * @return Zend_Controller_Request_Abstract
     */
    public function getRequest();

    /**
     * @param Zend_Controller_Request_Abstract $request 
     * @return self
     */
    public function setRequest(Zend_Controller_Request_Abstract $request);

    /**
     * @return Zend_Controller_Response_Abstract
     */
    public function getResponse();

    /**
     * @param Zend_Controller_Response_Abstract $response 
     * @return self
     */
    public function setResponse(Zend_Controller_Response_Abstract $response);

    /**
     * @return array
     */
    public function getInvokeArgs();

    /**
     * @return mixed
     */
    public function getInvokeArg($name);

    public function preDispatch();

    public function postDispatch();

    /**
     * @param string $methodName
     * @param array $args
     */
    public function __call($methodName, $args);

    /**
     * @param null|Zend_Controller_Request_Abstract $request Объект запроса для
     * использования (необязательный)
     * @param null|Zend_Controller_Response_Abstract $response Объект запроса 
     * для использования (необязательный)
     * @return Zend_Controller_Response_Abstract
     */
    public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null);

Конструктор требует передачи объектов запроса и ответа, также он принимает массив любых дополнительных конфигурационных аргументов в качестве третьего аргумента. Этот массив содержит параметры, зарегистрированные через методы setParam() или setParams() фронт-контроллера. В конце своего выполнения конструктор передает управление методу init().

Несмотря на то, что вы можете переопределять конструктор, мы советуем выполнять любые действия по инициализации в методе init() для того, чтобы обеспечить должную регистрацию объектов запроса и ответа.

Получить доступ к любым конфигурационным аргументам, переданным конструктору, можно через методы getInvokeArg() и getInvokeArgs(). Рекомендуется использовать эти аргументы для передачи в такие объекты, как объект вида, аутентификации/авторизации или реестра объектов. Например:

$front = Zend_Controller_Front::getInstance();
$front->setParam('view', new Zend_View())
      ->setControllerDirectory($config->controller->directory);
$response = $front->dispatch();

// Пример контроллера действия:
class FooController extends Zend_Controller_Action
{
    protected $_view = null;

    public function init()
    {
        $this->_view = $this->getInvokeArg('view');
    }
}

Когда действие запущено диспетчером, можно выполнять обработку до и после действия с помощью методов preDispatch() и postDispatch(). По умолчанию они не выполняют никакой работы.

Метод __call() принимает вызовы незарегистрированных в классе действий. По умолчанию он вызывает исключение, если действие не определено. Это должно происходить только тогда, когда не определен метод действия по умолчанию.

По умолчанию соглашение по именованию методов действий подразумевает имя метода следующего вида: lowercaseAction, где 'lowercase' обозначает имя действия, а 'Action' обозначает, что этот метод является методом действия. Таким образом, URL http://framework.zend.com/foo/bar будет вызывать действие FooController::barAction().

Контроллеры действия могут также использоваться как контроллеры страниц. Наиболее типичное использование может быть таким, как в коде ниже:

$controller = new FooController(
    new Zend_Controller_Request_Abstract(),
    new Zend_Controller_Response_Abstract()
);
$controller->run();
[Замечание] Использование фронт-контроллера и контроллеров действии

Мы рекомендуем использовать комбинацию фронт-контроллера и контроллеров действии вместо контроллера страниц для содействия написанию приложений, которые будут взаимодействовать друг с другом.

5.3.7. Объект ответа

Объект ответа служит для сбора содержимого и заголовков из вызываемых действий и возвращает их клиенту. Он имеет следующие методы:

    /**
     * @param string $name Имя заголовка
     * @param string $value Значение заголовка
     * @param boolean $replace Должен ли или нет заменять собой уже
     * зарегистрированный в объекте заголовок с тем же именем
     * @return self
     */
    public function setHeader($name, $value, $replace = false);

    /**
     * @return array
     */
    public function getHeaders();

    /**
     * @return void
     */
    public function clearHeaders();

    /**
     * Отправляет все заголовки
     * @return void
     */
    public function sendHeaders();

    /**
     * @param string $content
     * @return self
     */
    public function setBody($content);

    /**
     * @param string $content
     * @return self
     */
    public function appendBody($content);

    /**
     * @return string
     */
    public function getBody();

    /**
     * Выводит контент
     * @return void
     */
    public function outputBody();

    /**
     * @param Exception $e 
     * @return self
     */
    public function setException(Exception $e);

    /**
     * @return null|Exception
     */
    public function getException();

    /**
     * @return boolean
     */
    public function isException();

    /**
     * @param boolean $flag
     * @return boolean
     */
    public function renderExceptions($flag = null);

    /**
     * @return string
     */
    public function __toString();

Метод setBody() заменяет весь контент; мы советуем использовать вместо него метод appendBody(). __toString() должен выводить весь контент и отправлять все заголовки.

В объекте ответа также отлавливаются и регистрируются исключения из контроллера действий (до тех пор, пока не будет включено Zend_Controller_Front::throwExceptions()). Метод isException() должен возвращать булево значение, показывающее, было ли сгенерировано исключение или нет. renderExceptions() используется для определения того, должен ли метод __toString() выводить исключение, если оно было поймано.