PhpRiot
Become Zend Certified

Prepare for the ZCE exam using our quizzes (web or iPad/iPhone). More info...


When you're ready get 7.5% off your exam voucher using voucher CJQNOV23 at the Zend Store

Custom Decorators

If you find your rendering needs are complex or need heavy customization, you should consider creating a custom decorator.

Decorators need only implement Zend_Form_Decorator_Interface. The interface specifies the following:

<?php
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);
}

To make this simpler, you can simply extend Zend_Form_Decorator_Abstract, which implements all methods except render().

As an example, let's say you want to reduce the number of decorators you use, and build a "composite" decorator to take 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->isRequired()) {
            
$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(
$desc)) {
            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.

Zend Framework