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

Декораторы Zend_Form

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

Содержание

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

Данный текст - перевод статьи Decorators with Zend_Form, автором которой является Matthew Weier O'Phinney. Оригинал статьи был опубликован 5 мая 2008 года. Статья написана по Zend Framework версии 1.5, но сохраняет актуальность и для последующих релизов. Единственная модификация, которой подвергся предмет статьи - добавление в версии Zend Framework 1.6 стандартного декоратора Captcha.

Вступление

Многие говорят о компоненте Zend_Form как об отличном дополнении к Zend Framework и гибком решении "проблемы форм". Однако, несмотря на гибкость решения, для многих разработчиков оказывается не так просто разобраться с одной из сторон Zend_Form - декораторами. Цель этой статьи - пролить немного света на декораторы, а так же показать некоторые способы создания ваших собственных декораторов и их комбинирования для реализации различных подходов к генерации форм.

Проектирование Zend_Form

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

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

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

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

Для примера, рассмотрим так называемый класс «Окна» (Window). Вы можете создать декоратор окна, который будет это окно отображать (WindowDecorator) и дополнительные декораторы для отображения скроллбаров, заголовка окна и так далее. Каждый декоратор отвечает за единственный аспект финального отображения (декоратор скроллбар - добавляет скроллы, декоратор заголовка, соответственно, заголовок). Например, создание объекта может выглядеть вот таким образом:

    
$window = new WindowScrollbarDecorator(
new WindowTitleDecorator(
new WindowDecorator(
new Window()
)
)
);

И, для вывода итогового «Окна»:


$window->render();

Эта строчка вызывает метод WindowScrollbarDecorator::render(), который, в свою очередь, вызывает метод WindowTitleDecorator::render(), который, затем, вызывает WindowDecorator::render(), который, в заключение, запускает Window::render(). В результате мы получим окно с заголовком и со скроллбарами.

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

 

Основные операции с декораторами

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

Давайте посмотрим как работают декораторы "по умолчанию" для большинства элементов формы. По умолчанию используются следующие декораторы: ViewHelper, Errors, HtmlTag, и, наконец, Label. ViewHelper декоратор по умолчанию заменяет любой переданный контент (например формирует верстку отдельно взятого input'a на основе установленных атрибутов); декоратор ошибок, Errors добавляет контент после каждого элемента (выводит ошибки, если значения формы не прошли проверку после введения); HtmlTag оборачивает контент (по умолчанию он каждый элемент формы "заворачивает" в теги <dd></dd>) и декоратор Label добавляет контент (тег label) перед элементом формы. (Во всех случаях, исключая ViewHelper, подстановка контента (до или после элемента формы) или обертка - может быть изменено по желанию.) Итоговое отображение будет вызвано примерно вот таким образом:


$label->render($htmlTag->render($errors->render($viewHelper->render(''))))

Шаг за шагом, давайте посмотрим как же генерируется верстка:


  1. ''


  2. <input name="foo" id="foo" type="text" value="" />



  3. <input name="foo" id="foo" type="text" value="" />

    <div class="error"><ul>
    <li>...</li>
    </ul></div>



  4. <dd>
    <input name="foo" id="foo" type="text" value="" />

    <div class="error"><ul>
    <li>...</li>
    </ul></div>
    </dd>



  5. <dt><label for="foo" class="optional">Foo</label><dt>
    <dd>

    <input name="foo" id="foo" type="text" value="" />
    <div class="error"><ul>

    <li>...</li>
    </ul></div>
    </dd>

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

 

Настройка вывода с использованием стандартных декораторов

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

  • ViewHelper
  • Errors
  • HtmlTag(<dd>)
  • Label (с оборачиванием в тег <dt>)

Для объекта формы стандартные декораторы это:

  • FormElements (пробегает по списку всех элементов формы, выводит группы, вложенные формы, отвечает за вывод каждого)
  • HtmlTag(<dl>)
  • Form

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

  • FormElements
  • Fieldset
  • DtDdWrapper (оборачивает набор полей (fieldset) в <dd> и добавляет перед ним пустой тег <dt></dt>)

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


$label = $element->getDecorator('label');
$label->setOption('placement', 'append'); // append – после, prepend (по умолчанию) - до

Существуют также множество других свойств (опций), доступных для большинства декораторов; вам следует изучить мануал, и/или документацию по API, если вы хотите разобрать этот момент более детально.

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



$element->removeDecorator('label');

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


$element->setDescription($desc);
$element->setDecorators(array(
'ViewHelper',
'Description',
'Errors',
array('HtmlTag', array('tag' => 'dd')),
array('Label', array('tag' => 'dt')),
));

Несмотря на то, что существуют методы addDecorator() и addDecorators(), в большинстве случаев вам придется использовать именно setDecorators(), за исключением случаев, где вы изначально определяете стек с очень маленьким количеством декораторов.

Во время добавления декоратора вы можете назначить для него «алиас». Это позволит в будущем получать этот декоратор из стека, используя этот алиас. В первую очередь алиасы удобно использовать, если вам надо добавить два или более декораторов одного и того же типа; на самом деле, в таком случае, если вы не определите алиас, последний зарегистрированный декоратор перезапишет все другие объекты декораторов такого же типа! Чтобы установить алиас для декоратора вы передаете в качестве типа декоратора массив с единственной парой ключ/значение, где ключ - это алиас декоратора, а значение - тип декоратора. Например, если вам надо использовать два или более различных HtmlTag декораторов в вашем стеке, вы можете сделать что-то вроде этого:


$element->setDecorators(array(
'ViewHelper',
'Description',
'Errors',
array(array('elementDiv' => 'HtmlTag'), array('tag' => 'div')),
array(array('td' => 'HtmlTag'), array('tag' => 'td')),
array('Label', array('tag' => 'td')),
));


В вышеуказанном примере, элемент формы помещается в HTML тег div, после чего помещается в клетку таблицы (с помощью помещения в теги <td>...</td>). Эти два декоратора имеют алиасы 'elementDiv' и 'td', соответственно.

 

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

Теперь, когда мы знаем, как манипулировать стеком декораторов и отдельно взятыми объектами декораторов, мы рассмотрим список доступных стандартных декораторов:

  • Callback: запускает определенный PHP код, который возвращает контент
  • Description: выводит описание элемента из соответствующего свойства.
  • DtDdWrapper: помещает элемент в теги <dd>...</dd> и добавляет пустой тег <dt></dt> перед элементом.
  • Errors: выводит не упорядоченный список ошибок для элемента, если они есть.
  • Fieldset: выводит контент как набор полей (html тег <fieldset>), используя для легенды (тег <legend>) свойство элемента legend, если оно определено.
  • FormElements: перебирает все элементы формы, вложенной формы или группы элементов выводя каждый (элемент также может быть в свою очередь элементом формы, вложенной формы или группы элементов)
  • Form: оборачивает контент в тег <form>, используя метаданные объекта как атрибуты
  • HtmlTag: помещает элемент в указанный HTML тег, или добавляет тег перед или после элемента (также может быть использован для добавления только открывающего или закрывающего тега)
  • Image: оформляет элемент как изображение ( тег <input type="image" ... /> )
  • Label: выводит текстовую метку для элемента (по умолчанию она помещается перед элементом)
  • ViewHelper: отображает элемент, используя для этого помощник вида (view helper). Помощник вида определяется свойством элемента 'helper', если оно определено, но также может быть задан явно, путем передачи опции 'helpler' в декоратор. По умолчанию заменяет контент элемента на возвращенный хелпером.
  • ViewScript: для оформления элемента используется определенный скрипт вида.

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

Пример: Верстка таблицы

Одна из часто появляющихся в разработке веб-приложений задач - это вывод формы как таблицы HTML. Как же это может быть реализовано с помощью декораторов?

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

Для стандартных элементов, вы должны установить декораторы вот таким образом:


$element->setDecorators(array(
'ViewHelper',
'Errors',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array('Label', array('tag' => 'td')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));

Для кнопок и изображений, мы используем следующий код:


$element->setDecorators(array(
'ViewHelper',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array(array('label' => 'HtmlTag'), array('tag' => 'td', 'placement' => 'prepend')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));


Сама форма будет настраиваться вот таким образом:


$form->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'table')),
'Form',
));

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


<form enctype="application/x-www-form-urlencoded" action="" method="post">

<table>
<tr>
<td><label for="username" class="optional">Логин:</label></td>

<td class="element">
<input type="text" name="username" id="username" value="">
</td>

</tr>
<tr>
<td><label for="firstname" class="optional">Имя:</label></td>

<td class="element">
<input type="text" name="firstname" id="firstname" value="">
</td>

</tr>
<tr>
<td><label for="lastname" class="optional">Фамилия:</label></td>

<td class="element">
<input type="text" name="lastname" id="lastname" value="">
</td>

</tr>
<tr>
<td></td>
<td class="element">
<input type="submit" name="save" id="save" value="Save">

</td>
</tr>
</table>
</form>

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

Вы можете справедливо заметить, что устанавливать декораторы для всех элементов формы вручную не так чтобы уж очень удобно. И вам не придется так поступать, так как есть различные способы этот процесс упростить.

К примеру, вы можете использовать метод объекта формы setElementDecorator(). Он установит все декораторы для всех зарегистрированных на данный момент элементов формы (не для элементов зарегистрированных после вызова метода):


$form->setElementDecorators(array(
'ViewHelper',
'Errors',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array('Label', array('tag' => 'td')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));


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

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


class My_Form_Registration extends Zend_Form
{
public $elementDecorators = array(
'ViewHelper',
'Errors',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array('Label', array('tag' => 'td')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
);

public $buttonDecorators = array(
'ViewHelper',
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
array(array('label' => 'HtmlTag'), array('tag' => 'td', 'placement' => 'prepend')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
);

public function init()
{
$this->addElement('text', 'username', array(
'decorators' => $this->elementDecorators,
'label => 'Username:',
);
$this->addElement('text', 'firstname', array(
'decorators' => $this->elementDecorators,
'label => 'First Name:',
);
$this->addElement('text', 'lastname', array(
'decorators' => $this->elementDecorators,
'label => 'Last Name:',
);
$this->addElement('submit', 'save', array(
'decorators' => $this->buttonDecorators,
'label => 'Save',
);
}

public function loadDefaultDecorators()
{
$this->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'table')),
'Form',
));
}
}


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

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

 

Пример: Полный контроль отображения формы с помощью декоратора ViewScript

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

Этот декоратор оформляет форму, используя указанный скрипт вида, как помощник вида partial, передавая объект формы как переменную вида $form. Это позволяет вам выбирать необходимые метаданные элементов и/или сами элементы и выводить их напрямую в скрипте вида. Также в случае использования этого декоратора вы можете избежать потребности в группах элементов, так как вы можете делать их вручную в скрипте вида.

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


<h4>Пожалуйста, зарегистрируйтесь!</h4>
<form action="<?= $this->escape($this->form->getAction() ?>"

method="<?= $this->escape($this->form->getMethod() ?>">

<fieldset>
<legend>Демографические данные</legend>
<p>

Пожалуйста, предоставьте нам указанную информацию, чтобы мы могли узнать
больше о вас:
</p>
<?= $this->form->age ?>
<?= $this->form->nationality ?>
<?= $this->form->income ?>

</fieldset>

<fieldset>
<legend>Пользовательская информация</legend>
<p>
Теперь, расскажите, пожалуйста, кто вы и как с вами связаться.
</p>

<?= $this->form->firstname ?>
<?= $this->form->lastname ?>
<?= $this->form->email ?>
<?= $this->form->address1 ?>

<?= $this->form->address2 ?>
<?= $this->form->city ?>
<?= $this->form->state ?>
<?= $this->form->postal ?>

<?= $this->form->phone ?>
</fieldset>

<?= $this->form->submit ?>
</form>

Если продемонстрированная выше форма будет в скрипте вида "demogForm.phtml", вы можете использовать её для вашего объекта формы следующим образом:


$form->setDecorators(array(
array('ViewScript', array('viewScript' => 'demogForm.phtml'))
));

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

 

Создание собственного декоратора

Не сомневайтесь: обязательно наступит момент, когда функционала стандартных декораторов станет недостаточно. У вас может быть слишком сложная верстка, чтобы сгенерировать её, используя лишь набор стандартных декораторов, вы можете захотеть уменьшить количество вызовов функций, чтобы оптимизировать ваше приложение, вы можете захотеть скомбинировать несколько различных HTML элементов в один Zend_Form элемент и так далее. В этот момент вам следует начать рассматривать возможность создания собственного декоратора.

Анатомия Декоратора.

Все декораторы реализуют интерфейс Zend_Form_Decorator_Interface, который в упрощенном виде выглядит следующим образом:


interface Zend_Form_Decorator_Interface
{
public function __construct($options = null);
public function setElement($element);
public function getElement();
public function setOptions(array $options);
public function setConfig(Zend_Config $config);
public function setOption($key, $value);
public function getOption($key);
public function getOptions();
public function removeOption($key);
public function clearOptions();
public function render($content);
}

Для вашего удобства все эти методы присутствуют в Zend_Form_Decorator_Abstract, так что все, что вам нужно сделать для вашего декоратора это реализовать метод render():


class My_Form_Decorator_Foo extends Zend_Form_Decorator_Abstract

{
public function render($content)
{
// ...
return $content
}
}

Декоратор хранит текущий объект (который декорируется) как "element", однако, элемент не обязательно должен быть объектом Zend_Form_Element, но также может являться формой, группой элементов или вложенной формой. В результате вы можете создавать декораторы, используемые с любым из этих типов объектов и их метаданными. Получение текущего элемента, возможно с помощью getElement().

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


$view = $this->getElement()->getView();

Если возвращенное значение - null, значит объект вида не был определен в этом элементе.

Ещё два свойства, которые также всегда установлены: separator (разделитель) и placement (позиция подстановки). Вашему методу render() следует использовать эти свойства во время возвращения контента. Если ваш декоратор расширен от Zend_Form_Decorator_Abstract вы можете получать эти свойства с помощью getSeparator() и getPlacement(); иначе проверяйте их наличие с помощью getOption(), так как они обычно передаются как опции. Например:


public function render($content)
{
// ...
$separator = $this->getSeparator();
$placement = $this->getPlacement();

switch ($placment) {
case 'APPEND':
// добавляем новый контент после старого, используя разделитель
return $content . $separator . $newContent;
case 'PREPEND':
// добавляем новый контент перед старым, используя разделитель
return $newContent . $separator . $content;
default:
// заменяем старый контент на новый
return $newContent;
}
}

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

 

Определение местонахождения собственных декораторов для объектов формы или элемента

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

Формы

Формам вы можете сообщить где искать ваши декораторы с помощью addPrefixPath():


// Использование
$form->addPrefixPath($prefix, $path, 'decorator');
// Пример использования
$form->addPrefixPath('My_Form_Decorator', 'My/Form/Decorator/', 'decorator');

Второй аргумент, $path определяет путь до классов, содержащих указанный префикс. Если этот путь у вас в include_path, тогда вы можете использовать относительный путь; если нет - используйте полный путь, чтобы быть уверенным в том, что загрузчик плагинов сможет найти ваши классы. Третий аргумент это тип классов для загрузчика плагина; в нашем случае нам нужно искать только декораторы, поэтому мы используем строку 'decorator'; однако, вы также можете использовать addPrefixPath() чтобы установить пути для других типов классов в загрузчике плагинов, таких как: элементы, валидаторы и фильтры. Вы также можете устанавливать пути к декораторам для разнообразных групповых объектов Zend_Form, таких как группы элементов, вложенные формы и элементы. Это может быть сделано с помощью одного из следующих методов:


  1. addElementPrefixPath($path, $prefix, 'decorator')


  2. addDisplayGroupPrefixPath($path, $prefix)

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



// Программно:
$form->addElementPrefixPath('My_Form_Decorator', 'My/Form/Decorator', 'decorator');
$form->addDisplayGroupPrefixPath('My_Form_Decorator', 'My/Form/Decorator');

// во время создания объекта формы
$form = new Zend_Form(array(
'elementPrefixPath' => array(
array(
'prefix' => 'My_Form_Decorator',
'path' => 'My/Form/Decorator/',
'type' => 'decorator'
),
),
));

// Или используя конфигурационный файл INI
form.elementPrefixPath.my.prefix = "My_Form_Decorator"
form.elementPrefixPath.my.path = "My/Form/Decorator"

form.elementPrefixPath.my.type = "decorator"

// Или используя конфигурационный файл XML
<form>
<elementPrefixPath>
<my>
<prefix>My_Form_Decorator</prefix>

<path>My/Form/Decorator</path>
<type>decorator</type>
</my>
</elementPrefixPath>
</form>


 

Элементы

Чтобы установить путь для загрузчика плагинов для отдельного элемента вы можете вызвать метод addPrefixPath($prefix, $path, 'decorator') на самом элементе, или передать значение prefixPath во время создания объекта. В основном это может быть использовано, если вы знаете что единичный элементы или единичная группа элементов использует собственный декоратор, либо если вам необходимо чтобы некоторые элементы использовали стандартные декораторы, а некоторые - перегруженные.

Варианты использования почти такие же, как и в случае с Zend_Form


// Использование:
$element->addPrefixPath($prefix, $path, 'decorator');

// Пример
$element->addPrefixPath('My_Form_Decorator', 'My/Form/Decorator', 'decorator');

// Конфигурация во время создания
$element = new Zend_Form_Element('foo', array(
'prefixPath' => array(
array(
'type' => 'decorator',
'path' => 'My/Form/Decorator/',
'prefix' => 'My_Form_Decorator'
),
),
));

// или создание используя объект формы
$form->addElement('text', 'foo', array(
'prefixPath' => array(
array(
'type' => 'decorator',
'path' => 'My/Form/Decorator/',
'prefix' => 'My_Form_Decorator'
),
),
));

// Используя конфигурационный файл INI
form.foo.options.prefixPath.my.type = "decorator"

form.foo.options.prefixPath.my.path = "My/Form/Decorator/"
form.foo.options.prefixPath.my.prefix = "My_Form_Decorator"

// Используя конфигурационный файл XML
<form>
<element>
<options>

<prefixPath>
<my>
<type>decorator</type>
<path>My/Form/Decorator/</path>
<prefix>My_Form_Decorator</prefix>

</my>
</prefixPath>
</options>
</element>
</form>

 

Пример: Сгруппированные Чекбоксы

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

В качестве практической задачи мы рассмотрим создание набора сгруппированных по категориям чекбоксов. Чекбоксы будут представлены в виде двухуровневого массива, в котором первый уровень будет определять категорию, а второй будет парами значений/текстовых меток. Мы сгруппируем каждую категорию чекбоксов в набор полей (<fieldset></fieldset>), где название категории будет использоваться как легенда (<legend></legend>) и возле каждого чекбокса будет выведена текстовая метка.

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

Как хорошие разработчики, мы назовем наш декоратор именем, которое говорит само за себя, например 'CategorizedCheckbox', и используем наш собственный префикс класса 'My_Form_Decorator'.

Чтобы начать нам понадобится зарегистрированный элемент, унаследованный от Zend_Form_Element_Multi; таким образом, мы будем точно знать, что getValue() вернёт массив. Также, мы будем использовать помощники вида Zend_View, так что мы должны удостовериться, что объект вида зарегистрирован в элементе. Наконец, мы должны получить объект трансляции (для перевода текстовых меток) для дальнейшего использования.


class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
$element = $this->getElement();
if (!$element instanceof Zend_Form_Element_Multi) {
return $content;
}
if (null === ($view = $element->getView())) {
return $content;
}
$translator = $element->getTranslator();
}
}

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


class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
// ...
$html = '';
$values = (array) $element->getValue();
$baseName = $element->getName();
foreach ($element->getMultiOptions() as $category => $values) {
}
}
}

Теперь мы подошли к самой веселой части: генерации верстки. Так как контент помещается в fieldset, то вначале мы сгенерируем верстку списка чекбоксов, а потом поместим её в fieldset. Мы можем сделать это, используя помощники вида. Также здесь мы переведем наши текстовые метки. Чекбоксам нужны имена и идентификаторы, так что сгенерируем и их, перед передачей в помощник вида formCheckbox(). Когда все Чекбоксы категории готовы, мы оборачиваем их в <fieldset> используя переведенное на нужный язык имя категории как <legend>.


class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
// ...
foreach ($element->getMultiOptions() as $category => $values) {
$boxes = '<ul class="checkboxes">';
foreach ($values as $value => $label) {
if ($translator instanceof Zend_Translate) {
$label = $translator->translate($label);
}
$boxName = $baseName . '[' . $value . ']';
$boxId = $basename . '-' . $value;
$attribs = array(
'id' => $boxId,
'checked' => in_array($value, $values),
);
$boxes .= '<li>'
. $view->formCheckbox($boxName, $value, $attribs)
. $view->formLabel($boxName, $label)
. '</li>';
}
$boxes .= '</ul>';

$legend = ($translator instanceof Zend_Translate)
? $translator->translate($category)
: ucfirst($category);
$attribs = array('legend' => $legend);
$html .= $view->fieldset($category, $boxes, $attribs);
}
}
}


Всё, что нам остаётся - это вернуть сгенерированную верстку. При выводе учтем значение опции подстановки.


class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
public function render($content)
{
// ...
$placement = $this->getPlacement();
$separator = $this->getSeparator();
switch ($placement) {
case 'APPEND':
return $content . $separator . $html;
case 'PREPEND':
return $html . $separator . $content;
case null:
default:
return $html;
}
}
}

Вот и всё! Теперь мы можем использовать наш декоратор с элементами формы.

 

Другие способы настройки декораторов

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

Вам доступно несколько методов для установки свойств декораторов:

  • setOption($key, $value) устанавливает значение одной опции за раз
  • setOptions(array $options) устанавливает значение нескольких опций за раз
  • Также вы можете передать массив опций или объект конфигурации Zend_Config когда добавляете декоратор к элементу формы:


    // Передача опции 'tag' и 'class' во время конструкции декоратора

    $element->addDecorator('HtmlTag', array('tag' => 'div', 'class' => 'foo'));

 

Помощники вида (View Helpers)

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

Заключение

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

Обсудить на форуме

Лучший способ следить за обновлениями сайта это подписаться на RSS
Если информация была полезной для вас, вы можете поддержать сайт.
Комментарии:
Viktor 16.12.2008 02:19 #
Доброй ночи.
Спасибо за статью.
Мое мнение такое, что нет необходимости в этих декораторах...
Специально создавали шаблонизаторы, чтобы разбить работу программиста и дизайнера... Даже если человек делает сайты сам, он делает основной движок, для которого меняет дизайн...
Мне не нравиться когда на рнр пытаються сделать обёртки на ХТМЛ, это излишнее. РНР выполняет все функции шаблонизатора, так зачем писать какие-то сторонние шаблонизаторы, использовать декораторы.
Мне нравиться как Zend решила эту проблему с помощью Zend_View и других помощников вида.
Спасибо за внимание(Моё мнение).
Ответить
san 23.12.2008 14:51 #
Это не только ваше мнение, но главное то, что Zend_Form не навязывает обязательное использование декораторов. Вы можете вывести всю форму руками.

Декораторы на первый взгляд достаточно не понятные, но если над ними немного поработать они становятся достаточно удобной штукой. И что самое главное избавляют от массы рутинной работы по выводу формы, написании всех элементов, контроля надо ошибками и так далее.
Ответить
progLamer 03.03.2009 08:13 #
Небольшое исправление для декоратора ViewScript.

В статье указано с ошибкой:
$form-setDecorators(array(array('ViewScript', array('script' = 'demogForm.phtml'))));

Правильным вариантом будет viewScript
Ответить
lcf 12.03.2009 08:44 #
Ага, поправил... спасибо...
Ответить
maxyc 12.03.2009 08:21 #
одинарные кавычки пропустили... поправьте
Ответить
lcf 12.03.2009 08:45 #
ммм?
Ответить
TpeHeP 16.09.2009 14:37 #
Здесь кавычка после label пропущена (и в других строчках после нее тоже - видимо копипаст)
'label       => 'Username:',
Ответить
ILYAS 29.04.2009 14:56 #
здраствуйте! у меня замечание по поводу примера "Полный контроль отображения формы с помощью декоратора ViewScript".

в том виде как он приведён здесь и в оригинале статьи, скрипт неработоспособен. За исключением синтаксических ошибок, есть ошибка в установке декоратора:
никто за Вас не будет устанавливать переменную вида $form, установка декоратора должна выглядеть так:

$form->setDecorators(array(array('ViewScript', array('viewScript' => 'form.phtml', 'form'=>$form))));

Пришёл к такому выводу, покопавшись в коде зенда.
Ответить
lcf 29.04.2009 15:27 #
Да, я как-то совсем забыл об этом сказать, хотя когда-то столкнулся тоже. Это ошибка допущенная автором статьи за которую он извиняется в комментариях:

Aaargh; wrong variable. Use $this->element, not $this->form.

Перевожу:
Ах тыж екрный ж тыш бабай!; неправильная переменная. Используйте $this->element, а не $this->form.
Ответить
Алекс 23.06.2009 16:10 #
да наверно все это здорово но пока туго воспринимается...
Ответить
alexmegus 03.07.2009 14:39 #
Здравствуйте.
Вопрос следующий:
Можно ли использовать ViewScript для декорирования элемента Zend_Form_Element_File ?

В своем проекте пробовал это сделать, но столкнулся с трудностями, мол, этот декоратор недоступен для данного элемента.
Вот что нагуглил на тему - http://www.zfforums.com/zend-framework-general-discussions-1/general-q-zend-framework-2/zend_form_element_file-viewscript-2451.html

Правда решения все равно не нашлось.

Предложите, пожалуйста, свои варианты.

А задача в том, что нужно к стандартному полю "файл" налепить дополнительную внешнюю обертку.
Ответить
lcf 12.09.2009 01:23 #
http://zendframework.ru/forum/index.php?topic=1686.0

лучш поздно чем никогда :)
Ответить
Lesowik 20.10.2009 19:22 #
Спасибо за перевод! Очень помог понять.

А вот тут ошибочка:

public  $elementDecorators = array(
            'ViewHelper',
            'Errors',
            array(array('data'  => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
            array('Label', array('tag' => 'td')),
            array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
        );

Label с маленькой буквы надо, иначе не заработало.
Ответить
GEMOzloBIN 01.12.2009 13:38 #
В примере ViewScript закрывающие скобки потеряли

<form  action="<?= $this->escape($this->form->getAction()  ?>"        method="<?= $this->escape($this->form->getMethod()  ?>">

замените на
<form  action="<?= $this->escape($this->form->getAction())  ?>"        method="<?= $this->escape($this->form->getMethod())  ?>">
Ответить
vsushkov 26.03.2010 01:31 #
нашел небольшую ошибочку. вместо

        public  function init()
        {
            $this->addElement('text', 'username', array(
                'decorators' => $this->elementDecorators,
                'label       => 'Username:',
            );
            $this->addElement('text', 'firstname', array(
                'decorators' => $this->elementDecorators,
                'label       => 'First Name:',
            );
            $this->addElement('text', 'lastname', array(
                'decorators' => $this->elementDecorators,
                'label       => 'Last Name:',
            );
            $this->addElement('submit', 'save',  array(
                'decorators' => $this->buttonDecorators,
                'label       => 'Save',
            );
        }


следует

        public  function init()
        {
            $this->addElement('text', 'username', array(
                'decorators' => $this->elementDecorators,
                'label'      => 'Username:',
            ));
            
            $this->addElement('text', 'firstname', array(
                'decorators' => $this->elementDecorators,
                'label'       => 'First Name:',
            ));
            $this->addElement('text', 'lastname', array(
                'decorators' => $this->elementDecorators,
                'label'      => 'Last Name:',
            ));
            $this->addElement('submit', 'save',  array(
                'decorators' => $this->buttonDecorators,
                'label'      => 'Save',
            ));
        }
Ответить
Алексей 03.08.2010 11:42 #
Каким образом возможно при помощи Zend_Form создать имя формы?

<form name="MyForm" ... >

т.к. метод setName вместо имени, в форму подставляет ID
Ответить
Уважаемые пользователи. Комментарии не для того чтобы:
  1. Спрашивать почему у вас не работает код, для этого есть тема форума закрепленная за статьей.
  2. Спрашивать как реализовать ту или иную функциональность, для этого необходимо создать свою тему на форуме.

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

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

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