framework.zend.com
Stable релиз 2.0 / 1.12

Помощники действий в Zend Framework

К комментариям

Содержание

От переводчиков

Данный текст - перевод статьи Action Helpers in Zend Framework, автором которой является Matthew Weier O'Phinney. Оригинал статьи был опубликован 7 апреля 2008 года. Статья написана по Zend Framework версии 1.5, но сохраняет актуальность и для последующих релизов. На текущий момент последний стабильный релиз Zend Framework версии 1.8.1.

Вступление

Существует мнение, что Помощники Действий в Zend Framework настолько сложны в освоении, что понять их могут только профессионалы. Тем не менее, это самый простой способ расширить возможности Контроллеров Действий без создания своих собственных базовых контроллеров с индивидуальным функционалом. Цель данной статьи - показать как можно быстро и легко создать, а потом с пользой использовать Помощники Действий.

Основы

Многие статьи по ZF советуют создавать базовый класс, расширяющий Zend_Controller_Action, для обеспечения ваших контроллеров общими и часто используемыми функциями:

/**
* My_Controller_Action расширяется вашими контроллерами
*/
abstract class My_Controller_Action extends Zend_Controller_Action
{
// Здесь создаем свои методы…
}

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

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

Работа помощников действия обеспечивается брокером. Zend_Controller_Action_HelperBroker содержит статитеческий регистр зарегистрированных помощников, а также служит фабрикой для загрузки помощников по требованию. Свойство класса Zend_Controller_Action - $_helper по умолчанию содержит экземпляр брокера.

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

В основном, вы получаете ваш помощник, используя последнюю часть имени класса. Поэтому, например, если ваш помощник называется 'Foo_Helper_Bar', вам следует ссылаться на 'bar'. У вас есть 2 варианта для его вызова: через свойство брокера или с помощью метода getHelper():

$bar = $this->_helper->bar;
$bar = $this->_helper->getHelper('bar');

Однако, это лишь вершина айсберга.

Метод direct()

Помощники действий могут использовать паттерн Стратегию (Шаблон проектирования). Если вы определяете метод direct() в вашем помощнике, то вы можете вызвать помощник как если бы это был метод брокера.

Один хороший пример лучше тысячи слов. Давайте взглянем на Url помощника, который возвращает URL, основанный на введенных данных.

$url = $this->_helper->url('bar', 'foo'); // "/foo/bar"

Применение метода direct() в помощниках действия - это самый легкий путь добавления виртуальных функций в ваши помощники.

Перехватчики событий

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

  1. init(): вызывается когда происходит инициализация контроллера действия (но только если в брокере уже содержится экземпляр помощника)
  2. preDispatch(): вызывается после операций preDispatch()-перехватчика плагина (но до операций preDispatch()-перехватчика контроллера действия) и только если в брокере уже содержится экземпляр помощника.
  3. postDispatch(): вызывается после операций postDispatch()-перехватчика контроллера действия (но до операций postDispatch()-перехватчика плагина) и только если в брокере уже содержится экземпляр помощника.

 (Прим. переводчика: для лучшего понимания работы всех возможных перехватчиков в Zend Framework, вы можете взглянуть на графическую иллюстрацию в заметке)

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

Например, помощник действия ViewRenderer, который включен по умолчанию в ZF MVC. Он использует следующие перехватчики:

  1. init(): инициализирует объект вида, устанавливает соответствующие текущему контроллеру пути к скриптам, помощникам вида и фильтрам, и сохраняет ссылку на текущий объект вида в свойство 'view' контроллера;
  2. postDispatch(): в этом перехватчике определяется, нужно ли рендерить что-либо, и, если так, производит рендеринг скрипта вида, определяемого по текущему (или запрошенному) действию в установленном сегменте объекта ответа.

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

Регистрация помощников с брокером

Если вы хотите использовать перехватчики, то вам нужно зарегистрировать ваши помощники заранее, как правило, в момент загрузки или в раннее запускаемом плагине. Чтобы сделать это, вам нужно зарегистрировать их с брокером:

Zend_Controller_Action_HelperBroker::addHelper(
new Foo_Helper_Bar()
);

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

// Указываем префикс класса:
Zend_Controller_Action_HelperBroker::addPrefix('Foo_Helper');

// Другой способ, это предоставлять путь к классам с определенным
// префиксом, если они не находятся в include_path
Zend_Controller_Action_HelperBroker::addPath($path, 'Foo_Helper');

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

Статическое возвращение помощников из брокера

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

Например, мне часто бывает нужно сконфигурировать объект помощника ViewRenderer, для установки некоторых путей к видам по-умолчанию:

$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');

$viewRenderer->initView(); // убеждаемся, что объект вида иницилизован
$viewRenderer->view->addHelperPath($path); // устанавливаем путь, где могут находится помощники

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

Создание вашего собственного помощника

Помощники действий должны наследовать класс Zend_Controller_Action_Helper_Abstract. Этот класс содержит следующие сервисные методы:

  1. setActionController(): устанавливает текущий контроллер действия;
  2. getActionController(): получает текущий контроллер действия;
  3. getFrontController(): получает экземпляр текущего фронт-контроллера;
  4. getRequest(): получает текущий объект ответа(использует вначале контроллер действия, который затем ищет во фронт-контроллере);
  5. getResponse(): получает текущий ответный объект (использует вначале контроллер действия, который затем ищет во фронт-контроллере)
  6. getName(): возвращает имя помощника;

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

 Ваш помощник действия может делать всё, что угодно. Если это будет действие общего характера, то вы можете захотеть реализовать его с помощью метода direct(), который был описан ранее. В другом случае - вы можете реализовывать всё так как сами захотите.

Пример: Помощник Получения Формы

Теперь, когда мы многое узнали о помощниках действий, давайте создадим свой. В нашем примере, скажем, у нас будет набор контроллеров, которые используют одну или несколько форм; кроме того, определенная форма может быть использована в нескольких контроллерах. Мы создадим помощник, который позволит вам извлекать форму по имени класса.

Предположим, что классы форм сохраняются в поддиректории «forms» текущего модуля. Мы также предположим, что они в одном пространстве имен с текущим модулем (если мы не в модуле по умолчанию), с прибавлением префикса «Form_»; Например, если у нас есть модуль новости - формы будут с префиксом «News_Form_ ». После этого мы используем имя, переданное помощнику, для определения какой класс формы должен был загружен, используя префикс. Главным образом мы будем использовать метод direct() для взаимодействия с помощником, от которого нам по-настоящему нужно только одно - мы хотим, чтобы помощник загружал формы.

/**
* Помощник действия для загрузки форм
*
* @uses Zend_Controller_Action_Helper_Abstract
*/
class My_Helper_FormLoader extends Zend_Controller_Action_Helper_Abstract
{
/**
* @var Zend_Loader_PluginLoader
*/
public $pluginLoader;

/**
* Конструктор: инициализирует плагин загрузки
*
* @return void
*/
public function __construct()
{
$this->pluginLoader = new Zend_Loader_PluginLoader();
}

/**
* Загружает форму с выбранными опциями
*
* @param string $name
* @param array|Zend_Config $options
* @return Zend_Form
*/
public function loadForm($name, $options = null)
{
$module = $this->getRequest()->getModuleName();
$front = $this->getFrontController();
$default = $front->getDispatcher()
->getDefaultModule();
if (empty($module)) {
$module = $default;
}
$moduleDirectory = $front->getControllerDirectory($module);
$formsDirectory = dirname($moduleDirectory) . '/forms';

$prefix = (('default' == $module) ? '' : ucfirst($module) . '_')
. 'Form_';
$this->pluginLoader->addPrefixPath($prefix, $formsDirectory);

$name = ucfirst((string) $name);
$formClass = $this->pluginLoader->load($name);
return new $formClass($options);
}

/**
* Паттерн Стратегии: вызываем помощник как метод брокера
*
* @param string $name
* @param array|Zend_Config $options
* @return Zend_Form
*/
public function direct($name, $options = null)
{
return $this->loadForm($name, $options);
}
}

Поместим этот код в файл с именем «FormLoader.php» в директорию «My/Helper/» вашей библиотеки (или любую директорию вашего include_path).

Как же мы это будем использовать? Предположим, что в LoginController в модуле по умолчанию, мы хотим загружать форму «login». Назовем ее «Form_Login» и поместим файл класса в «forms/Login.php» вашей директории приложений:

application/
controllers/
LoginController.php
forms/
Login.php - содержит класс 'Form_Login'

В загрузочном файле или ранне загружаемом плагине, мы должны сообщить брокеру, где найти помощники:

Zend_Controller_Action_HelperBroker::addPrefix('My_Helper');

После этого в нашем контроллере мы можем извлечь нашу форму, используя помощник:

$loginForm = $this->_helper->formLoader('login');

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

$regForm = $this->_helper->formLoader('registration');

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

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

Тема для обсуждения на форуме.

Лучший способ следить за обновлениями сайта это подписаться на RSS
Если информация была полезной для вас, вы можете поддержать сайт.
Комментарии:
lcf 14.05.2009 13:51 #
Хороший перевод. На этом сайте как раз не хватало информации по помошникам действий.
Ответить
Alexey 15.05.2009 07:18 #
Статья очень понравилась. Все довольно понятно. Давно искал подход, как вынести часто используемые методы в отдельные независимые классы с удобным подключением к проекту.
Хотелось бы увидеть подобную статью по созданию, подключению и использованию плагинов.
Ответить
Александр Махомет 15.05.2009 09:32 #
Такая статья есть, http://devzone.zend.com/article/3372-Front-Controller-Plugins-in-Zend-Framework, ребята как раз трудятся сейчас над переводом.
Ответить
Zh0rzh 15.05.2009 09:35 #
Скоро будет такая статья. Перевод почти готов.
Ответить
Zh0rzh 20.05.2009 17:46 #
Вот перевод статьи про плагины:

/articles/front-controller-plugins
Ответить
IgorN 15.05.2009 13:04 #
Использую помощники довольно часто, но статья позволила взглянуть на них с другого угла. Спасибо за перевод.
Ответить
LighteR 15.05.2009 17:41 #
Тем не менее, в этом не только нет необходимости, но и зачастую это не лучшее решение с точки зрения расширяемости. В будущем, вы можете обнаружить, что конкретным контроллерам нужны лишь некоторые методы из базового класса, или что вы постоянно добавляете новые методы, нужные только лишь нескольким контроллерам, раздувая при этом базовый класс.;

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

Другое дело, что подобные вещи, возможно, стоит выносить из контроллера по эстетическим соображениям.
Ответить
Александр Махомет 15.05.2009 18:14 #
Если мы будем для каждого ограниченного количества методов создавать наследника, то мы по сути получим ничто иное как помощники действий ;)

Помощники для того и существуют что бы разбить этот один огромный контролер на ряд простых классов, чтобы в определенном контроллере использовать только то что нам нужно.
Ответить
Zh0rzh 18.05.2009 11:00 #
Такая точка зрения тоже имеет место на жизнь, но главное не переборщить и учитывать, что возможно в ваше приложение в дальнейшем будут встраиваться сторонние модули, которые про вашу иерархию вообще ничего не знают.
Ответить
Денис 27.02.2010 15:04 #
Не совсем понятно про $path
Ответить
freeneutron 17.12.2010 09:27 #
Хорошая статья, но хотелось бы проделать то же только с помощниками видов, то есть научиться устанавливать их автоматически без повторений.
Ответить
Чупакабра 19.03.2011 10:13 #
А если создать просто класс где-нибудь в library и в нём определить нужные мне методы, в чем преимущество над этим у хэлперов?
Ответить
cmygeHm 07.11.2011 14:55 #
"вы можете взглянуть на графическую иллюстрацию в заметке"
Заметка - СДОХЛА, хвост облез! Кто ее увидит - тот ее и съест! :)))
Ответить
Munhgauzen 07.03.2012 18:07 #
Кстати да, обидно. И в сети на нее куча ссылок.

На замену надыбал такую:
http://just-code-it.com/blog/php/zend-framework/zend-framework-plugins-action-helpers-and-controllers-life-cycle-during-dispatch.html

Не знаю, может это она и есть? Но мне помогла :)
И еще картинка:
http://lh3.googleusercontent.com/-LR30nAptM_U/T1bZPFpouBI/AAAAAAAAAG8/rr3jvJOTliw/s800/zf-dispatch-lifecycle2.png
Ответить
Уважаемые пользователи. Комментарии не для того чтобы:
  1. Спрашивать почему у вас не работает код, для этого есть тема форума закрепленная за статьей.
  2. Спрашивать как реализовать ту или иную функциональность, для этого необходимо создать свою тему на форуме.

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

Комментарии имеют древовидную структуру.
Если вы хотите ответить на определенный комментарий - нажмите на ссылку "Ответить" возле этого комментария.

Комментарии не соответствующие этим правилам могут быть удалены. Спасибо за понимание.
Комментарии временно отключены, вы можете воспользоваться форумом.