Введение
Zend Framework — замечательная система. Такое мнение у меня сложилось на протяжение долгого времени тесного «общения» с этой системой. И замечательная она не в силу каких-то сверхвозможностей, предоставляемых программисту, а в силу того, что система эта удивительным образом приглашает программиста к собственному усовершенствованию для его, программиста, блага, предлагая простой и в то же время мощный фундамент для собственных разработок.
Работая над проектом с использованием Zend Framework, решил попытаться по максимуму использовать его возможности и сразу же обратил внимание на компонент Zend_Form (я намеренно называю Zend_Form компонентом, а не классом, поскольку компонент Zend_Form состоит из класса Zend_Form и целого набора сопутствующих классов и интерфейсов). В документации сказано достаточно просто: «Zend_Form упрощает создание форм и управление ими в ваших веб-приложениях». В общем-то это так, но без предварительной подготовки с вас семь потов сойдёт прежде, чем вы сможете создать и отобразить одну более или менее сложную форму. Концептуально форма в Zend Framework состоит из:
- элементов
- декораторов
- фильтров
- валидаторов
Декоратор — это вся верстка, которая логически связана с элементом формы (окружает его), но не является его частью. Проще говоря, декоратор — оформление элемента формы.
В данной статье я докажу необходимость существования группового декоратора и опубликую его код. Кому лень читать плод графоманства автора, может сразу перейти к коду.
Так в чём проблема?
Работа с декораторами пока, на мой взгляд, наиболее сложная и кропотливая часть работы по внедрению форм. В примерах, приведенных в документации, все просто замечательно, за исключением одного — все это очень простые, я бы даже сказал, примитивные примеры.
Но как только нужно отобразить что-нибудь вроде этого

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

Как мы видим, в дереве элементов есть две независимые ветки: ячейка таблицы, обрамляющая непосредственно элемент формы (input), и ячейка, обрамляющая метку (label). Беда в том, что label не является элементом формы (это также декоратор). А значит, чтобы создать такую структуру нужно выкручиваться при помощи грязных хаков:
- $this->setElementDecorators(array(
- 'ViewHelper',
- array('decorator' => array('br' => 'HtmlTag'), 'options' => array('tag' => 'span', 'placement' => Zend_Form_Decorator_Abstract::APPEND)),
- array('decorator' => array('tdOpen' => 'HtmlTag'), 'options' => array('tag' => 'td', 'openOnly' => true, 'placement' => Zend_Form_Decorator_Abstract::PREPEND)),
- array('decorator' => array('tdClose' => 'HtmlTag'), 'options' => array('tag' => 'td', 'closeOnly' => true, 'placement' => Zend_Form_Decorator_Abstract::APPEND)),
- array('decorator' => array('label' => 'Label'), 'options' => array('separator' => '*')),
- 'Errors',
- array('decorator' => array('mainCell' => 'HtmlTag'), 'options' => array('tag' => 'td', 'class' => 'tregleft')),
- array('decorator' => array('mainRowClose' => 'HtmlTag'), 'options' => array('tag' => 'tr'))
- ));
-
- $usernameFieldLabel = $this->user_name->getDecorator('label');
- $defaultSeparator = $usernameFieldLabel->getOption('separator');
- $usernameFieldLabel->setOption('separator', $defaultSeparator.'
Логин может состоять только из латинских букв и знака "_".');
* This source code was highlighted with Source Code Highlighter.
Из недостатков:
- ручная подстановка закрывающихся и открывающихся тэгов td
- использование опции separator не по назначению
Решение проблемы
Погуглив, я ничего нормального не нашел и решил написать свой собственный групповой декоратор.
Вот, что получилось:
- <?php
- require_once 'Zend/Form/Decorator/Abstract.php';
-
- class Zend_Form_Decorator_GroupDecorator extends Zend_Form_Decorator_Abstract {
- /**
- * Items (decorators) to group
- * @var array
- */
- protected $_items = null;
-
- /**
- * Temporary form to perform decorators operations
- * @var Zend_Form_Element
- */
- private $_temporaryDecoratorsContainer = null;
-
- /**
- * Constructor
- *
- * @param array|Zend_Config $options
- * @return void
- */
- public function __construct($options = null) {
- parent::__construct($options);
- $this->_temporaryDecoratorsContainer = new Zend_Form_Element('_temporaryDecoratorsContainer', array('DisableLoadDefaultDecorators' => true));
- $this->getItems();
- }
-
- /**
- * Set items to use
- *
- * @param array $items
- * @return Zend_Form_Decorator_GroupDecorator
- */
- public function setItems($items) {
- $this->_items = $this->_temporaryDecoratorsContainer->clearDecorators()->addDecorators($items)->getDecorators();
- return $this;
- }
-
- /**
- * Get tag
- *
- * If no items is registered, either via setItems() or as an option, uses empty array.
- *
- * @return array
- */
- public function getItems() {
- if (null === $this->_items) {
- if (null === ($items = $this->getOption('items'))) {
- $this->setItems(array());
- } else {
- $this->setItems($items);
- $this->removeOption('items');
- }
- }
- return $this->_items;
- }
-
- public function addDecorator($decorator, $options = null) {
- $this->_temporaryDecoratorsContainer->addDecorator($decorator, $options);
- return $this;
- }
-
- public function clearDecorators() {
- $this->_temporaryDecoratorsContainer->clearDecorators();
- $this->_items = array();
- }
-
- public function getDecorator($index = null) {
- if (null === $index) {
- return $this->_items;
- }
- if (is_numeric($index)) {
- $_items = array_values($this->_items);
- return ($index < count($_items))?$_items[$index]:null;
- }
- if (is_string($index)) {
- return (array_key_exists($index, $this->_items))?$this->_items[$index]:null;
- }
- return null;
- }
-
- public function insertDecoratorBefore($index, $decorator, $options = null) {
- $_decoratorsToAdd = $this->_temporaryDecoratorsContainer->clearDecorators()->addDecorator($decorator, $options)->getDecorators();
- if (is_string($index)) {
- $index = array_search($index, array_keys($this->_items));
- }
- if (false !== $index) {
- $first = ($index > 0)?array_slice($this->_items, 0, $index, true):array();
- $last = ($index < count($this->_items))?array_slice($this->_items, $index, null, true):array();
- $this->_items = array_merge($first, (array)$_decoratorsToAdd, $last);
- }
- return $this;
- }
-
- /**
- * Render content wrapped in a group of decorators
- *
- * @param string $content
- * @return string
- */
- public function render($content) {
- $placement = $this->getPlacement();
- $items = $this->getItems();
- $_content = '';
- foreach ($items as $_decorator) {
- if ($_decorator instanceOf Zend_Form_Decorator_Interface) {
- $_decorator->setElement($this->getElement());
- $_content = $_decorator->render($_content);
- }
- else {
- require_once 'Zend/Form/Decorator/Exception.php';
- throw new Zend_Form_Decorator_Exception('Invalid decorator '.$_decorator.' provided; must be string or Zend_Form_Decorator_Interface');
- }
- }
- switch ($placement) {
- case self::APPEND:
- return $content . $_content;
- break;
- case self::PREPEND:
- return $_content . $content;
- break;
- default:
- return $_content.$content.$_content;
- break;
- }
- }
- }
- ?>
* This source code was highlighted with Source Code Highlighter.
Данный декоратор может:
- Создавать и отображать декораторы произвольной вложенности, а значит и какой угодно сложности
- Производить операции с декораторами (добавление, удаление, вставка) на лету, тем самым, позволяя редактировать групповые декораторы, заданные по умолчанию
- <?php
- class Form_MemberRegister extends Zend_Form {
- public function init() {
- $this->setDisableLoadDefaultDecorators(true);
-
- $this->addDecorator('FormElements')
- ->addDecorator(array('table' => 'HtmlTag'), array('tag' => 'table', 'class' => 'treg'))
- ->addDecorator('Form');
-
- $this->addElement('text', 'user_name', array('label' => 'Логин:'));
- $this->addElement('password', 'password', array('label' => 'Пароль:'));
- $this->addElement('password', 'password2', array('label' => 'Повторите пароль:'));
- $this->addElement('text', 'email', array('label' => 'E-mail:'));
-
- $this->setElementDecorators(array(
- array('decorator' => array('labelGroup' => 'GroupDecorator'), 'options' => array('items' => array(
- array('decorator' => 'Text', 'options' => array('text' => '*')),
- array('decorator' => array('span' => 'HtmlTag'), 'options' => array('tag' => 'span', 'class' => 'red')),
- array('decorator' => 'Label','options' => array('placement' => Zend_Form_Decorator_Abstract::PREPEND)),
- array('decorator' => array('labelCell' => 'HtmlTag'), 'options' => array('tag' => 'td', 'class' => 'tregleft'))
- ))),
- array('decorator' => array('elementGroup' => 'GroupDecorator'), 'options' => array('items' => array(
- 'ViewHelper',
- array('decorator' => array('elementCell' => 'HtmlTag'), 'options' => array('tag' => 'td'))
- )), 'placement' => Zend_Form_Decorator_Abstract::APPEND),
- array('decorator' => array('mainRowClose' => 'HtmlTag'), 'options' => array('tag' => 'tr'))
- ));
-
- /**
- * @var Zend_Form_Decorator_GroupDecorator
- */
- $usernameFieldLabel = $this->user_name->getDecorator('labelGroup');
- $usernameFieldLabel->insertDecoratorBefore('labelCell', array('usernameNotes' => $this->_getNotesDecorator($this->user_name, 'Логин может состоять только из латинских букв и знака "_".')));
-
- /**
- * @var Zend_Form_Decorator_GroupDecorator
- */
- $emailFieldDecorator = $this->email->getDecorator('elementGroup');
- $emailFieldDecorator->insertDecoratorBefore('elementCell', array('emailNotes' => $this->_getNotesDecorator($this->email, 'Вводите только существующий и рабочий е-маил. На этот адрес Вам будет отправлена ссылка для подтверждения регистрации.')));
- }
-
- protected function _getNotesDecorator($element, $notesText = '') {
- $_d = new Zend_Form_Decorator_GroupDecorator(array('items' => array(
- array('decorator' => array('notesText' => 'Text'), 'options' => array('text' => $notesText)),
- array('decorator' => array('notesTag' => 'HtmlTag'), 'options' => array('tag' => 'small')),
- array(
- 'decorator' => array('br' => 'HtmlTag'),
- 'options' => array('tag' => 'br', 'openOnly' => true, 'placement' => Zend_Form_Decorator_Abstract::PREPEND)
- )
- )));
- return $_d->setElement($element);
- }
- }
- ?>
* This source code was highlighted with Source Code Highlighter.
Заранее прошу прощения, если пост оказался слишком длинным.
Если есть другое, более изящное решение, поставленной проблемы, дайте знать — я посыплю свою голову пеплом :)
PS: Для противников табличной верстки хочу заметить, что это пост о программировании, я не верстальщик — мне всего лишь нужно было отобразить заданный шаблон.