De cuando en cuando, tengo que lidiar con formularios Zend_Form en Zend Framework. Son maravillosos, ya que simplifican enormemente el trabajo, y bien usados pueden aumentar notablemente la calidad de los formularios. Si no estás ya convencido, deja que te cuente alguna de sus ventajas:

  • Separación del formulario de las vistas. Ahora los formularios serán un objeto sobre el que puedes realizar numerosas operaciones. La clase encargada es Zend_Form.
  • Cada elemento del formulario es también un objeto, que hereda de Zend_Form_Element. Es decir, si añades un campo de texto, éste será también un objeto que, entre otras propiedades contiene su nombre, valor, etiqueta, descripción, errores, filtros, validadores, etc. Están todos los elementos de un formulario HTML disponibles en la biblioteca de Zend_Framework
  • Filtros, gracias a Zend_Filter. Podemos hacer que un elemento filtre de forma limpia y elegante su valor, como por ejemplo conversión a mayúsculas. Zend Framework trae un gran número de filtros.
  • Validaciones, gracias a Zend_Validate. Del mismo modo que podemos filtrar, también podemos añadir validaciones. Por ejemplo, que un valor numérico se encuentre en un determinado rango, que cumpla una determinada expresión regular, o que no exceda un determinado número de caracteres.
  • Comprobar el formulario. ¿Queremos saber si el formulario que ha completado el usuario es correcto? Simplemente invocamos el método isValid() del formulario, y sabremos si ha sido correcto.
  • Definir el formulario en un archivo .ini o .xml.

Pero volviendo al hilo, cada vez que tengo que retomar Zend_Form, hay un punto en el que me atasco: los decoradores. Su funcionamiento se basa en el patrón decorador.

Decorator Pattern

¿Qué hace exactamente? Añadir en tiempo de ejecución una nueva funcionalidad (decorar) a un objeto. Se usa mucho en entornos gráficos. Por ejemplo, para añadir nuevas opciones a una ventana o a un elemento gráfico. Nótese que en lugar de eso, podrían crearse subclases que heredaran unas de otras, pero esto conllevaría un problema: el número de clases total seguiría un crecimiento exponencial del tipo 2^funcionalidades. Precisamente ese problema es el que soluciona ese patrón.

Por ejemplo. Supongamos una clase base ‘Cafe’. Para la clase Cafe creamos 4 decoradores: ConAzucar, ConLeche, ConChocolate, ConEspuma. Si quisiéramos resolver todas las combinaciones posible, creando una clase para cada una de ellas, tendríamos 2^4 clases, es decir, 16. Con decoradores, añadimos estas funcionalidades al vuelo.

¿Y en qué afecta esto a Zend_Form? Como hemos dicho, tanto el formulario como los campos de éste, son objetos. Al renderizarse en HTML, muy seguramente, queramos modificar el modo en que se muestran por defecto. Y ahí es donde entra en juego el patrón decorador: Añadimos nuevas funcionalidades dinámicamente, para que por ejemplo un campo de texto esté envuelto en un div, que a su vez pertenece a una celda td.

Nota: La mayoría de la información de este artículo, y ejemplos, los he sacado del artículo Decorators with Zend Forms de la Zend Developer Zone.

La mayoría de elementos de un formulario (por ejemplo un campo de texto, un textarea o un combo) tiene una serie de decoradores “de serie” (el orden importa):

  1. ViewHelper – Es el encargado de renderizar el propio elemento
  2. Errors – En caso de que haya errores (por ejemplo, el valor introducido supera el tamaño máximo), lo mostraría este decorador.
  3. HtmlTag – Envoltura, por defecto con la etiqueta <dd> para el ViewHelper y Errors.
  4. Label – Etiqueta, envuelta en la etiqueta <dt> por defecto.

Se invocan (y anidan de dentro a fuera) en orden, de modo que la anterior pila generaría algo similar a lo siguiente:

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

Visto paso a paso, esto es lo que ocurriría:

Punto de partida

Partimos de una cadena de texto vacía

''

Se renderiza ViewHelper

A continuación se genera el código HTML de la etiqueta. En este ejemplo, al elemento se le había definido como tipo texto con nombre e id igual a “foo”

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

Se renderiza Errors

Posteriormente se añade el código que muestra los errores. Por defecto se pospone (prepend) al contenido que ya hubiera.

<input name="foo" id="foo" type="text" value="" />
<div><ul> <li>...</li> </ul></div>

Se renderiza HtmlTag

En este paso, añade una etiqueta dd (por omisión), que, por defecto, envuelve al contenido que ya hubiera.

<dd>
    <input name="foo" id="foo" type="text" value="" />
    <div><ul>
        <li>...</li>
    </ul></div>
</dd>

Se renderiza Label

Y en último lugar, se genera el código HTML de la etiqueta (por omisión viene envuelto en dd), y lo antepone a lo que ya hubiera (append).

<dt><label for="foo">Foo</label><dt>
<dd>
    <input name="foo" id="foo" type="text" value="" />
    <div><ul>
        <li>...</li>
    </ul></div>
</dd>

¿Cómo modificamos la forma en que se genera el código por defecto? Hay dos vías.

La primera nos permite añadir nuevos decoradores al comienzo de la pila, eliminar alguno de ellos, y/o modificar la ubicación en que se muestra alguno de los que ya hay. Por ejemplo, esto extraería el decorador ‘label’ de la pila de decoradores del elemento, y modificaría su ubicación (valores posibles appendo o prepend):

$label = $element->getDecorator('label');
$label->setOption('placement', 'append');

Del mismo modo, podríamos eliminar de la pila de decoradores el decorador ‘label’ así:

$element->removeDecorator(‘label’);

La segunda es más potente, y nos permite redefinir toda la pila

$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')),
));

Cada elemento dentro del array es un decorador, de modo que en este caso tenemos 6: ‘ViewHelper’, ‘Description’, ‘Errors’, ‘HtmlTag’, ‘HtmlTag’, ‘Label’.

A cada elemento le podemos añadir opciones, como por ejemplo el tag a utilizar. En ese caso, en lugar de utilizar sólo su nombre, lo haríamos así:

array('decorador', array('opción' => 'valor'))

Además, si usamos dos veces un mismo decorador, debemos necesariamente etiquetarle con un nombre que lo identifique de manera unívoca, ya que sino, el último decorador de ese tipo que se añada, sustituirá al anterior. Para poner una etiqueta, lo haríamos así:

array( array('etiqueta' => 'decorador'), array('opción' => 'valor'))

Explicación:

  1. Primero renderiza ViewHelper (V), que es un atajo del elemento (por ejemplo, el campo de entrada). De forma resumida, generaría el siguiente código: <V>.
  2. En segundo lugar, renderiza la descripción (D), que por defecto se ubica tras (prepend) lo que hubiera, de modo que quedaría: <V><D>.
  3. Ahora con los errores (E), los renderiza por defecto antes (append) de lo que hubiera: <E><V><D>.
  4. En el cuarto paso, creamos un decorador de tipo HtmlTag al que etiquetamos como ‘elementDiv’, y que le pasamos como opciones que use el tag ‘div’. Este decorador, por defecto, envuelve lo que hubiera: <div><E><V><D></div>.
  5. Quinto paso, al igual que en el anterior creamos un decorador de tipo HtmlTag, etiquetado como ‘td’, y que usará el tag ‘td’, quedando así: <td><div><E><V><D></div></td>.
  6. Sexto y último paso, renderizamos la etiqueta (L), cuya ubicación por defecto es antes de lo que hubiera (append), y a la que le pasamos una opción adicional para que esté contenida dentro de un tag de tipo ‘td’: <td><div><E><V><D></div></td><td><L></td>.

Ahora ya conocemos la dinámica de cómo decorar un elemento de un formulario. Hemos visto sólo unos pocos (ViewHelper, Description, Errors, HtmlTag y Label). Por cada tipo de campo (input text, button, file, etc.) hay un decorador, siendo ViewHelper sólo un atajo que deduce cuál de ellos usar. Estos algunos decoradores más (aquí la lista completa):

  • Callback – Si no nos queremos complicar, podemos definir una función callback que reciba como parámetro el elemento, para con una sentencia similar a ésta, devolver el nuevo contenido:
    return ’<span>’ . $element->getLabel() . ”</span>”;
  • Form – Es el decorador por defecto del propio formulario. No lo hemos dicho, pero no sólo los elementos del formulario tienen decoradores. También los tienen los formularios en sí.
  • FormElements – Este es el encargado de renderizar cada uno de los elementos dentro de un formulario, es decir, es el que envuelve a nuestros elementos.
  • Image – Para renderizar una etiqueta de tipo img en el formulario. Útil por ejemplo cuando queremos mostrar en un formulario de actualizar perfil de usuario su avatar activo.
  • ViewScript – ¿Que todo lo anterior resulta demasiado farragoso para un simple formulario? No hay problemas. ViewScript permite diseñarlo a la antigua usanza, con una plantilla. Todo esto sin tener que dejar de lado toda la potencia y facilidades que nos ofrece utilizar Zend_Form como sustituto de los formularios no gestionados.

Hay mucho más que ver sobre los decoradores, como por ejemplo, aprender a definir nuestros propios decoradores, o saber cómo hacer uso de ViewScript. En cualquier caso, Matthew Weier O’Phinney lo tiene todo explicado de maravilla en el artículo del Zend Developer Zone que enlazaba al comienzo.

Gracias por leer este artículo :)