Глава 7. Zend_Controller

Содержание

7.1. Обзор
7.1.1. Введение
7.1.2. Объект запроса
7.1.3. Процесс маршрутизации
7.1.4. Процесс диспетчеризации
7.1.5. Объект ответа
7.2. Начало работы
7.2.1. Введение
7.2.2. Конфигурация сервера
7.2.3. Файл загрузки
7.2.4. Структура каталогов
7.2.5. Контроллер по умолчанию
7.3. Создание подклассов
7.3.1. Введение
7.3.2. Соглашения
7.3.3. Фронт-контроллер
7.3.4. Абстрактный класс запроса
7.3.5. Интерфейс маршрутизатора
7.3.6. Интерфейс диспетчера
7.3.7. Контроллер действий
7.3.8. Объект ответа
7.4. Готовые подклассы
7.4.1. Введение
7.4.2. Zend_Controller_Request_Http
7.4.3. Zend_Controller_Router_Rewrite
7.4.4. Zend_Controller_Response_Http
7.4.5. Zend_Controller_Response_Cli
7.5. Контроллеры действий
7.5.1. Введение
7.5.2. Инициализация объекта
7.5.3. Перехватчики Pre- и Post-Dispatch
7.5.4. Методы доступа
7.5.5. Вспомогательные методы
7.6. Плагины
7.6.1. Введение
7.6.2. Написание плагинов
7.6.3. Использование плагинов
7.7. Модульная структуры директорий
7.7.1. Введение
7.7.2. Определение директорий контроллеров для модулей
7.7.3. Маршрутизация применительно к модулям
7.7.4. Контроллер, используемый по умолчанию - для модуля или общий
7.8. Исключения
7.8.1. Введение
7.8.2. Как я могу обрабатывать исключения?
7.8.3. Исключения MVC
7.9. Переход с предыдущих версий
7.9.1. Переход с 0.6.0 на 0.8.0
7.9.2. Переход с 0.2.0 или более ранних версий на 0.6.0

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 может также использоваться для сопоставления с произвольными путями, за более подробной информацией см. документацию по Rewrite Router

  • 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.

[Замечание] Гибкость

Если вы хотите большей гибкости, то можете обратиться к документации по Rewrite Router.

Имя контроллера, имя действия в этом контроллере и любые необязательные параметры (включая модуль) устанавливаются в объекте запроса. Когда 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();