# Фронт-контроллер

## Обзор

`Zend_Controller_Front` реализует паттерн Front Controller, используемый в приложениях MVC. Его назначение состоит в инициализации окружения запроса, проложении маршрута приходящего запроса и последующем запуске выявленных действий. Он агрегирует все ответы и возвращает их по завершении процесса.

`Zend_Controller_Front` также реализует паттерн Singleton, это означает, что в любое время может быть доступен только один экземпляр этого класса. Это позволяет ему также выступать в качестве реестра, в котором хранятся другие объекты, участвующие в процессе диспетчеризации.

`Zend_Controller_Front` регистрирует в себе брокер плагинов (plugin broker), что позволяет с помощью плагинов отслеживать события, инициируемые фронт-контроллером. В большинстве случаев это дает возможность подгонять процесс диспетчеризации под конкретный сайт без расширения фронт-контроллера для добавления функциональности.

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

### Поведение по умолчанию

По умолчанию фронт-контроллер загружает плагин ErrorHandler и помощник действий ViewRenderer. Это сделано для упрощения обработки ошибок и рендеринга видов в контроллерах, соответственно.

Для того, чтобы отключить `ErrorHandler`, произведите следующее до вызова метода `dispatch()`:

    // Отключение плагина ErrorHandler:
    $front->setParam('noErrorHandler', true);

Для того, чтобы отключить `ViewRenderer`, сделайте следующее до вызова метода `dispatch()`:

    // Отключение помощника ViewRenderer:
    $front->setParam('noViewRenderer', true);

## Ключевые методы

Фронт-контроллер имеет несколько аксессоров для настройки его конфигурации. Тем не менее, он имеет несколько ключевых в его функционале методов.

### getInstance()

`getInstance()` используется для получения экземпляра фронт-контроллера. Поскольку фронт-контроллер реализует паттерн Singleton, то это также единственно доступный способ инстанцирования объекта фронт-контроллера.

    $front = Zend_Controller_Front::getInstance();

### setControllerDirectory() и addControllerDirectory()

`setControllerDirectory()` используется для того, чтобы указать диспетчеру, где искать файлы классов контроллеров действий. Он принимает один путь или ассоциативный массив пар модуль => путь в качестве параметра.

Примеры:

    // Установка директории контроллеров, используемой по умолчанию:
    $front->setControllerDirectory('../application/controllers');

    // Одновременная установка нескольких директорий модулей:
    $front->setControllerDirectory(array(
        'default' => '../application/controllers',
        'blog'    => '../modules/blog/controllers',
        'news'    => '../modules/news/controllers',
    ));

    // Добавление директории модуля 'foo':
    $front->addControllerDirectory('../modules/foo/controllers', 'foo');

Если вы используете `addControllerDirectory()` без имени модуля, то он установит директорию для модуля `default`. Если директория уже существует, то она будет перезаписана.

Можно получить текущие установки для директорий контроллеров, используя метод `getControllerDirectory()`. Он вернет массив пар модуль => директория.

### addModuleDirectory() и getModuleDirectory()

Одной из возможностей фронт-контроллера является то, что вы можете определять модульную структуру директорий для создания отдельных компонент, которые называются "модулями".

Каждый модуль должен находиться в собственной директории и отражать структуру директорий используемого по умолчанию модуля - т.е., он должен содержать, как минимум, поддиректорию "controllers"; с ней, как правило, присутствует поддиректория "views" и другие поддиректории приложения.

Метод `addModuleDirectory()` позволяет передавать имя директории, содержащей один или более директорий модулей. Он их сканирует и добавляет в качестве директорий контроллеров во фронт-контроллер.

Если вы хотите получить путь к определенному модулю или к текущему модулю, то можете использовать метод `getModuleDirectory()`, при передаче имени модуля он возвращает путь к нему, иначе возвращается путь к текущему модулю.

### dispatch()

`dispatch(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null)` является "рабочей лошадкой" фронт-контроллера. Он может опционально принимать объект запроса и/или объект ответа, что дает разработчикам возможность передавать свои объекты.

Если методу `dispatch()` не были переданы объект запроса или ответа, то он будет проверять, были ли ранее зарегистрированы объекты, и использовать их, либо инстанцировать версии по умолчанию (в обоих случаях по умолчанию будут использоваться разновидность HTTP).

Аналогичным образом `dispatch()` производит проверку на уже зарегистрированные объекты маршрутизатора и диспетчера, и если они не найдены, то инстанцирует версии по умолчанию.

Процесс диспетчеризации имеет три отдельных события:

- Маршрутизация

- Диспетчеризация

- Ответ

Маршрутизация производится только один раз, когда вызывается `dispatch()`, при этом используются значения в объекте запроса. Диспетчеризация производится циклически. Запрос может означать вызов нескольких действий, контроллер или плагин могут сбрасывать объект запроса для дополнительного вызова других действий. Когда все действия выполнены, фронт-контроллер возвращает ответ.

### run()

`Zend_Controller_Front::run($path)` - статический метод, принимающий только путь к директории контроллеров. Он извлекает экземпляр фронт-контроллера через getInstance()), регистрирует этот путь через setControllerDirectory(), и в конце вызывает метод dispatch().

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

    // Инстанцирование фронт-контроллера, установка директории контроллера и
    // выполнение диспетчеризации в одном вызове:
    Zend_Controller_Front::run('../application/controllers');

## Методы-аксессоры для конфигурирования

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

- `resetInstance()` может использоваться для сброса всех текущих настроек. Он в основном предназначен для тестирования, но может также использоваться в тех случаях, когда нужно связать между собой несколько фронт-контроллеров.

- `(set|get)DefaultControllerName()` позволяют установить другое имя используемого по умолчанию контроллера (иначе используется 'index') и получить текущее значение. Они служат посредниками к диспетчеру.

- `(set|get)DefaultAction()` позволяют установить другое имя используемого по умолчанию действия (иначе используется 'index') и получить текущее значение. Они служат посредниками к диспетчеру.

- `(set|get)Request()` позволяют установить класс запроса или его объект для использования в процессе диспетчеризации и получить текущий объект. При установке объекта запроса вы можете передать имя класса запроса, в этом случае метод загрузит файл класса и инстанцирует его.

- `(set|get)Router()` позволяют установить класс маршрутизатора или его объект, используемые в течение процесса диспетчеризации, и получить текущий объект. Когда устанавливается объект маршрутизатора, вы можете передать имя класса маршрутизатора, в этом случае метод загрузит файл класса и инстанцирует его.

  При извлечении объекта маршрутизатора сначала производится проверка, представлен ли во фронт-контроллере такой объект и в случае его отсутствия инстанцирует используемый по умолчанию маршрутизатор (Rewrite Router).

- `(set|get)BaseUrl()` дают возможность установить базовый URL, который удаляется из начала при маршрутизации запросов, и получить его текущее значение. Это значение передается объекту запроса непосредственно до маршрутизации.

- `(set|get)Dispatcher()` дают возможность установки класса диспетчера или его объекта для использования в течение диспетчеризации, и получения текущего объекта. При установке объекта диспетчера можно передать имя класса диспетчера, в этом случае метод загрузит файл класса и инстанцирует его.

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

- `(set|get)Response()` дают возможность установить класс ответа или его объект для использования в процессе диспетчеризации, и извлечь текущий объект. При установке объекта ответа можно передать имя класса ответа, в этом случае метод загрузит файл класса и инстанцирует его.

- `registerPlugin(Zend_Controller_Plugin_Abstract $plugin, $stackIndex = null)` позволяет регистрировать объекты плагинов. Путем установки опционального параметра `$stackIndex`, вы можете контролировать порядок, в котором выполняются плагины.

- `unregisterPlugin($plugin)` позволяет отменять регистрацию объектов плагинов. `$plugin` может быть как объектом плагина, так и строкой, обозначающей класс плагина, регистрацию которого надо отменить.

- `throwExceptions($flag)` используется для включения/отключения возможности генерации исключений в течение процесса диспетчеризации. По умолчанию исключения отлавливаются и размещаются в объекте ответа; включение `throwExceptions()` переопределит это поведение

  Более полробную информацию читайте в .

- `returnResponse($flag)` используется для того, чтобы указать фронт-контроллеру - возвращать ли ответ из `dispatch()` (`TRUE`), либо ответ должен быть отправлен автоматически (`FALSE`). По умолчанию ответ отправляется автоматически (через вызов `Zend_Controller_Response_Abstract::sendResponse()`); включение `returnResponse()` переопределит это поведение.

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

## Параметры фронт-контроллера

Во введении мы указали на то, что фронт-контроллер также выступает как реестр для различных компонент контроллера. Это реализуется через группу методов для параметров. Эти методы позволяют регистрировать произвольные данные - объекты и переменные - во фронт-контроллере, чтобы их можно было извлечь в любой точке цепочки диспетчеризации. Эти значения передаются маршрутизатору, диспетчеру, и контроллерам действий. Эти методы включают в себя:

- `setParam($name, $value)` дает возможность установить единственный параметр с именем `$name` и значением `$value`.

- `setParams(array $params)` позволяет установить несколько параметров одновременно с помощью ассоциативного массива.

- `getParam($name)` дает возможность извлечь один параметр за один раз, используя его имя `$name` в качестве идентификатора.

- `getParams()` позволяет извлечь все параметры за один раз.

- `clearParams()` позволяет удалить один параметр (путем передачи строкового идентификатора), нескольких параметров (путем передачи массива строковых идентификаторов) или весь стек параметров (без передачи чего-либо).

Есть несколько предопределенных параметров специально для использования в цепочке диспетчеризации:

- `useDefaultControllerAlways` используется для указания диспетчеру, что следует использовать контроллер по умолчанию в модуле по умолчанию для любых запросов, диспетчеризация которых невозможна (т.е. модуль, контроллер и/или действие не существуют). По умолчанию он отключен.

  См. за более подробной информацией об использовании этого параметра настройки.

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

- `noViewRenderer` используется для отключения помощника ViewRenderer. Установите этот параметр в true для его отключения.

- `noErrorHandler` используется для отключения плагина Error Handler Установите этот параметр в true для его отключения.

## Расширение фронт-контроллера

При наследовании от фронт-контроллера, необходимо, как минимум, переопределить метод `getInstance()`.

    class My_Controller_Front extends Zend_Controller_Front
    {
        public static function getInstance()
        {
            if (null === self::$_instance) {
                self::$_instance = new self();
            }

            return self::$_instance;
        }
    }

Такое переопределение метода `getInstance()` гарантирует то, что последующий вызов `Zend_Controller_Front::getInstance()` вернет экземпляр созданного вами подкласса вместо экземпляра `Zend_Controller_Front` - это особенно полезно для некоторых альтернативных маршрутизаторов и помощников видов.

Как правило, нет необходимости создавать подклассы фронт-контроллера, если только не нужно добавить новый функционал (например, автозагрузку плагинов или способ определения путей к помощникам действий). Целями, для достижения которых может потребоваться изменение поведения фронт-контроллера, могут быть изменение принципа хранения директорий контроллеров, установка других используемых по умолчанию маршрутизатора и диспетчера и т.д.