[ZF-10089] Zend_Log
[zend.git] / documentation / manual / es / module_specs / Zend_Form-Advanced.xml
bloba806fc4860818766dd159adf5ab5e4d2a8a4528b
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- EN-Revision: 20115 -->
3     <!-- Reviewed: no -->
4 <sect1 id="zend.form.advanced">
5     <title>Uso avanzado de Zend_Form</title>
7     <para>
8         <classname>Zend_Form</classname> tiene una funcional riqueza, muchas de
9         ellas dirigidas a expertos desarroladores. Este capítulo esta dirigido a
10         documentar algunas de las funcionalidades con ejemplos y casos de uso. </para>
12     <sect2 id="zend.form.advanced.arrayNotation">
13         <title>Notación de array</title>
15         <para>A muchos desarroladores web experimentados les gusta agrupar
16             elementos relacionados de formulario usando notación de array en los
17             nombres del elemento. Por ejemplo, si se tienen dos direcciones que
18             se desean capturar, un envío y una dirección de facturación, se
19             pueden tener elementos idénticos; agrupándolos en un array se puede
20             asegurar que son capturados por separado. Nótese el siguiente
21             formulario por ejemplo:</para>
23         <programlisting language="html"><![CDATA[
24 <form>
25     <fieldset>
26         <legend>Shipping Address</legend>
27         <dl>
28             <dt><label for="recipient">Ship to:</label></dt>
29             <dd><input name="recipient" type="text" value="" /></dd>
31             <dt><label for="address">Address:</label></dt>
32             <dd><input name="address" type="text" value="" /></dd>
34             <dt><label for="municipality">City:</label></dt>
35             <dd><input name="municipality" type="text" value="" /></dd>
37             <dt><label for="province">State:</label></dt>
38             <dd><input name="province" type="text" value="" /></dd>
40             <dt><label for="postal">Postal Code:</label></dt>
41             <dd><input name="postal" type="text" value="" /></dd>
42         </dl>
43     </fieldset>
45     <fieldset>
46         <legend>Billing Address</legend>
47         <dl>
48             <dt><label for="payer">Bill To:</label></dt>
49             <dd><input name="payer" type="text" value="" /></dd>
51             <dt><label for="address">Address:</label></dt>
52             <dd><input name="address" type="text" value="" /></dd>
54             <dt><label for="municipality">City:</label></dt>
55             <dd><input name="municipality" type="text" value="" /></dd>
57             <dt><label for="province">State:</label></dt>
58             <dd><input name="province" type="text" value="" /></dd>
60             <dt><label for="postal">Postal Code:</label></dt>
61             <dd><input name="postal" type="text" value="" /></dd>
62         </dl>
63     </fieldset>
65     <dl>
66         <dt><label for="terms">I agree to the Terms of Service</label></dt>
67         <dd><input name="terms" type="checkbox" value="" /></dd>
69         <dt></dt>
70         <dd><input name="save" type="submit" value="Save" /></dd>
71     </dl>
72 </form>
73 ]]></programlisting>
75         <para>En este ejemplo, la facturación y la dirección de envío contienen
76             algunos campos idénticos, eso significa que uno puede sobrescribir
77             al otro. Nosotros podemos resolver esta solución usando una notación
78             de array:</para>
80         <programlisting language="html"><![CDATA[
81 <form>
82     <fieldset>
83         <legend>Shipping Address</legend>
84         <dl>
85             <dt><label for="shipping-recipient">Ship to:</label></dt>
86             <dd><input name="shipping[recipient]" id="shipping-recipient"
87                 type="text" value="" /></dd>
89             <dt><label for="shipping-address">Address:</label></dt>
90             <dd><input name="shipping[address]" id="shipping-address"
91                 type="text" value="" /></dd>
93             <dt><label for="shipping-municipality">City:</label></dt>
94             <dd><input name="shipping[municipality]" id="shipping-municipality"
95                 type="text" value="" /></dd>
97             <dt><label for="shipping-province">State:</label></dt>
98             <dd><input name="shipping[province]" id="shipping-province"
99                 type="text" value="" /></dd>
101             <dt><label for="shipping-postal">Postal Code:</label></dt>
102             <dd><input name="shipping[postal]" id="shipping-postal"
103                 type="text" value="" /></dd>
104         </dl>
105     </fieldset>
107     <fieldset>
108         <legend>Billing Address</legend>
109         <dl>
110             <dt><label for="billing-payer">Bill To:</label></dt>
111             <dd><input name="billing[payer]" id="billing-payer"
112                 type="text" value="" /></dd>
114             <dt><label for="billing-address">Address:</label></dt>
115             <dd><input name="billing[address]" id="billing-address"
116                 type="text" value="" /></dd>
118             <dt><label for="billing-municipality">City:</label></dt>
119             <dd><input name="billing[municipality]" id="billing-municipality"
120                 type="text" value="" /></dd>
122             <dt><label for="billing-province">State:</label></dt>
123             <dd><input name="billing[province]" id="billing-province"
124                 type="text" value="" /></dd>
126             <dt><label for="billing-postal">Postal Code:</label></dt>
127             <dd><input name="billing[postal]" id="billing-postal"
128                 type="text" value="" /></dd>
129         </dl>
130     </fieldset>
132     <dl>
133         <dt><label for="terms">I agree to the Terms of Service</label></dt>
134         <dd><input name="terms" type="checkbox" value="" /></dd>
136         <dt></dt>
137         <dd><input name="save" type="submit" value="Save" /></dd>
138     </dl>
139 </form>
140 ]]></programlisting>
142         <para>En el ejemplo anterior, obtenemos direcciones separadas. En el
143             formulario sometido, ahora tenemos tres elementos, 'guardar'
144             elemento para someterlo, y dos arrays, 'envio' y 'cuenta', cada uno
145             con llaves para los variados elementos.</para>
147         <para>
148             <classname>Zend_Form</classname> intenta automatizar este proceso
149             con los <link linkend="zend.form.forms.subforms"
150                 >subformularios</link> . Por defecto, los subformularios son
151             generados usando la notación de array como se muestra en el anterior
152             formulario <acronym>HTML</acronym> listado completo con
153             identificadores. El nombre del array esta basado en el nombre del
154             subformulario, con las llaves basados en los elementos contenidos en
155             el subformulario. Los subformularios pueder ser anidados
156             arbitrariamente, y esto puede crear arrays anidados que reflejan la
157             estructura. Adicionalmente, las validaciones rutinarias en
158                 <classname>Zend_Form</classname> respetan la estructura del
159             array, asegurando que sus formularios sean validados correctamente,
160             no importa cuan arbitrariamente anidados esten los subformularios.
161             No se necesita hacer nada para beneficiarse; éste comportamiento
162             esta activo por defecto. </para>
164         <para>Adicionalmente, existen facilidades que le permiten activar
165             condicionalmente la notación de un array, así como también
166             especificar el específico array al cual un elemento o coleccion
167             pertenece:</para>
169         <itemizedlist>
170             <listitem>
171                 <para>
172                     <methodname>Zend_Form::setIsArray($flag)</methodname> :
173                     Definiendo la bandera a <constant>TRUE</constant>, se puede indicar que un
174                     formulario entero debería ser tratado como un array. Por
175                     defecto, el nombre del formulario será usado como el nombre
176                     del array, a no ser que
177                         <methodname>setElementsBelongTo()</methodname> haya sido
178                     llamado. Si el formulario no tiene un nombre específico, o
179                     si <methodname>setElementsBelongTo()</methodname> no ha sido
180                     definido, esta bandera será ignorada (como cuando no hay
181                     nombre del array al cual los elementos puedan pertenecer). </para>
183                 <para> Se deberá determinar si un formulario está siendo tratado
184                     como un array usando el accesor
185                         <methodname>isArray()</methodname> . </para>
186             </listitem>
188             <listitem>
189                 <para>
190                     <methodname>Zend_Form::setElementsBelongTo($array)</methodname>
191                     : Usando este método, se puede especificar el nombre de un
192                     array al cual todos los elementos del formulario pertenecen.
193                     Se puede determinar el nombre usando el accesor
194                         <methodname>getElementsBelongTo()</methodname> . </para>
195             </listitem>
196         </itemizedlist>
198         <para> Adicionalmente, a nivel del elemento, se pueden especificar
199             elementos individuales que puedan pertenecer a arrays particulares
200             usando el método
201                 <methodname>Zend_Form_Element::setBelongsTo()</methodname> .
202             Para descubrir el valor que tiene -- sea o no sea definido
203             explícitamente o implícitamente a través del formulario -- se puede
204             usar el accesor <methodname>getBelongsTo()</methodname> . </para>
205     </sect2>
207     <sect2 id="zend.form.advanced.multiPage">
208         <title>Formularios Multi-Página</title>
210         <para> Actualmente, los formularios multi-página no están oficialmente
211             soportados en <classname>Zend_Form</classname> ; sin embargo, la
212             mayoría del soporte para implementarlos está disponible y puede ser
213             utilizado con algunos retoques. </para>
215         <para>La clave para crear fomrularios multi-página es utilizar
216             subformularios, pero solo para mostrar un solo subformulario por
217             página. Esto le permite someter un solo subformulario a la vez y
218             validarlo, pero no procesar el formulario hasta que todos los
219             subformularios esten completos.</para>
221         <example id="zend.form.advanced.multiPage.registration">
222             <title>Ejemplo de formulario de registro</title>
223             <para>Vamos a usar un formulario de registro como ejemplo. Para
224                 nuestros propósitos, queremos capturar el nombre del usuario y
225                 la contraseña en la primera página, después la información del
226                 usuario -- nombre, apellido, y ubicación -- y finalmente
227                 permitirles decidir qué lista de correo, si desean
228                 suscribirse.</para>
230             <para>Primero, vamos a crear nuestro propio formulario, y definir
231                 varios subformularios dentro del mismo:</para>
233             <programlisting language="php"><![CDATA[
234 class My_Form_Registration extends Zend_Form
236     public function init()
237     {
238         // Crea un subformulario usuario: username y password
239         $user = new Zend_Form_SubForm();
240         $user->addElements(array(
241             new Zend_Form_Element_Text('username', array(
242                 'required'   => true,
243                 'label'      => 'Username:',
244                 'filters'    => array('StringTrim', 'StringToLower'),
245                 'validators' => array(
246                     'Alnum',
247                     array('Regex',
248                           false,
249                           array('/^[a-z][a-z0-9]{2,}$/'))
250                 )
251             )),
253             new Zend_Form_Element_Password('password', array(
254                 'required'   => true,
255                 'label'      => 'Password:',
256                 'filters'    => array('StringTrim'),
257                 'validators' => array(
258                     'NotEmpty',
259                     array('StringLength', false, array(6))
260                 )
261             )),
262         ));
264         // Crea un subformulario de datos demográficos : given name, family name, y
265         // location
266         $demog = new Zend_Form_SubForm();
267         $demog->addElements(array(
268             new Zend_Form_Element_Text('givenName', array(
269                 'required'   => true,
270                 'label'      => 'Given (First) Name:',
271                 'filters'    => array('StringTrim'),
272                 'validators' => array(
273                     array('Regex',
274                           false,
275                           array('/^[a-z][a-z0-9., \'-]{2,}$/i'))
276                 )
277             )),
279             new Zend_Form_Element_Text('familyName', array(
280                 'required'   => true,
281                 'label'      => 'Family (Last) Name:',
282                 'filters'    => array('StringTrim'),
283                 'validators' => array(
284                     array('Regex',
285                           false,
286                           array('/^[a-z][a-z0-9., \'-]{2,}$/i'))
287                 )
288             )),
290             new Zend_Form_Element_Text('location', array(
291                 'required'   => true,
292                 'label'      => 'Your Location:',
293                 'filters'    => array('StringTrim'),
294                 'validators' => array(
295                     array('StringLength', false, array(2))
296                 )
297             )),
298         ));
300         // Crea un sub fomulario de correos
301         $listOptions = array(
302             'none'        => 'No lists, please',
303             'fw-general'  => 'Zend Framework General List',
304             'fw-mvc'      => 'Zend Framework MVC List',
305             'fw-auth'     => 'Zend Framwork Authentication and ACL List',
306             'fw-services' => 'Zend Framework Web Services List',
307         );
308         $lists = new Zend_Form_SubForm();
309         $lists->addElements(array(
310             new Zend_Form_Element_MultiCheckbox('subscriptions', array(
311                 'label'        =>
312                     'Which lists would you like to subscribe to?',
313                 'multiOptions' => $listOptions,
314                 'required'     => true,
315                 'filters'      => array('StringTrim'),
316                 'validators'   => array(
317                     array('InArray',
318                           false,
319                           array(array_keys($listOptions)))
320                 )
321             )),
322         ));
324         // Adjuntando los subformlarios al formulario principal
325         $this->addSubForms(array(
326             'user'  => $user,
327             'demog' => $demog,
328             'lists' => $lists
329         ));
330     }
332 ]]></programlisting>
334             <para>Note que no hay botones de enviar, y que ni hemos hecho nada
335                 con los decoradores de subformularios -- lo que significa que
336                 por defecto serán desplegados como campos. Necesitaremos hacer
337                 algo con ellos mientras desplegamos cada subformulario
338                 individualmente, y añadir botones de manera que podamos
339                 procesarlos realmente -- el cual requerira las propiedades
340                 acción y método. Vamos a añadir algunos andamios a nuestras
341                 clases para proveer esa información:</para>
343             <programlisting language="php"><![CDATA[
344 class My_Form_Registration extends Zend_Form
346     // ...
348     /**
349      * Prepara un subformulario para mostrar
350      *
351      * @param  string|Zend_Form_SubForm $spec
352      * @return Zend_Form_SubForm
353      */
354     public function prepareSubForm($spec)
355     {
356         if (is_string($spec)) {
357             $subForm = $this->{$spec};
358         } elseif ($spec instanceof Zend_Form_SubForm) {
359             $subForm = $spec;
360         } else {
361             throw new Exception('Invalid argument passed to ' .
362                                 __FUNCTION__ . '()');
363         }
364         $this->setSubFormDecorators($subForm)
365              ->addSubmitButton($subForm)
366              ->addSubFormActions($subForm);
367         return $subForm;
368     }
370     /**
371      * Add form decorators to an individual sub form
372      *
373      * @param  Zend_Form_SubForm $subForm
374      * @return My_Form_Registration
375      */
376     public function setSubFormDecorators(Zend_Form_SubForm $subForm)
377     {
378         $subForm->setDecorators(array(
379             'FormElements',
380             array('HtmlTag', array('tag' => 'dl',
381                                    'class' => 'zend_form')),
382             'Form',
383         ));
384         return $this;
385     }
387     /**
388      * Añade un Boton de envio(submit) a cada subformulario
389      *
390      * @param  Zend_Form_SubForm $subForm
391      * @return My_Form_Registration
392      */
393     public function addSubmitButton(Zend_Form_SubForm $subForm)
394     {
395         $subForm->addElement(new Zend_Form_Element_Submit(
396             'save',
397             array(
398                 'label'    => 'Save and continue',
399                 'required' => false,
400                 'ignore'   => true,
401             )
402         ));
403         return $this;
404     }
406     /**
407      * Añade el method y el action a cada subformulario
408      *
409      * @param  Zend_Form_SubForm $subForm
410      * @return My_Form_Registration
411      */
412     public function addSubFormActions(Zend_Form_SubForm $subForm)
413     {
414         $subForm->setAction('/registration/process')
415                 ->setMethod('post');
416         return $this;
417     }
419 ]]></programlisting>
421             <para> Siguiente, necesitamos añadir andamios a nuestro action
422                 controller, y tener varias consideraciones. Primero, necesitamos
423                 asegurar que persiste la información del formulario entre los
424                 requerimientos, de esa manera determinar cuándo terminar.
425                 Segundo, necesitamos alguna lógica para determinar qué segmentos
426                 del formulario han sido sometidos, y qué subformulario mostrar
427                 de acuerdo a la información. Usaremos
428                     <classname>Zend_Session_Namespace</classname> para persistir
429                 la información, el cual nos ayudará a responder la pregunta de
430                 qué formulario someter. </para>
432             <para>Vamos a crear nuestro controlador, y añadir un método para
433                 recuperar un formulario instanciado:</para>
435             <programlisting language="php"><![CDATA[
436 class RegistrationController extends Zend_Controller_Action
438     protected $_form;
440     public function getForm()
441     {
442         if (null === $this->_form) {
443             $this->_form = new My_Form_Registration();
444         }
445         return $this->_form;
446     }
448 ]]></programlisting>
450             <para>Ahora, vamos a añadir algunas funcionalidades para determinar
451                 qué formulario mostrar. Básicamente, hasta que el formulario
452                 entero sea considerado válido, necesitamos continuar mostrando
453                 segmentos de formulario. Adicionalmente, queremos asegurar que
454                 están en un orden particular: usuario, demog, y después las
455                 listas. Podemos determinar qué información ha sido sometida
456                 verificando nuestro session namespace para claves particulares
457                 representando cada subformulario.</para>
459             <programlisting language="php"><![CDATA[
460 class RegistrationController extends Zend_Controller_Action
462     // ...
464     protected $_namespace = 'RegistrationController';
465     protected $_session;
467     /**
468      * Obtiene el namespace de la sesión que estamos usando
469      *
470      * @return Zend_Session_Namespace
471      */
472     public function getSessionNamespace()
473     {
474         if (null === $this->_session) {
475             $this->_session =
476                 new Zend_Session_Namespace($this->_namespace);
477         }
479         return $this->_session;
480     }
482     /**
483      * Obtiene la lista de Formularios que ya están almacenados en la sesión
484      *
485      * @return array
486      */
487     public function getStoredForms()
488     {
489         $stored = array();
490         foreach ($this->getSessionNamespace() as $key => $value) {
491             $stored[] = $key;
492         }
494         return $stored;
495     }
497     /**
498      * Obtiene la lista de todos los subformularios disponibles
499      *
500      * @return array
501      */
502     public function getPotentialForms()
503     {
504         return array_keys($this->getForm()->getSubForms());
505     }
507     /**
508      * ¿Qué subformulario se envio?
509      *
510      * @return false|Zend_Form_SubForm
511      */
512     public function getCurrentSubForm()
513     {
514         $request = $this->getRequest();
515         if (!$request->isPost()) {
516             return false;
517         }
519         foreach ($this->getPotentialForms() as $name) {
520             if ($data = $request->getPost($name, false)) {
521                 if (is_array($data)) {
522                     return $this->getForm()->getSubForm($name);
523                     break;
524                 }
525             }
526         }
528         return false;
529     }
531     /**
532      * Obtiene el siguiente subformulario para mostrarlo
533      *
534      * @return Zend_Form_SubForm|false
535      */
536     public function getNextSubForm()
537     {
538         $storedForms    = $this->getStoredForms();
539         $potentialForms = $this->getPotentialForms();
541         foreach ($potentialForms as $name) {
542             if (!in_array($name, $storedForms)) {
543                 return $this->getForm()->getSubForm($name);
544             }
545         }
547         return false;
548     }
550 ]]></programlisting>
552             <para> El método de arriba nos permite usar notaciones tal como "
553                     <command>$subForm =
554                     $this-&gt;getCurrentSubForm();</command> " recuperar el
555                 actual subformulario para la validación, o " <command>$next =
556                     $this-&gt;getNextSubForm();</command> " obtener el
557                 siguiente para mostrar. </para>
559             <para> Ahora, vamos a encontrar la manera para procesar y mostrar
560                 varios subformularios. Podemos usar
561                     <methodname>getCurrentSubForm()</methodname> para determinar
562                 si algún subformulario ha sido sometido (los valores de retorno
563                 <constant>FALSE</constant> indican que ninguno ha sido desplegado o sometido), y
564                     <methodname>getNextSubForm()</methodname> recupera el
565                 formulario que mostrar. Podemos entonces usar el método del
566                 formulario <methodname>prepareSubForm()</methodname> para
567                 asegurar que el formulario está listo para mostrar. </para>
569             <para>Cuando tenemos un formulario sometido, podemos validar el
570                 subformulario, y luego verificar si el formulario entero es
571                 válido ahora. Para hacer esas tareas, necesitamos métodos
572                 adicionales que aseguren que la información sometida es añadida
573                 a la sesión, y que cuando validamos el formulario entero,
574                 nosotros validamos contra todos los segmentos de la
575                 sesión:</para>
577             <programlisting language="php"><![CDATA[
578 class RegistrationController extends Zend_Controller_Action
580     // ...
582     /**
583      * ¿Es válido el subformulario?
584      *
585      * @param  Zend_Form_SubForm $subForm
586      * @param  array $data
587      * @return bool
588      */
589     public function subFormIsValid(Zend_Form_SubForm $subForm,
590                                    array $data)
591     {
592         $name = $subForm->getName();
593         if ($subForm->isValid($data)) {
594             $this->getSessionNamespace()->$name = $subForm->getValues();
595             return true;
596         }
598         return false;
599     }
601     /**
602      * ¿Es válido todo el formulario?
603      *
604      * @return bool
605      */
606     public function formIsValid()
607     {
608         $data = array();
609         foreach ($this->getSessionNamespace() as $key => $info) {
610             $data[$key] = $info;
611         }
613         return $this->getForm()->isValid($data);
614     }
616 ]]></programlisting>
618             <para>Ahora que tenemos el trabajo preparado, vamos a construir las
619                 acciones para este controlador. Necesitaremos una página de
620                 destino para el formulario, y luego una acción 'process' para
621                 procesar el formulario.</para>
623             <programlisting language="php"><![CDATA[
624 class RegistrationController extends Zend_Controller_Action
626     // ...
628     public function indexAction()
629     {
630         // volver a mostrar la página actual, o mostrar el "siguiente"
631         // (primer) subformulario
632         if (!$form = $this->getCurrentSubForm()) {
633             $form = $this->getNextSubForm();
634         }
635         $this->view->form = $this->getForm()->prepareSubForm($form);
636     }
638     public function processAction()
639     {
640         if (!$form = $this->getCurrentSubForm()) {
641             return $this->_forward('index');
642         }
644         if (!$this->subFormIsValid($form,
645                                    $this->getRequest()->getPost())) {
646             $this->view->form = $this->getForm()->prepareSubForm($form);
647             return $this->render('index');
648         }
650         if (!$this->formIsValid()) {
651             $form = $this->getNextSubForm();
652             $this->view->form = $this->getForm()->prepareSubForm($form);
653             return $this->render('index');
654         }
656         // Formulario Válido!
657         // Render information in a verification page
658         $this->view->info = $this->getSessionNamespace();
659         $this->render('verification');
660     }
662 ]]></programlisting>
664             <para>Como se ha notado, el código actual para procesar el
665                 formulario es relativamente simple. Verificamos si tenemos un
666                 subformulario actual sometido y si no, retornamos a la página de
667                 destino. Si tenemos un subformulario, intentaremos validarlo,
668                 volviéndolo a mostrar si tiene fallos. Si el subformulario es
669                 válido, entonces verificaremos si el formulario es válido, lo
670                 que debería indicar que hemos terminado; si no, mostraremos el
671                 siguiente segmento del formulario. Finalmente, mostraremos una
672                 página de verificación con el contenido de la sesión.</para>
674             <para>Los scripts de vista son muy simples:</para>
676             <programlisting language="php"><![CDATA[
677 <?php // registration/index.phtml ?>
678 <h2>registro</h2>
679 <?php echo  $this->form ?>
681 <?php // registration/verification.phtml ?>
682 <h2>Gracias por Registrarse!</h2>
684     Aquí está la información que nos ha proporcionado:
685 </p>
687 <?php
688 // Tienen que construir esto con los items que estan almacenados en los namespaces
689 // de la sesión
690 foreach ($this->info as $info):
691     foreach ($info as $form => $data): ?>
692 <h4><?php echo  ucfirst($form) ?>:</h4>
693 <dl>
694     <?php foreach ($data as $key => $value): ?>
695     <dt><?php echo  ucfirst($key) ?></dt>
696     <?php if (is_array($value)):
697         foreach ($value as $label => $val): ?>
698     <dd><?php echo  $val ?></dd>
699         <?php endforeach;
700        else: ?>
701     <dd><?php echo  $this->escape($value) ?></dd>
702     <?php endif;
703     endforeach; ?>
704 </dl>
705 <?php endforeach;
706 endforeach
707 ]]></programlisting>
709             <para>Próximas novedades de Zend Framework incluirán componentes
710                 para hacer formularios multi páginas mas simples, abstrayendo la
711                 sesión y la lógica de orden. Mientras tanto, el ejemplo de
712                 arriba debería servir como guia razonable para alcanzar esta
713                 tarea en su web.</para>
714         </example>
715     </sect2>
716 </sect1>