Zend_Form:
Часть 1 – Основы
Часть 2 – Работа с декораторами

В этой статье мы рассмотрим использование декораторов компонента Zend_Form. В официальном руководстве они описаны довольно сложно для понимания, к тому же нужная секция не переведена на русский язык.

Принципы работы

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

setElement($element)
Этот метод служит для передачи в декоратор ссылки на объект Zend_Form или Zend_Form_Element
render($content)
Здесь происходит генерация HTML-кода на основе элемента, переданного через setElement(). $content содержит HTML-код, уже сгенерированный предыдущими декораторами. Соответственно, первый декоратор получит пустую строку.

Хорошо иллюстрирует использование декораторов код Zend_Form_Element::render()

1
2
3
4
5
6
$content = '';
foreach ($this->getDecorators() as $decorator) {
    $decorator->setElement($this);
    $content = $decorator->render($content);
}
return $content;

Приведу ростейший пример использования. В данном случае мы обрамляем нашу форму в тег ‘<div class=”loginForm”></div>’.

1
$form->addDecorator('htmlTag', array('tag' => 'div', 'class' => 'loginForm'));

На выходе мы получаем следующее:

1
2
3
4
5
<div class="loginForm">
    <form method="post" param="value">
        <!-- элементы формы -->
    </form>
</div>

Следует уточнить ещё один момент – декораторы обрабатываются в порядке их добавления, причём возможности изменить сортировку нет.

Стандартные декораторы

Стандартных декораторов на момент написания статьи 12. Основные из них:

FormElements
Этот декоратор предназначен для экземпляров Zend_Form, и вызывается он обычно первым. Задача его состоит в том, чтобы по одному рендерить элементы формы.
HtmlTag
Этот декоратор обрамляет весь HTML-код, сгенерированный предыдущими декораторами, переданным в массиве опций тегом.
Form
Обрамляет весь сгенерированный ранее HTML-код в тег <form>
ViewHelper
Предназначен для экземпляров Zend_Form_Element, служит для вызова нужного хэлпера Zend_View. Вызванный view-хэлпер вернёт код нашего элемента формы
Errors
Тоже предназначен только для элементов формы. При необходимости подставляет HTML-код с сообщениями об некорректости введённых данных
Label
Декоратор подставляет элементу формы метку
Callback
Пожалуй, самый интересный из представленных декораторов. Позволяет в большинстве случаев обойтись без создания собственных декораторов, передав в Zend_Form_Decorator_Callback имя своей функции. За подробностями – в Zend Framework API

Все декораторы поражают гибкостью настройки.
Приведу пример из жизни. Неоднократно ко мне обращались программисты с вопросом, как сменить вид генерируемой формы. По умолчанию, как вы наверное уже знаете, форма генерируется в виде списка определений <dl></dl>. Этот вариант, насколько мне известно, является наиболее подходящим для HTML-формы. Однако программистам, в том числе и мне, более удобно табличное оформление. Следующий код демонстрирует перевод формы в привычный табличный вид.

1
2
3
4
5
6
7
8
9
10
11
12
13
$form = new Zend_Form(array('disableLoadDefaultDecorators' => true));
$form->setMethod('post')
    ->setAction('/mypage')
    ->addDecorator('formElements')
    ->addDecorator('htmlTag', array('tag' => 'table'))
    ->addDecorator('form');
$form->addElement('text', 'username', array('disableLoadDefaultDecorators' => true, 'required' => true, 'label' => 'Логин'));

$form->username->addDecorator('viewHelper')
    ->addDecorator('errors')
    ->addDecorator(array('tdTag' => 'htmlTag'), array('tag' => 'td'))
    ->addDecorator('label', array('tag' => 'td'))
    ->addDecorator(array('trTag' => 'htmlTag'), array('tag' => 'tr'));

Написание собственных декораторов

Здесь тоже всё просто. Ваши декораторы должны наследовать класс Zend_Form_Decorator_Abstract.
Здесь довольно забавная ситуация – Zend_Form_Decorator_Abstract, как следует из названия, абстрактный класс. Однако он не содержит ни одного абстрактного метода – даже метод render() уже объявлен. На момент написания статьи метод Zend_Form_Decorator_Abstract::render() реализован следующим кодом:

1
2
3
4
5
public function render($content)
{
    require_once 'Zend/Form/Decorator/Exception.php';
    throw new Zend_Form_Decorator_Exception('render() not implemented');
}

Причина такого содержимого лично мне непонятна, но я уверен – она есть.
Таким образом, метод render() объявить всё же придётся.
Пример простейшего декоратора, не имеющего никакой практической ценности (практичнее будет использовать Zend_Form_Decorator_HtmlTag):

1
2
3
4
5
6
7
8
class Serkys_Form_Decorator_Div extends Zend_Form_Decorator_Abstract
{

    public function render($content) {
        return "<div>$content</div>";
    }

}

Как видите, совершенно ничего сложного в создании собственных декораторов нет.

Недостатки использования декораторов

Говорят, что если перед курицей начертить прямую линию, она впадает в ступор, длящийся от 30 секунд до нескольких минут. К сожалению, столь интересный опыт я пока не проводил (однако я обязательно попробую), но ступор верстальщика, со смешанными чувствами смотрящего на view-скрипт, состоящий из единственной строки <?php echo $this->form ?> я уже видел.
Значит ли это, что мы снова уходим от разделения логики и представления в смешанный код? Скорее всего нет. Это выход на новый уровень, доработка концепции MVC. Впоследствии, мне кажется, Zend_Form будет в большей степени интегрирован с Zend_View, за счёт чего HTML-код формы будет настраиваться в view-хэлперах или в самих шаблонах, при выводе. Пока же могу предложить только оставлять в своих шаблонах, рядом с выводом формы, ссылку на руководство по Zend_Form_Decorator и, при желании, на эту статью

Спасибо за внимание, всего доброго

Связанные записи

9 Comments

  1. Zh0rzh says:

    Сумбурная статья.
    Новичку не будет понятно, как именно генерируется форма с помощью декораторов.

    Нашел опечатку:
    ” … В данном случае мы обрамляем нашу форму в тег ‘

    ‘. …”

    Я так понимаю, тек пропал.

  2. serkys says:

    Спасибо, поправил.
    По поводу неясности статьи – подскажите, пожалуйста, с чем конкретно возникли трудности, а я распишу подробнее.

  3. guest says:

    Ребята, а что с форумом? :(

  4. Сергей Митрошин says:

    А что с ним не так?
    Нормально работает – http://zend-framework.ru/forum/

  5. sany_sazan says:

    Статья на мой взгляд доходчиво написана

  6. Дмитрий Геннадьевич says:

    Хорошо бы после исходного кода (там, где создаётся объект $form) показать что получается при прорисовке формы (html код).

  7. Сергей Митрошин says:

    Дмитрий, где именно? После примера написания собственного валидатора?

  8. nik :-) says:

    Можно увидеть то же описание декораторов, но в виде *.ini?
    Я попытался перевести код в файл конфига, но отображение совсем не то получается.

    action = “/user/login”
    method = “post”
    disableLoadDefaultDecorators = true
    decorators.elements = “FormElements”
    decorators.htmltag.decorator = “HtmlTag”
    decorators.htmltag.options.tag = “table”
    decorators.htmltag.options.class = “table_form”
    decorators.form = “Form”

    ; элемент username
    elements.username.type = “text”
    elements.username.options.disableLoadDefaultDecorators = true
    elements.username.options.label = “Логин”
    elements.username.options.required = true
    elements.username.options.decorators.viewhelper.decorator = “ViewHelper”
    elements.username.options.decorators.errors.decorator = “Errors”
    elements.username.options.decorators.tdTag.options.tag = “td”
    elements.username.options.decorators.tdTag.decorator = “HtmlTag”
    elements.username.options.decorators.label.decorator = “Label”
    elements.username.options.decorators.label.options.tag = “th”
    elements.username.options.decorators.trTag.decorator = “HtmlTag”
    elements.username.options.decorators.trTag.options.tag = “tr”

  9. Сергей Митрошин says:

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

Leave a Reply