Rendering a form object is completely optional -- you do not need to use
Zend_Form
's render() methods at all. However, if you do,
decorators are used to render the various form objects.
An arbitrary number of decorators may be attached to each item (elements, display groups, sub forms, or the form object itself); however, only one decorator of a given type may be attached to each item. Decorators are called in the order they are registered. Depending on the decorator, it may replace the content passed to it, or append or prepend the content.
Object state is set via configuration options passed to the constructor
or the decorator's setOptions()
method. When creating
decorators via an item's addDecorator()
or related methods,
options may be passed as an argument to the method. These can be used to
specify placement, a separator to use between passed in content and
newly generated content, and whatever options the decorator supports.
Before each decorator's render()
method is called, the
current item is set in the decorator using setElement()
,
giving the decorator awareness of the item being rendered. This allows
you to create decorators that only render specific portions of the item
-- such as the label, the value, error messages, etc. By stringing
together several decorators that render specific element segments, you
can build complex markup representing the entire item.
To configure a decorator, pass an array of options or a
Zend_Config
object to its constructor, an array to
setOptions()
, or a Zend_Config
object to
setConfig()
.
Standard options include:
placement
: Placement can be either 'append' or 'prepend' (case insensitive), and indicates whether content passed torender()
will be appended or prepended, respectively. In the case that a decorator replaces the content, this setting is ignored. The default setting is to append.separator
: The separator is used between the content passed torender()
and new content generated by the decorator, or between items rendered by the decorator (e.g. FormElements uses the separator between each item rendered). In the case that a decorator replaces the content, this setting may be ignored. The default value isPHP_EOL
.
The decorator interface specifies methods for interacting with options. These include:
setOption($key, $value)
: set a single option.getOption($key)
: retrieve a single option value.getOptions()
: retrieve all options.removeOption($key)
: remove a single option.clearOptions()
: remove all options.
Decorators are meant to interact with the various
Zend_Form
class types: Zend_Form
,
Zend_Form_Element
, Zend_Form_DisplayGroup
,
and all classes deriving from them. The method
setElement()
allows you to set the object the
decorator is currently working with, and getElement()
is used to retrieve it.
Each decorator's render()
method accepts a string,
$content
. When the first decorator is called, this
string is typically empty, while on subsequent calls it will be
populated. Based on the type of decorator and the options passed in,
the decorator will either replace this string, prepend the string,
or append the string; an optional separator will be used in the
latter two situations.
Zend_Form
ships with many standard decorators; see
the chapter on Standard
Decorators for details.
If you find your rendering needs are complex or need heavy customization, you should consider creating a custom decorator.
Decorators need only implement
Zend_Decorator_Interface
. The interface specifies the
following:
<?php interface Zend_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); } ?>
To make this simpler, you can simply extend
Zend_Decorator_Abstract
, which implements all methods
except render()
.
As an example, let's say you wanted to reduce the number of
decorators you use, and build a "composite" decorator that took care
of rendering the label, element, any error messages, and description
in an HTML div
. You might build such a 'Composite'
decorator as follows:
<?php class My_Decorator_Composite extends Zend_Form_Decorator_Abstract { public function buildLabel() { $element = $this->getElement(); $label = $element->getLabel(); if ($translator = $element->getTranslator()) { $label = $translator->translate($label); } if ($element->getRequired()) { $label .= '*'; } $label .= ':'; return $element->getView()->formLabel($element->getName(), $label); } public function buildInput() { $element = $this->getElement(); $helper = $element->helper; return $element->getView()->$helper( $element->getName(), $element->getValue(), $element->getAttribs(), $element->options ); } public function buildErrors() { $element = $this->getElement(); $messages = $element->getMessages(); if (empty($messages)) { return ''; } return '<div class="errors">' . $element->getView()->formErrors($messages) . '</div>'; } public function buildDescription() { $element = $this->getElement(); $desc = $element->getDescription(); if (empty($messages)) { return ''; } return '<div class="description">' . $desc . '</div>'; } public function render($content) { $element = $this->getElement(); if (!$element instanceof Zend_Form_Element) { return $content; } if (null === $element->getView()) { return $content; } $separator = $this->getSeparator(); $placement = $this->getPlacement(); $label = $this->buildLabel(); $input = $this->buildInput(); $errors = $this->buildErrors(); $desc = $this->buildDescription(); $output = '<div class="form element">' . $label . $input . $errors . $desc . '</div>' switch ($placement) { case (self::PREPEND): return $output . $separator . $content; case (self::APPEND): default: return $content . $separator . $output; } } } ?>
You can then place this in the decorator path:
<?php // for an element: $element->addPrefixPath('My_Decorator', 'My/Decorator/', 'decorator'); // for all elements: $form->addElementPrefixPath('My_Decorator', 'My/Decorator/', 'decorator'); ?>
You can then specify this decorator as 'Composite' and attach it to an element:
<?php // Overwrite existing decorators with this single one: $element->setDecorators(array('Composite')); ?>
While this example showed how to create a decorator that renders complex output from several element properties, you can also create decorators that handle a single aspect of an element; the 'Decorator' and 'Label' decorators are excellent examples of this practice. Doing so allows you to mix and match decorators to achieve complex output -- and also override single aspects of decoration to customize for your needs.
For example, if you wanted to simply display that an error occurred when validating an element, but not display each of the individual validation error messages, you might create your own 'Errors' decorator:
<?php class My_Decorator_Errors { public function render($content = '') { $output = '<div class="errors">The value you provided was invalid; please try again</div>'; $placement = $this->getPlacement(); $separator = $this->getSeparator(); switch ($placement) { case 'PREPEND': return $output . $separator . $content; case 'APPEND': default: return $content . $separator . $output; } } } ?>
In this particular example, because the decorator's final segment,
'Errors', matches the same as Zend_Form_Decorator_Errors
,
it will be rendered in place of that decorator
-- meaning you would not need to change any decorators to modify the
output. By naming your decorators after existing standard
decorators, you can modify decoration without needing to modify your
elements' decorators.