Renderizar un objeto formulario es completamente opcional -- no necesita usar
los métodos render() de Zend_Form en absoluto. Sin embargo, si lo hace,
los decoradores se usan para renderizar los distintos objetos del formulario.
Se puede adjuntar un número arbitrario de decoradores a cada elemento (elementos, grupos de visualización, subformularios o el propio objeto formulario); sin embargo, solo se puede adjuntar un decorador de un tipo dado a cada elemento. Los decoradores se invocan en el orden en que se registran. Dependiendo del decorador, este puede reemplazar el contenido que se le pasa, o añadirlo al final o al principio del contenido.
El estado del objeto se establece mediante opciones de configuración pasadas al constructor
o al método setOptions() del decorador. Al crear
decoradores mediante el método addDecorator() de un elemento o métodos
relacionados, las opciones se pueden pasar como argumento del método. Estas se pueden usar para
especificar la ubicación, un separador a usar entre el contenido pasado y
el contenido recién generado, y cualquier opción que soporte el decorador.
Antes de invocar el método render() de cada decorador, el
elemento actual se establece en el decorador usando setElement(),
lo que da al decorador conocimiento del elemento que se está renderizando. Esto le permite
crear decoradores que solo renderizan porciones específicas del elemento
-- como la etiqueta, el valor, los mensajes de error, etc. Encadenando
varios decoradores que renderizan segmentos de elementos específicos, puede
construir un marcado complejo que represente el elemento completo.
Para configurar un decorador, pase un array de opciones o un
objeto Zend_Config a su constructor, un array a
setOptions(), o un objeto Zend_Config a
setConfig().
Las opciones estándar incluyen:
placement: La ubicación puede ser 'append' o 'prepend' (sin distinción entre mayúsculas y minúsculas), e indica si el contenido pasado a
render()se añadirá al final o al principio, respectivamente. En el caso de que un decorador reemplace el contenido, esta configuración se ignora. La configuración predeterminada es añadir al final (append).separator: El separador se usa entre el contenido pasado a
render()y el nuevo contenido generado por el decorador, o entre elementos renderizados por el decorador (por ejemplo, FormElements usa el separador entre cada elemento renderizado). En el caso de que un decorador reemplace el contenido, esta configuración puede ignorarse. El valor predeterminado esPHP_EOL.
La interfaz del decorador especifica métodos para interactuar con las opciones. Estos incluyen:
setOption($key, $value): establece una única opción.getOption($key): recupera el valor de una única opción.getOptions(): recupera todas las opciones.removeOption($key): elimina una única opción.clearOptions(): elimina todas las opciones.
Los decoradores están pensados para interactuar con los distintos
tipos de clase de Zend_Form: Zend_Form,
Zend_Form_Element, Zend_Form_DisplayGroup,
y todas las clases que derivan de ellas. El método
setElement() le permite establecer el objeto con el que el
decorador está trabajando actualmente, y getElement()
se usa para recuperarlo.
El método render() de cada decorador acepta una cadena,
$content. Cuando se invoca el primer decorador, esta
cadena normalmente está vacía, mientras que en llamadas posteriores estará
poblada. Según el tipo de decorador y las opciones pasadas,
el decorador reemplazará esta cadena, la antepondrá,
o la añadirá al final; se usará un separador opcional en estas dos
últimas situaciones.
Zend_Form viene con muchos decoradores estándar; consulte
el capítulo sobre
decoradores estándar para más detalles.
Si considera que sus necesidades de renderización son complejas o requieren una gran personalización, debería considerar crear un decorador personalizado.
Los decoradores solo necesitan implementar
Zend_Form_Decorator_Interface. La interfaz especifica lo
siguiente:
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);
}
Para simplificar esto, puede simplemente extender
Zend_Form_Decorator_Abstract, que implementa todos los métodos
excepto render().
Como ejemplo, supongamos que quiere reducir el número de decoradores que usa, y crear un decorador "composite" que se encargue de renderizar la etiqueta, el elemento, cualquier mensaje de error, y la descripción en un 'div' de HTML. Podría construir tal decorador 'Composite' de la siguiente manera:
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;
}
}
}
Después puede colocar esto en la ruta de decoradores:
// for an element:
$element->addPrefixPath('My_Decorator',
'My/Decorator/',
'decorator');
// for all elements:
$form->addElementPrefixPath('My_Decorator',
'My/Decorator/',
'decorator');
Después puede especificar este decorador como 'Composite' y adjuntarlo a un elemento:
// Overwrite existing decorators with this single one:
$element->setDecorators(array('Composite'));
Aunque este ejemplo mostró cómo crear un decorador que renderiza una salida compleja a partir de varias propiedades del elemento, también puede crear decoradores que manejen un único aspecto de un elemento; los decoradores 'Decorator' y 'Label' son excelentes ejemplos de esta práctica. Hacerlo le permite mezclar y combinar decoradores para lograr una salida compleja -- y también sobrescribir aspectos individuales de la decoración para personalizarla según sus necesidades.
Por ejemplo, si quisiera simplemente mostrar que ocurrió un error al validar un elemento, pero no mostrar cada uno de los mensajes de error de validación individuales, podría crear su propio decorador 'Errors':
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;
}
}
}
En este ejemplo en particular, dado que el segmento final del decorador,
'Errors', coincide con el de Zend_Form_Decorator_Errors,
se renderizará en lugar de ese decorador
-- lo que significa que no necesitaría cambiar ningún decorador para modificar la
salida. Al nombrar sus decoradores según los decoradores estándar
existentes, puede modificar la decoración sin necesidad de modificar los
decoradores de sus elementos.
Dado que los decoradores pueden apuntar a metadatos distintos del elemento o formulario que decoran, a menudo resulta útil renderizar un decorador individual a la vez. Este comportamiento es posible mediante la sobrecarga de métodos en cada tipo de clase de formulario principal (formularios, subformulario, grupo de visualización, elemento).
Para hacerlo, simplemente invoque render[DecoratorName](), donde
"[DecoratorName]" es el "nombre corto" de su decorador; opcionalmente,
puede pasar el contenido que desea decorar. Por ejemplo:
// render just the element label decorator:
echo $element->renderLabel();
// render just the display group fieldset, with some content:
echo $group->renderFieldset('fieldset content');
// render just the form HTML tag, with some content:
echo $form->renderHtmlTag('wrap this content');
Si el decorador no existe, se lanza una excepción.
Esto puede resultar útil particularmente al renderizar un formulario con el decorador ViewScript; cada elemento puede usar sus decoradores adjuntos para generar contenido, pero con un control preciso.