Глава 7. Zend_Controller
Пред. |  | След.

## Глава 7. Zend_Controller

## 7.1. Обзор

### 7.1.1. Введение

`Zend_Controller` предоставляет основу для построения веб-сайта, базирующегося на паттерне Model-View-Controller (MVC).

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

Рабочий процесс `Zend_Controller` осуществляется несколькими компонентами. Несмотря на то, что для использования этой системы нет необходимости в совершенном понимании внутренней работы всех компонент, полезно иметь практические знания данного процесса.

- `Zend_Controller_Front` (далее фронт-контроллер) управляет всем рабочим процессом системы `Zend_Controller`. Это интерпретация паттерна FrontController. `Zend_Controller_Front` обрабатывает все запросы, полученные сервером, и в конечном счете отвечает за делегацию запросов контроллерам действий (`Zend_Controller_Action`).

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

  По умолчанию используется `Zend_Controller_Request_Http`. Он предоставляет доступ ко всем переменным HTTP-запроса.

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

  Используемый по умолчанию маршрутизатор - `Zend_Controller_Router_Rewrite`.

  Используемый по умолчанию маршрутизатор `Zend_Controller_Router_Rewrite` принимает конечную точку URI так, как она определена в `Zend_Controller_Request_Http`, и разлагает ее на контроллер, действие и параметры, основываясь на пути в URL. Например, URL `http://localhost/foo/bar/key/value` будет расшифрован следующим образом: контроллер `foo`, действие `bar` и параметр `key` со значением `value`.

  `Zend_Controller_Router_Rewrite` может также использоваться для сопоставления с произвольными путями, за более подробной информацией см. Раздел 7.4.3, «Zend_Controller_Router_Rewrite»

- `Zend_Controller_Dispatcher_Interface` используется для определения диспетчеров. Диспетчеризация — это процесс извлечения контроллера и действия из объекта запроса, нахождения соответствующих файла/класса контроллера и метода действия в классе контроллера. Ситуации, когда соответствующие контроллер или действие не найдены, решаются определением контроллеров и действий по умолчанию.

  Фактически процесс диспетчеризации состоит из инстанцирования класса контроллера и вызова метода действия в этом классе. В отличие от маршрутизации, которая производится только один раз, диспетчеризация производится циклически. Если флаг диспетчеризации объекта запроса сбрасывается, то цикл будет повторяться, при этом вызывается действие, которое будет установлено на данный момент в объекте запроса. Если цикл завершится с установленным флагом диспетчеризации (значение `true`) в объекте запроса, то этим завершается процесс диспетчеризации.

  По умолчанию используется диспетчер `Zend_Controller_Dispatcher_Standard`. Он определяет контроллеры как классы с именами, завершающимися словом Controller, и в которых все "слова" начинаются со сточной буквы, методы действий — как методы с именами, завершающимися словом Action, и в которых все "слова", кроме первого, начинаются со строчной буквы: `SomeFooController::barAction`. В этом случае на контроллер нужно ссылаться как на `somefoo`, а на действие как на `bar`.

  Кроме этого, вы можете определять модуль, используемый при загрузке контроллера. Модуль используется для определения префикса класса и поддиректории, в которой следует искать класс. Для того, чтобы использовать модули, установите параметр `useModules` во фронт-контроллере:

      $front->setParam('useModules', true);

  При этом необходимо установить соответствие директорий модулям. Вы можете делать это как при установке директорий так и при добавлении новой:

      $front->setControllerDirectory(array(
          0      => '/path/to/default/controllers',
          'user' => '/path/to/user/controllers'
      ));

      // OR
      $front->setControllerDirectory('/path/to/default/controllers');
      $front->addControllerDirectory('/path/to/user/controllers', 'user');

  В качестве примера рассмотрим следующий URL:

      http://example.com/user/news/action

  В этом примере указаны модуль 'user' и контроллер 'news'. Если был определен модуль 'user' с соответствующей директорией, то из нее загружается файл `NewsController.php`, при этом ожидается, что в нем определен класс `User_NewsController`. Диспетчер переводит это как "класс `User_NewsController`" и ищет его, используя путь `User/NewsController.php` в путях, заданных через `setControllerDirectory()`.

  Модули полезны, если вы хотите разделить код по директориям, использовать код сторонних разработчиков или повторно использовать одну и ту же библиотеку контроллеров для различных приложений.

- `Zend_Controller_Action` — базовая составляющая контроллера. Каждый контроллер является отдельным классом, наследующим от класса `Zend_Controller_Action`, и этот класс имеет методы действия.

- `Zend_Controller_Response_Abstract` определяет основной класс ответа, используемый для сбора и возвращения ответов из действий контроллеров. Он собирает заголовки и содержимое и, поскольку реализует метод `__toString()`, может выводиться конструкцией echo для отправки всех заголовков и содержимого одновременно.

  По умолчанию используется класс `Zend_Controller_Response_Http`, который пригоден для использования в среде HTTP.

Рабочий процесс `Zend_Controller` относительно простой. Запрос получается объектом `Zend_Controller_Front`, который по очереди вызывает `Zend_Controller_Router_Rewrite` для определения того, какой контроллер (и какое действие в этом контроллере) следует использовать. `Zend_Controller_Router_Rewrite` анализирует URI для установки имен контроллера и действия в запросе. Затем `Zend_Controller_Front` входит в цикл диспетчеризации. Он вызывает `Zend_Controller_Dispatcher_Standard` и передает ему запрос для запуска контроллера и действия, определенных в запросе (или тех, которые используются по умолчанию). После того, как контроллер завершит работу, управление возвращается `Zend_Controller_Front`. Если предыдущий контроллер посредством переустановки статуса диспетчеризации в запросе указал, что должен быть запущен другой контроллер, то цикл продолжается и выполняется запуск другого контроллера. В противном случае процесс завершается.

### 7.1.2. Объект запроса

Объект запроса является простым объектом значений, который передается между `Zend_Controller_Front`, роутером, диспетчером и контроллерами. Он включает в себя определение контроллера, действия и параметров, которые нужно передать действию, а также остальные переменные запроса, будь это HTTP, CLI или PHP-GTK.

- Имя контроллера получается посредством методов `getControllerName()` и `setControllerName()`.

- Имя действия, вызываемого внутри контроллера получается посредством методов `getActionName()` и `setActionName()`.

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

Методов может быть больше, в зависимости от типа запроса. Например, используемый по умолчанию запрос `Zend_Controller_Request_Http` имеет методы для получения URI запроса, пути, параметры $_GET и $_POST и т.д.

Объект запроса передается Zend_Controller_Front либо, если ничего не было передано, инициализируется в начале процесса диспетчеризации до того, как будет запущен прощесс маршрутизации. Он передается каждому объекту в цепочке диспетчеризации.

Кроме этого, объект запроса особенно полезен в тестировании. Разработчики могут имитировать переменные запроса, включая в объект запроса контроллер, действие, параметры, URI и т.д., и затем передавая его фронт-контроллеру для проверки работы приложения. Если комбинируется с объектом ответа, становится возможным тщательное и точное юнит-тестирование приложений на основе MVC.

### 7.1.3. Процесс маршрутизации

Перед тем, как начать писать свой первый контроллер, надо сначала понять, как работает процесс маршрутизации, и как это реализовано в `Zend_Controller_Router_Rewrite`. Помните, что рабочий процесс делится на маршрутизацию, которая выполняется только один раз, и диспетчеризацию, которая выполняется циклически после маршрутизации.

`Zend_Controller_Front` вызывает `Zend_Controller_Router_Rewrite` (или другой установленный маршрутизатор) для определения того, какой контроллер (и какое действие внутри этого контроллера) соответствует данному URI. `Zend_Controller_Router` извлекает URI из объекта запроса и передает его объектам маршрута в своей цепочке; по умолчанию он использует `Zend_Controller_Router_Route_Module` для сопоставления с приходящими URL-ами. Объект маршрута разбирает URL для определения контроллера, действия и других параметров URL, переданных в пути, и устанавливает их в объекте запроса.

`Zend_Controller_Router_Route_Module` использует очень простую схему определения имени контроллера и имени действия в этом контроллере:

    http://framework.zend.com/controller/action/

Обратите внимание, что первый сегмент всегда является именем контроллера, а второй сегмент — именем действия.

В URI могут быть определены параметры, которые будут переданы контроллеру. Они имеют вид пар ключ/значение:

    http://framework.zend.com/controller/action/key1/value1/

Если в пути URI отсутствуют контроллер и действие, то `Zend_Controller_Dispatcher_Standard` попытается получить значения из параметров объекта запроса и, если ничего не найдено, использовать значения по умолчанию. В обоих случаях значениями по умолчанию будет "`index`". Следующие примеры иллюстрируют это:

    http://framework.zend.com/roadmap/future/
    Controller: roadmap
    Action    : future

    http://framework.zend.com/roadmap/
    Controller: roadmap
    Action    : index

    http://framework.zend.com/
    Controller: index
    Action    : index

Кроме этого, вы можете определять контроллеры в поддиректориях или модулях следующими способами:

- Могут использоваться имена контроллеров со знаком подчеркивания (_). Например, `http://framework.zend.com/admin_roadmap/future` должен соответствовать классу контроллера Admin_RoadmapController. Альтернативный разделитель путей может быть задан через метод диспетчера `setPathSeparator()`.

- После установки параметра `useModules` во фронт-контроллере вы можете указывать соответствие поддиректориям, используя чистый URI. URL в предыдущем примере может быть преобразован в `http://framework.zend.com/admin/roadmap/future`. Для этого установите параметр `useModules` во фронт-контроллере или маршрутизаторе и добавьте директорию контроллера как модуль 'admin':

      $router->setParam('useModules', true);
      // ИЛИ
      $front->setParam('useModules', true);

      // И
      $front->addControllerDirectory('/path/to/admin/controllers', 'admin');

  Это будет работать как основного маршрутизатора, так и для RewriteRouter.

| Гибкость
Если вы хотите большей гибкости, то можете обратиться к Раздел 7.4.3, «Zend_Controller_Router_Rewrite».

Имя контроллера, имя действия в этом контроллере и любые необязательные параметры (включая модуль) устанавливаются в объекте запроса. Когда `Zend_Controller_Front` входит в цикл диспетчеризации, объект запроса передается `Zend_Controller_Dispatcher_Standard`.

### 7.1.4. Процесс диспетчеризации

Диспетчеризация — это процесс принятия объекта запроса (`Zend_Controller_Request_Abstract`), извлечения содержащихся в нем имени контроллера, имени действия и необязательных параметров, и затем инстанцирования контроллера, вызова действия в нем. Если не найдены контроллер и действие, то будут использоваться значения, принятые для них по умолчанию. Для обоих `Zend_Controller_Dispatcher_Standard` использует `index` как значение по умолчанию, но позволяет изменять значения по умолчанию через методы `setDefaultController()` и `setDefaultAction()`.

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

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

Метод действия контроллера, который таким образом управляет диспетчеризацией, называется `_forward()`; вызывайте этот метод из любого метода pre/postDispatch() или действия, указывая контроллер, действие и необязательные параметры, которые вы, возможно, захотите передать новому действию.

    public function myAction()
    {
        // выполнение каких-либо действий...
        // передача управления другому действию FooController::barAction()
        // в текущем модуле
        $this->_forward('bar', 'foo', null, array('baz' => 'bogus'));
    }

### 7.1.5. Объект ответа

Объект ответа составляет логичную пару объекту запроса. Его назначение — сбор контента и/или заголовков, таким образом они могут возвращаться вместе. Кроме этого, фронт-контроллер будет передавать любые пойманные исключения объекту ответа, давая разработчику возможность должным образом обрабатывать исключения. Эта возможность может быть отключена установкой `Zend_Controller_Front::throwExceptions(true)`:

    $front->throwExceptions(true);

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

    $response->sendOutput();

Разработчики должны использовать объекты ответа в своих контроллерах. Вместо непосредственного вывода и отправки заголовков, следует помещать их в объект запроса:

    // В действии контроллера:
    // Установка заголовка
    $this->getResponse()
        ->setHeader('Content-Type', 'text/html')
        ->appendBody($content);

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

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

Вы можете получить объект ответа после вызова метода dispatch() фронт-контроллера или указать фронт-контроллеру, чтобы он возвращал объект ответа вместо его вывода.

    // получение объекта ответа после диспетчеризации
    $front->dispatch();
    $response = $front->getResponse();
    if ($response->isException()) {
        // log, mail, etc...
    }

    // или dispatch() возвращает объект ответа
    $front->returnResponse(true);
    $response = $front->dispatch();

    // делаем что-либо...

    // вывод ответа
    $response->sendResponse();

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

    $response->renderExceptions(true);
    $front->dispatch($request, $response);

    // или:
    $front->returnResponse(true);
    $response = $front->dispatch();
    $response->renderExceptions();
    $response->sendResponse();

    // или:
    $front->throwExceptions(true);
    $front->dispatch();

Пред. |  | След.
6.4. Configuring Zend_Console_Getopt | Начало | 7.2. Начало работы