Uso avanzado de Zend_Form Zend_Form tiene una funcional riqueza, muchas de ellas dirigidas a expertos desarroladores. Este capítulo esta dirigido a documentar algunas de las funcionalidades con ejemplos y casos de uso. Notación de array Muchos desarroladores web experimentados les gusta agrupar relacionados elementos de formulario usando notación de array en los nombres del elemento. Por ejemplo, si se tiene dos direcciones que se desea capturar, un envio y una dirección de facturación, se puede tener elementos idénticos; agrupandolos en un array se puede asegurar que son capturados por separado. Notese el siguiente formulario por ejemplo:
Shipping Address
Billing Address
]]>
En este ejemplo, la facturación y la dirección de envío contienen algunos campos idénticos, eso significa uno puede sobre escribir al otro. Nosotros podemos resolver esta solución usando una notación de array: In this example, the billing and shipping address contain some identical fields, which means one would overwrite the other. We can solve this solution using array notation:
Shipping Address
Billing Address
]]>
En el ejemplo anterior, obtenemos separadas direcciones. En el formulario sometido, ahora tenemos tres elementos, 'guardar' elemento para someterlo, y dos arrays, 'envio' y 'cuenta', cada uno con llaves para los variados elementos. Zend_Form intenta automatizar este proceso con los sub formularios. Por defecto, los sub formularios son generados usando la notación de array como se muestra en el anterior formulario HTML listado completo con identificadores. El nombre del array esta basado en el nombre del sub formulario, con las llaves basados en los elementos contenidos en el sub formulario. Los sub formularios pueder ser anidados arbitrariamente, y esto puede crear arrays anidados que reflejan la estructura. Adicionalmente, las validaciones rutinarias en Zend_Form respetan la estructura del array, asegurando que sus formularios sean validados correctamente, no importa cuan arbitrariamente anidados esten los sub formularios. No se necesita hacer nada para beneficiarse; éste comportamiento esta activo por defecto. Adicionalmente, existen facilidades que le permiten activar condicionalmente la notación de un array, así como también especificar el específico array al cual un elemento o coleccion pertenece: Zend_Form::setIsArray($flag): Definiendo la bandera a verdadero, se puede indicar que un entero formulario deberia ser tratado como un array. Por defecto, el nombre del formulario será usado como el nombre del array, a no ser que setElementsBelongTo() haya sido llamado. si el formulario no tiene un específico nombre, o si setElementsBelongTo() no ha sido definido, esta bandera será ignorada (como no hay nombre del array al cual los elementos puedan pertenecer). Se deberá determinar si un formulario esta siendo tratado como un array usando el acceso isArray(). Zend_Form::setElementsBelongTo($array): Usando este método, se puede especificar el nombre de un array al cual todos los elementos del formulario pertenecen. Se puede determinar el nombre usando el accesor getElementsBelongTo(). Adicionalmente, sobre el nivel del elemento, se puede especificar elementos individuales que puedan pertenecer a arrays particulares usando el método Zend_Form_Element::setBelongsTo(). Para descubrir el valor que tiene -- sea o no sea definido explicitamente o implicitamente a través del formulario -- se puede usar el acceso getBelongsTo().
Formularioss Multi-Página Actualmente, los formularios multi-página no son oficialmente soportados in Zend_Form; sin embargo, mas soporte para implementarlos esta disponible y puede ser utilizado con una pequeña ayuda. La clave para crear fomrularios multi-página es utilizar sub formularios, pero solo para desplegar un solo sub formulario por página. Esto le permite someter un solo sub formulario a la vez y validarlo, pero no procesar el formulario hasta que todos los sub formularios esten completos. Ejemplo de formulario registración Vamos a usar un formulario registración como un ejemplo. para nuestros propósitos, nosotros queremos capturar el nombre del usuario y la contraseña en la primera página, despues la información del usuario -- nombre, apellido, y ubicación -- y finalmente prmitirles decidir que lista de correo, si ellos desean suscribirse. Primero, vamos a crear nuestro propio formulario, y definir varios subformularios dentro del mismo: addElements(array( new Zend_Form_Element_Text('username', array( 'required' => true, 'label' => 'Username:', 'filters' => array('StringTrim', 'StringToLower'), 'validators' => array( 'Alnum', array('Regex', false, array('/^[a-z][a-z0-9]{2,}$/')) ) )), new Zend_Form_Element_Password('password', array( 'required' => true, 'label' => 'Password:', 'filters' => array('StringTrim'), 'validators' => array( 'NotEmpty', array('StringLength', false, array(6)) ) )), )); // Crea un sub formulario de datos demográficos : given name, family name, y // location $demog = new Zend_Form_SubForm(); $demog->addElements(array( new Zend_Form_Element_Text('givenName', array( 'required' => true, 'label' => 'Given (First) Name:', 'filters' => array('StringTrim'), 'validators' => array( array('Regex', false, array('/^[a-z][a-z0-9., \'-]{2,}$/i')) ) )), new Zend_Form_Element_Text('familyName', array( 'required' => true, 'label' => 'Family (Last) Name:', 'filters' => array('StringTrim'), 'validators' => array( array('Regex', false, array('/^[a-z][a-z0-9., \'-]{2,}$/i')) ) )), new Zend_Form_Element_Text('location', array( 'required' => true, 'label' => 'Your Location:', 'filters' => array('StringTrim'), 'validators' => array( array('StringLength', false, array(2)) ) )), )); // Crea un sub fomulario de de correos $listOptions = array( 'none' => 'No lists, please', 'fw-general' => 'Zend Framework General List', 'fw-mvc' => 'Zend Framework MVC List', 'fw-auth' => 'Zend Framwork Authentication and ACL List', 'fw-services' => 'Zend Framework Web Services List', ); $lists = new Zend_Form_SubForm(); $lists->addElements(array( new Zend_Form_Element_MultiCheckbox('subscriptions', array( 'label' => 'Which lists would you like to subscribe to?', 'multiOptions' => $listOptions, 'required' => true, 'filters' => array('StringTrim'), 'validators' => array( array('InArray', false, array(array_keys($listOptions))) ) )), )); // Adjuntando los sub formlarios al formulario principal $this->addSubForms(array( 'user' => $user, 'demog' => $demog, 'lists' => $lists )); } } ]]> Note que hay botones de sometimiento, y que ni hemos hecho nada con el decorador de sub formularios -- lo que significa que por defecto serán desplegados como campos. Necesitaremos hacer algo con ellos mientras desplegamos cada sub formulario indivisualmente, y adicionar botones demanera que podamos actualmente procesarlos -- el cual requerira las propiedades acción y método. vamos adicionar algunos scaffolding a nuestras clases para proveer esa información: {$spec}; } elseif ($spec instanceof Zend_Form_SubForm) { $subForm = $spec; } else { throw new Exception('Invalid argument passed to ' . __FUNCTION__ . '()'); } $this->setSubFormDecorators($subForm) ->addSubmitButton($subForm) ->addSubFormActions($subForm); return $subForm; } /** * Add form decorators to an individual sub form * * @param Zend_Form_SubForm $subForm * @return My_Form_Registration */ public function setSubFormDecorators(Zend_Form_SubForm $subForm) { $subForm->setDecorators(array( 'FormElements', array('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form')), 'Form', )); return $this; } /** * Añade un Boton de envio(submit) a cada sub formulario * * @param Zend_Form_SubForm $subForm * @return My_Form_Registration */ public function addSubmitButton(Zend_Form_SubForm $subForm) { $subForm->addElement(new Zend_Form_Element_Submit( 'save', array( 'label' => 'Save and continue', 'required' => false, 'ignore' => true, ) )); return $this; } /** * Añade el method y el action a cada sub formulario * * @param Zend_Form_SubForm $subForm * @return My_Form_Registration */ public function addSubFormActions(Zend_Form_SubForm $subForm) { $subForm->setAction('/registration/process') ->setMethod('post'); return $this; } } ]]> Siguiente, necesitamos adicionar scaffolding en nuestro controlador acción, y tener muchas consideraciones. Primero, necesitamos asegurar que persiste la información del formulario entre los requerimientos, de esa manera determinar cuadno terminar. Segundo, necesitamos alguna lógica para determinar que segmentos del formulario han sido sometidos, y que sub formulario desplegar de acuerdo a la información. Usaremos Zend_Session_Namespace para persistir la información, el cual nos ayudará a responder la pregunta de cual formulario someter. Vamos a crear nuestro controlador, y adicionar un método para recuperar un formulario instanciado: _form) { $this->_form = new My_Form_Registration(); } return $this->_form; } } ]]> Ahora, vamos adicionar algunas funcionalidades para determinar cual formulario desplegar. Basicamente, hasta que el entero formulario sea considerado válido, necesitamos continuar desplegando segmentos de formulario. Adicionalmente, queremos asegurar que estan en un orden particular: usuario, demog, y despues las listas. Podemos determinar que información ha sido sometida verificando nuestra sesión namespace para claves particulares representando cada sub formulario. _session) { $this->_session = new Zend_Session_Namespace($this->_namespace); } return $this->_session; } /** * Obtiene la lista de Formularios que ya estan almacenados en la sesión * * @return array */ public function getStoredForms() { $stored = array(); foreach ($this->getSessionNamespace() as $key => $value) { $stored[] = $key; } return $stored; } /** * Obtiene la lista de todos los subformularios disponibles * * @return array */ public function getPotentialForms() { return array_keys($this->getForm()->getSubForms()); } /** * Que sub formulario se envio? * * @return false|Zend_Form_SubForm */ public function getCurrentSubForm() { $request = $this->getRequest(); if (!$request->isPost()) { return false; } foreach ($this->getPotentialForms() as $name) { if ($data = $request->getPost($name, false)) { if (is_array($data)) { return $this->getForm()->getSubForm($name); break; } } } return false; } /** * Obtiene el siguiente sub formulario para mostratlo * * @return Zend_Form_SubForm|false */ public function getNextSubForm() { $storedForms = $this->getStoredForms(); $potentialForms = $this->getPotentialForms(); foreach ($potentialForms as $name) { if (!in_array($name, $storedForms)) { return $this->getForm()->getSubForm($name); } } return false; } } ]]> El método de arriba nos permite usar notaciones tal como "$subForm = $this->getCurrentSubForm();" recuperar el actual sub formulario para la validación, o "$next = $this->getNextSubForm();" obtener el siguiente para desplegar. Ahora, vamos a encontrar la manera para procesar y desplegar varios sub formularios. pdemos usar getCurrentSubForm() para determianr si algún sub formulario ha sido sometido (falso retorna ningún valor ha sido desplegado o sometido), y getNextSubForm() recupera el formulario a desplegar. Podemos entonces usar el método del formulario prepareSubForm() para asegurar que el formulario esta listo para desplegar. Cuando tenemos un formulario sometido, podemos validar el sub formulario, y luego verificar si el entero formulario es válido ahora. Para hacer esas tareas, necesitamos métodos adicionales que aseguren que la información sometida es adicionada a la sesión, y que cuando validamos el entero formulario, nosotros validamos contra todos los segmentos de la sesión: getName(); if ($subForm->isValid($data)) { $this->getSessionNamespace()->$name = $subForm->getValues(); return true; } return false; } /** * Es válido todo el formulario? * * @return bool */ public function formIsValid() { $data = array(); foreach ($this->getSessionNamespace() as $key => $info) { $data[$key] = $info; } return $this->getForm()->isValid($data); } } ]]> Ahora que tenemos el trabajo preparado, vamos a construir las acciones para este controlador. Necesitaremos una página landing para el formulario, y luego un 'proceso' acción para procesar el formulario. getCurrentSubForm()) { $form = $this->getNextSubForm(); } $this->view->form = $this->getForm()->prepareSubForm($form); } public function processAction() { if (!$form = $this->getCurrentSubForm()) { return $this->_forward('index'); } if (!$this->subFormIsValid($form, $this->getRequest()->getPost())) { $this->view->form = $this->getForm()->prepareSubForm($form); return $this->render('index'); } if (!$this->formIsValid()) { $form = $this->getNextSubForm(); $this->view->form = $this->getForm()->prepareSubForm($form); return $this->render('index'); } // Formulario Válido! // Render information in a verification page $this->view->info = $this->getSessionNamespace(); $this->render('verification'); } } ]]> Como se ha notado, el código actual para procesar el formulario es relativamente simple. Verificamos si tenemos un actual sub formulario sometido y si no, retornamos a la página landing. Si tenemos un sub formulario, intentaremos validarlo, redesplagandolo si tiene fallas. Si el sub formulario es válido, entonces verificaremos si el formulario es válido, lo que debería indicar hemos terminado; si no, desplegaremos el siguiente segmento del formulario. Finalmente, desplegaremos una página de verificación con el contenido de la sesión. La vista de scripts son muy simples:

registro

form ?>

Gracias por Registrarse!

Aquí está la información que nos ha proporcionado:

info as $info): foreach ($info as $form => $data): ?>

:

$value): ?>
$val): ?>
escape($value) ?>
]]>
Próximas novedades de Zend Framework incluirán componentes para hacer formularios multi páginas mas simple extrayendo la sesión y el lógico orden. Mientras tanto, el ejemplo de arriba debería servir como guia razonable en como alcanzar la tarea para su sitio.