1 <?xml version="1.0" encoding="UTF-8"?>
3 <sect1 id="zend.controller.action">
4 <title>Контроллеры действий</title>
6 <sect2 id="zend.controller.action.introduction">
7 <title>Введение</title>
9 <classname>Zend_Controller_Action</classname> - абстрактный класс,
10 который можно использовать для реализации контроллеров действий
11 для последующего их использования с фронт-контроллером при
12 разработке сайта, основанного на паттерне Model-View-Controller
17 Для того, чтобы использовать <classname>Zend_Controller_Action</classname>,
18 нужно создать его подкласс в своей действующей директории
19 контроллеров (или расширить его для создания своего базового класса
20 контроллеров действий). Работа с ним в основном сводится к
21 созданию его подкласса и написании методов действий, соответствующих
22 различным действиям, которые должен обрабатывать этот контроллер.
23 Маршрутизатор и диспетчер компоненты <classname>Zend_Controller</classname>
24 будут считать за методы действий все методы в классе
25 контроллера с именем, заканчивающимся на 'Action'.
29 Для примера предположим, что ваш класс определен следующим образом:
32 <programlisting language="php"><![CDATA[
33 class FooController extends Zend_Controller_Action
35 public function barAction()
40 public function bazAction()
48 Приведенный выше класс <code>FooController</code> (контроллер
49 <code>foo</code>) определяет два действия - <code>bar</code> и
54 Класс может быть дополнен инициализирующим методом, методом
55 действия по умолчанию (если не был вызван метод, либо
56 вызван несуществующий метод), перехватчиками pre- и post-dispatch и
57 различными вспомогательными методами. Этот раздел служит обзором
58 функционала контроллера действий.
62 <title>Поведение по умолчанию</title>
66 linkend="zend.controller.front">фронт-контроллер</link>
67 активирует помощника действий <link
68 linkend="zend.controller.actionhelpers.viewrenderer">ViewRenderer</link>.
69 Этот помощник обеспечивает добавление объекта вида в контроллер
70 и автоматический рендеринг видов. Вы можете отключить его в
71 своем контроллере действия, используя один из следующих методов:
74 <programlisting language="php"><![CDATA[
75 class FooController extends Zend_Controller_Action
77 public function init()
79 // Локально, только для данного контроллера:
80 $this->_invokeArgs['noViewRenderer'] = true;
83 $this->_helper->removeHelper('viewRenderer');
85 // Тоже глобально, но должен использоваться вместе
86 // с локальной версией для того, чтобы распространить
87 // действие на данный контроллер:
88 Zend_Controller_Front::getInstance()
89 ->setParam('noViewRenderer', true);
95 <code>initView()</code>, <code>getViewScript()</code>,
96 <code>render()</code> и <code>renderScript()</code> служат
97 посредниками для <code>ViewRenderer</code>, пока этот помощник
98 находится в брокере помощников и не установлен флаг
99 <code>noViewRenderer</code>.
103 Вы можете также отключить рендеринг для отдельного вида
104 посредством установки флага <code>noRender</code> в
105 <code>ViewRenderer</code>:
108 <programlisting language="php"><![CDATA[
109 class FooController extends Zend_Controller_Action
111 public function barAction()
113 // отключение авторендеринга для этого действия:
114 $this->_helper->viewRenderer->setNoRender();
120 Основные причины для отключения <code>ViewRenderer</code> - вам
121 просто не нужен объект вида или если вы не производите рендеринг
122 через скрипты вида (например, когда используется контроллер
123 действий для обслуживания протоколов веб-сервисов, таких, как
124 SOAP, XML-RPC, или REST). В большинстве случаев не
125 нужно будет глобально отключать <code>ViewRenderer</code>,
126 только избирательно в отдельных контроллерах или действиях.
131 <sect2 id="zend.controller.action.initialization">
132 <title>Инициализация объекта</title>
135 Несмотря на то, что вы всегда можете переопределить конструктор
136 контроллера действий, мы не рекомендуем делать это.
137 <code>Zend_Controller_Action::__construct()</code>
138 выполняет некоторые важные
139 задачи, такие, как регистрация объектов запроса и ответа, аргументов
140 вызова, переданных из фронт-контроллера. Если необходимо
141 переопределить контроллер, то всегда вызывайте конструктор
142 родительского класса <code>parent::__construct($request, $response,
143 $invokeArgs)</code> в конструкторе подкласса.
147 Более подходящим способом настройки инстанцирования
148 является использование метода <code>init()</code>, который
149 вызывается в конце выполнения <code>__construct()</code>. Например,
150 если вы хотите устанавливать соединение с БД при инстанцировании:
153 <programlisting language="php"><![CDATA[
154 class FooController extends Zend_Controller_Action
156 public function init()
158 $this->db = Zend_Db::factory('Pdo_Mysql', array(
160 'username' => 'user',
161 'password' => 'XXXXXXX',
162 'dbname' => 'website'
169 <sect2 id="zend.controller.action.prepostdispatch">
170 <title>Перехватчики Pre- и Post-Dispatch</title>
173 <classname>Zend_Controller_Action</classname> определяет два метода, которые
174 вызываются до и после требуемого действия,
175 <code>preDispatch()</code> и <code>postDispatch()</code>. Они
176 могут быть полезны в различных случаях - например, проверка
177 аутентификации и списка управления доступом до запуска действия
178 (при вызове метода <code>_forward()</code> в
179 <code>preDispatch()</code> текущее действие будет пропущено) или
180 размещение сгенерированного содержимого в шаблоне боковой части
181 сайта (метод <code>postDispatch()</code>).
185 <sect2 id="zend.controller.action.accessors">
186 <title>Аксессоры</title>
189 С объектом контроллера регистрируется несколько объектов
190 и переменных, они имеют свои методы-аксессоры.
195 <emphasis>Объект запроса</emphasis>: через метод
196 <code>getRequest()</code> извлекается объект запроса, который
197 использовался для вызова данного действия.
202 <emphasis>Объект ответа</emphasis>:
203 через метод <code>getResponse()</code> извлекается объект
204 ответа, объединяющий в себе заголовки и содержимое ответа.
205 Некоторые типичные вызовы могут выглядеть следующим образом:
208 <programlisting language="php"><![CDATA[
209 $this->getResponse()->setHeader('Content-Type', 'text/xml');
210 $this->getResponse()->appendBody($content);
216 <emphasis>Аргументы вызова</emphasis>: фронт-контроллер
217 может добавлять параметры в маршрутизатор, диспетчер и
218 контроллер действий. Для их получения используйте
219 <code>getInvokeArg($key)</code>, можно также извлечь весь список
220 аргументов, используя метод <code>getInvokeArgs()</code>.
226 <emphasis>Параметры запроса</emphasis>: Объект запроса
227 заключает в себе параметры запроса, такие, как значения
228 _GET, _POST, или пользовательские параметры, определенные в
229 пути URL. Для их получения используйте
230 <code>_getParam($key)</code> или
231 <code>_getAllParams()</code>. Вы можете также установить
232 параметры запроса, используя метод <code>_setParam()</code>,
233 это полезно при перенаправлении на другие действия через
234 метод <code>_forward()</code>.
238 Для определения того, существует ли параметр или нет
239 (полезно для логического ветвления), используйте
240 <code>_hasParam($key)</code>.
245 <code>_getParam()</code> может принимать опциональный
246 второй аргумент, содержащий значение по умолчанию,
247 которое используется, если параметр не установлен или
248 пустой. Его использование устраняет необходимость
249 вызова <code>_hasParam()</code> до получения значения:
252 <programlisting language="php"><![CDATA[
253 // Используется значение по умолчанию 1, если id не установлен
254 $id = $this->_getParam('id', 1);
257 if ($this->_hasParam('id') {
258 $id = $this->_getParam('id');
268 <sect2 id="zend.controller.action.viewintegration">
269 <title>Интеграция вида</title>
271 <note id="zend.controller.action.viewintegration.viewrenderer">
272 <title>По умолчанию интеграция вида производится через ViewRenderer</title>
275 Изложенное в этом разделе действительно только в том случае,
276 если вы явным образом отключили
277 <link linkend="zend.controller.actionhelpers.viewrenderer">ViewRenderer</link>.
278 Иначе вы можете спокойно пропустить этот раздел.
283 <classname>Zend_Controller_Action</classname> предоставляет простейший и
284 гибкий механизм интеграции видов. Два метода осуществляют это:
285 <code>initView()</code> и <code>render()</code>. Первый метод
286 выполняет отложенную загрузку открытого свойства <varname>$view</varname>,
287 второй выполняет рендеринг вида, основываясь на запрошенном в данный
288 момент действии, используя иерархию директорий для определения пути
292 <sect3 id="zend.controller.action.viewintegration.initview">
293 <title>Инициализация вида</title>
296 <code>initView()</code> инициализирует объект вида.
297 <code>render()</code> вызывает <code>initView()</code> для
298 извлечения объекта вида, но этот объект может быть
299 инициализирован в любое время. По умолчанию
300 <code>initView()</code> заполняет свойство <varname>$view</varname>
301 объектом <classname>Zend_View</classname>, но может также использоваться
302 любой класс, реализующий интерфейс
303 <classname>Zend_View_Interface</classname>. Если <varname>$view</varname> уже
304 инициализирован, то просто возвращается это свойство.
308 Реализация, используемая по умолчанию, делает следующие
309 предположения по структуре директорий:
312 <programlisting language="php"><![CDATA[
325 Другими словами, предполагается, что скрипты вида находятся в
326 поддиректории <code>views/scripts/</code> и поддиректория
327 <code>views</code> должна содержать родственный функционал того
328 же уровня (это могут быть помощники, фильтры). Когда
329 определяется имя и путь к скрипту вида, то в качестве базового
330 пути используется директория <code>views/scripts/</code>
331 с директориями, именованными в соответствии с отдельными
332 контроллерами, что дает иерархию скриптов вида.
336 <sect3 id="zend.controller.action.viewintegration.render">
337 <title>Рендеринг видов</title>
340 <code>render()</code> имеет следующую сигнатуру:
343 <programlisting language="php"><![CDATA[
344 string render(string $action = null,
346 bool $noController = false);
350 <code>render()</code> рендерит скрипт вида. Если не были
351 переданы аргументы, то предполагается, что запрашивается скрипт
352 <code>[controller]/[action].phtml</code> (где
353 <code>.phtml</code> - значение свойства
354 <varname>$viewSuffix</varname>). Передача значения для
355 <varname>$action</varname> вызовет генерацию этого шаблона в
356 поддиректории <code>[controller]</code>. Для того, чтобы
357 отменить использование поддиректории <code>[controller]</code>,
358 передавайте значение true для <varname>$noController</varname>.
359 Шаблоны рендерятся в объект ответа, если же вы хотите сохранить
361 <link linkend="zend.controller.response.namedsegments">именованный
362 сегмент</link> объекта ответа, то передавайте значение для
363 <varname>$name</varname>.
367 Поскольку имена контроллера и действия могут содержать
368 символы-ограничители слов, такие, как '_', '.', и '-', то
369 <code>render()</code> нормализует их к '-', когда
370 определяет имя скрипта.
371 Внутри себя для такой нормализации он использует
372 ограничители слов и путей для диспетчера. Таким образом,
373 запрос к <code>/foo.bar/baz-bat</code> приведет к рендерингу
374 скрипта <code>foo-bar/baz-bat.phtml</code>. Если ваш метод
375 действия содержит camelCase, то следует иметь в виду, что
376 при определении имени скрипта вида результатом будут
377 разделенные '-' слова.
384 <programlisting language="php"><![CDATA[
385 class MyController extends Zend_Controller_Action
387 public function fooAction()
389 // Рендеринг my/foo.phtml
392 // Рендеринг my/bar.phtml
393 $this->render('bar');
395 // Рендеринг baz.phtml
396 $this->render('baz', null, true);
398 // Рендеринг my/login.phtml в сегмент 'form' объекта ответа
399 $this->render('login', 'form');
401 // Рендеринг site.phtml в сегмент 'page' объекта ответа,
402 // при этом не используется поддиректория 'my/'
403 $this->render('site', 'page', true);
406 public function bazBatAction()
408 // Рендеринг my/baz-bat.phtml
416 <sect2 id="zend.controller.action.utilmethods">
417 <title>Сервисные методы</title>
420 Кроме аксессоров и методов интеграции видов,
421 <classname>Zend_Controller_Action</classname> имеет несколько сервисных
422 методов для выполнения распространенных зачач в методах действий
423 (или в методах pre- и post-dispatch).
429 <code>_forward($action, $controller = null, $module = null,
430 array $params = null)</code>: выполяет другое действие.
431 Если был вызван в <code>preDispatch()</code>, то
432 запрошенноое в данный момент
433 действие будет пропущено в пользу нового. Иначе
434 действие, запрошенное в _forward(), будет выполнено после
435 того, как было выполнено текущее действие.
441 <code>_redirect($url, array $options =
442 array())</code>: производит перенаправление по другому
443 адресу. Этот метод принимает URL и опционально набор опций.
444 По умолчанию он производит перенаправление HTTP 302.
448 Опции могут включать в себя одну или более из следующих:
454 <emphasis>exit:</emphasis> производить или нет выход
455 после этого. Если установлена, то будет произведены
456 надлежащее закрытие всех открытых сессий и
461 Вы можете установить эту опцию глобально в
462 контроллере, используя аксессор
463 <code>setRedirectExit()</code>.
469 <emphasis>prependBase:</emphasis> добавлять или нет
470 базовый URL из объекта запроса в начало данного URL.
474 Вы можете установить эту опцию глобально в
475 контроллере, используя аксессор
476 <code>setRedirectPrependBase()</code>.
482 <emphasis>code:</emphasis> какой код HTTP
483 использовать при перенаправлении. По умолчанию
484 используется HTTP 302. Могут использоваться любые
489 Вы можете установить эту опцию глобально в
490 контроллере, используя аксессор
491 <code>setRedirectCode()</code>.
499 <sect2 id="zend.controller.action.subclassing">
500 <title>Создание подклассов контроллера действий</title>
503 Задумано, что в порядке создания контроллеров действий должны
504 создаваться подклассы от <classname>Zend_Controller_Action</classname>.
505 Как минимум, вам нужно будет определить методы действий, которые
506 может вызывать контроллер.
510 Помимо создания полезного функционала для своих веб-приложений, вы
511 можете также обнаружить, что большинство установок или сервисных
512 методов повторяются в ваших различных контроллерах. В этом случае
513 создание общего базового контроллера, расширяющего
514 <classname>Zend_Controller_Action</classname>, может решить проблему
518 <example id="zend.controller.action.subclassing.example-call">
519 <title>Обрабаботка обращений к несуществующим действиям</title>
522 Если сделан запрос к контроллеру, который содержит в себе
523 не определенный в контроллере метод действия, то вызывается метод
524 <code>Zend_Controller_Action::__call()</code>.
525 <code>__call()</code> является магическим методом для перегрузки
530 По умолчанию этот метод бросает исключение
531 <classname>Zend_Controller_Action_Exception</classname>, означающее, что
532 требуемый метод не найден в контроллере. Если требуемый метод
533 заканчивается строкой 'Action', то предполагается, что было
534 запрошено действие и оно не существует; такая ошибка приводит к
535 исключению с кодом 404. В остальных случаях бросается исключение
536 с кодом 500. Это позволяет легко дифференцировать в обработчике
537 ошибок случаи, когда страница не найдена, и когда произошла
542 Например, если вы хотите выводить сообщение об ошибке, то можете
543 написать нечто подобное:
546 <programlisting language="php"><![CDATA[
547 class MyController extends Zend_Controller_Action
549 public function __call($method, $args)
551 if ('Action' == substr($method, -6)) {
552 // Если метод действия не найден, то рендерится шаблон ошибки
553 return $this->render('error');
556 // все другие методы бросают исключение
557 throw new Exception('Invalid method "'
566 Другая возможность состоит в том, что вы можете
567 производить переход на страницу контроллера по умолчанию:
570 <programlisting language="php"><![CDATA[
571 class MyController extends Zend_Controller_Action
573 public function indexAction()
578 public function __call($method, $args)
580 if ('Action' == substr($method, -6)) {
581 // Если метод действия не был найден, то производится
582 // переход к действию index
583 return $this->_forward('index');
586 // все другие методы бросают исключение
587 throw new Exception('Invalid method "'
597 Как и метод <code>__call()</code>, любые аксессоры,
598 сервисные методы, методы инициализации, вида и перехвата, упомянутые
599 ранее в этом разделе, могут быть переопределены для того, чтобы
600 приспособить свои контроллеры под конкретные нужды. Например, если
601 вы храните свои объекты вида в реестре, то можете модифицировать
602 свой метод <code>initView()</code>:
605 <programlisting language="php"><![CDATA[
606 abstract class My_Base_Controller extends Zend_Controller_Action
608 public function initView()
610 if (null === $this->view) {
611 if (Zend_Registry::isRegistered('view')) {
612 $this->view = Zend_Registry::get('view');
614 $this->view = new Zend_View();
615 $this->view->setBasePath(dirname(__FILE__) . '/../views');
625 Надеемся, из написанного в этом разделе вы смогли увидеть, насколько
626 гибка эта компонента, и как можно заточить ее под нужды своего
627 приложения или сайта.