Sunteți pe pagina 1din 167

FORMULARIOS SYMPHONY

Captulo 1. Creacin de formularios


Los formularios estn formados por campos de diferentes tipos: campos ocultos (hidden), cuadros de texto (input text), listas desplegables (select), cuadros de seleccin (checkbox), etc. Este captulo explica cmo crear formularios y cmo tratar sus campos con el framework de formularios de Symfony. Para seguir los captulos de este libro es necesario conocer Symfony 1.2 y tenerlo correctamente instalado y configurado. Tambin debes crear un proyecto nuevo y una aplicacin llamada frontend para seguir todos los ejemplos. Puedes consultar el libro oficial de Symfony 1.2 si tienes alguna duda sobre cmo crear el proyecto y la aplicacin.

1.1. Antes de empezar


Vamos a empezar aadiendo un formulario de contacto a una aplicacin Symfony. La figura 1-1 muestra el formulario de contacto tal y como lo ven los usuarios que quieren enviar un mensaje.

Figura 1.1. Formulario de contacto El formulario dispone de tres campos: el nombre del usuario, el email del usuario y el mensaje que el usuario quiere enviar. Como respuesta al envo del formulario, en este primer ejemplo simplemente se va a mostrar toda la informacin enviada por el usuario, tal y como se muestra en la figura 1-2.

Figura 1.2. Pgina de agradecimiento como respuesta al envo del formulario La figura 1-3 muestra la interaccin completa de la aplicacin con el usuario, desde que se muestra el formulario hasta que se visualiza la pgina de respuesta.

Figura 1.3. Interaccin con el usuario

1.2. Widgets
1.2.1. Las clases sfForm y sfWidget

Los usuarios introducen la informacin en los campos de los formularios. En Symfony un formulario es un objeto que hereda de la clase sfForm. En nuestro ejemplo vamos a crear una clase llamada ContactoForm y que hereda de la clase sfForm.
Nota
sfForm es la clase base de todos los formularios y simplifica la gestin de su flujo de

trabajo. Para empezar a configurar el formulario, se aaden widgets mediante el mtodo configure(). Un widget representa un campo del formulario. En nuestro ejemplo tenemos que aadir tres widgets, que son los tres campos del formulario: nombre, email y mensaje. El listado 1-1 muestra la primera versin de la clase ContactoForm. Listado 1-1 - La clase ContactoForm con tres campos
// lib/form/ContactoForm.class.php class ContactoForm extends sfForm { publicfunction configure() { $this->setWidgets(array( 'nombre' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(),

'mensaje' )); } }

=>new sfWidgetFormTextarea(),

Los widgets se definen en el mtodo configure(). Este mtodo se invoca automticamente desde el constructor de la clase sfForm. El mtodo setWidgets() se utiliza para definir los widgets del formulario. Este mtodo acepta como parmetro un array asociativo en el que las claves son los nombres de los campos y los valores son los objetos de tipo widget. Cada widget es un objeto que hereda de la clase sfWidget. En el ejemplo anterior se han utilizado dos tipos de widgets:
y y sfWidgetFormInput: este widget representa un campo de tipo <input> sfWidgetFormTextarea: este widget representa un campo de tipo <textarea>

Nota

Las clases de los formularios se guardan por convencin en el directorio lib/form/. No obstante, puedes guardar los formularios en cualquier otro directorio incluido en el mecanismo de carga automtica de clases de Symfony. Ms adelante se ver que el propio framework utiliza el directorio lib/form/ para generar automticamente los formularios a partir de los objetos del modelo.
1.2.2. Visualizando el formulario

Nuestro formulario ya est listo para ser utilizado. A continuacin se crea un mdulo en la aplicacin para visualizar el formulario:
$ cd /ruta/hasta/el/proyecto $ php symfony generate:module frontend contacto

En el mdulo contacto se modifica la accin index para pasar una instancia del formulario a la plantilla, tal y como se muestra en el listado 1-2. Listado 1-2 - La clase de las acciones del mdulo contacto
// apps/frontend/modules/contacto/actions/actions.class.php class contactoActions extends sfActions { publicfunction executeIndex() { $this->formulario = new ContactoForm(); } }

Al crear un formulario mediante new ContactoForm(), se invoca automticamente el mtodo configure() definido anteriormente.

Ahora ya slo es necesario crear una plantilla como la del listado 1-3 para visualizar el formulario. Listado 1-3 - La plantilla utilizada para visualizar el formulario
// apps/frontend/modules/contacto/templates/indexSuccess.php <form action="<?php echo url_for('contacto/enviar') ?>" method="POST"> <table> <?phpecho$formulario?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>

Los formularios de Symfony slo se encargan de los widgets que muestran la informacin a los usuarios. En la plantilla indexSuccess, la lnea <?php echo $formulario ?> slo muestra los tres campos del formulario. El resto de elementos, como la etiqueta <form> y el botn de envo, los debe aadir el programador. Aunque este comportamiento puede parecer poco intuitivo al principio, pronto se ver lo til y sencillo que es para crear formularios ms complejos. Utilizar la instruccin <?php echo $formulario ?> es muy til para crear prototipos y definir formularios. Permite a los programadores concentrarse en la lgica de la aplicacin sin preocuparse de los detalles grficos. El captulo tres explica cmo personalizar la plantilla y el diseo del formulario.
Nota

Cuando se muestra un objecto usando <?php echo $formulario ?>, el intrprete de PHP muestra la representacin en texto del objeto $formulario. Para convertir el objeto en una cadena de texto, PHP intenta ejecutar el mtodo mgico __toString(). Todos los widgets implementan este mtodo para convertir el objeto en cdigo HTML. Por lo tanto, ejecutar <?php echo $formulario ?> es equivalente a ejecutar <?php echo $formulario>__toString() ?>. Ahora ya es posible visualizar el formulario en un navegador (figura 1-4) y comprobar el resultado accediendo a la direccin de la accin contacto/index (/frontend_dev.php/contacto).

Figura 1.4. Formulario de contacto generado A continuacin se muestra el cdigo HTML generado por la plantilla: Listado 1-4 Cdigo HTML generado por la plantilla
<formaction="/frontend_dev.php/contacto/enviar"method="POST"> <table> <!-- Cdigo generado por <?php echo $formulario ?> --> <tr> <th><labelfor="nombre">Nombre</label></th> <td><inputtype="text"name="nombre"id="nombre" /></td> </tr> <tr> <th><labelfor="email">Email</label></th> <td><inputtype="text"name="email"id="email" /></td> </tr> <tr> <th><labelfor="mensaje">Mensaje</label></th> <td><textarearows="4"cols="30"name="mensaje"id="mensaje"></textarea></td> </tr> <!-- Fin del cdigo generado por <?php echo $formulario ?> --> <tr> <tdcolspan="2"> <inputtype="submit" /> </td> </tr> </table> </form>

El cdigo HTML generado por el formulario est compuesto de tres filas de tabla (etiqueta <tr>). Por ese motivo el cdigo de la plantilla utilizaba antes una etiqueta <table> para

encerrar los contenidos del formulario. Cada fila de formulario incluye una etiqueta <label> y una etiqueta de formulario (<input> o <textarea>).
1.2.3. Labels

Los ttulos o labels de cada campo se generan de forma automtica. La lgica que se utiliza por defecto para convertir los nombres de los campos en sus ttulos sigue dos reglas:
y y

La primera letra se convierte en mayscula Los guiones bajos se convierten en espacios en blanco

A continuacin se muestra otro ejemplo:


$this->setWidgets(array( 'codigo_postal' =>new sfWidgetFormInput(), // Ttulo generado: "Codigo postal" 'fecha_nacimiento' =>new sfWidgetFormInput(), // Ttulo generado: "Fecha nacimiento" ));

Aunque la generacin automtica de los ttulos es muy til, el framework tambin permite definir ttulos personalizados con el mtodo setLabels():
$this->widgetSchema->setLabels(array( 'nombre' =>'Tu nombre', 'email' =>'Tu correo electrnico', 'mensaje' =>'Tu mensaje', ));

Tambin se puede modificar un solo ttulo mediante el mtodo setLabel():


$this->widgetSchema->setLabel('email', 'Tu correo electrnico');

El captulo 3 muestra cmo se pueden personalizar los ttulos desde la plantilla para personalizar aun ms los formularios. Widget Schema Cuando se utiliza el mtodo setWidgets(), Symfony crea un objeto de tipo sfWidgetFormSchema. Este objeto es un widget que representa a un conjunto de widgets. En nuestro formulario ContactoForm hemos utilizado el mtodo setWidgets(), que es equivalente al siguiente cdigo:
$this->setWidgetSchema(new sfWidgetFormSchema(array( 'nombre' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(), 'mensaje' =>new sfWidgetFormTextarea(), ))); // es equivalente a:

$this->widgetSchema = new sfWidgetFormSchema(array( 'nombre' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(), 'mensaje' =>new sfWidgetFormTextarea(), ));

El mtodo setLabels() se aplica al conjunto de widgets includos en el objecto widgetSchema. En el captulo 5 se explica por qu el concepto de "schema widget" hace ms fcil el manejo de formularios complejos.
1.2.4. Ms all de las tablas generadas

Aunque por defecto el formulario se muestra con una tabla HTML, su diseo se puede modificar completamente. Los diferentes tipos de diseos o layouts se definen en clases que heredan de sfWidgetFormSchemaFormatter. El estilo por defecto de los formularios corresponde a la clase sfWidgetFormSchemaFormatterTable. Tambin es posible utilizar el diseo list, basado en una lista HTML:
class ContactoForm extends sfForm { publicfunction configure() { $this->setWidgets(array( 'nombre' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(), 'mensaje' =>new sfWidgetFormTextarea(), )); $this->widgetSchema->setFormFormatterName('list'); } }

Los diseos de tabla y de lista estn includos por defecto en Symfony. En el captulo 5 se explica cmo crear tus propias clases de formato para personalizar el diseo de los formularios. Despus de mostrar cmo se visualiza un formulario, el siguiente paso consiste en realizar el envo de los datos introducidos por el usuario.
1.2.5. Enviando el formulario

Al crear la plantilla que muestra el formulario, se emple la URL interna contacto/enviar en la etiqueta <form> para enviar el formulario. Por tanto, el siguiente paso consiste en aadir la accin enviar en el mdulo contacto. El listado 1-5 muestra cmo obtener en la accin la informacin enviada por el usuario y cmo se le puede redirigir a la pgina de agradecimiento, donde se vuelve a mostrar la misma informacin. Listado 1-5 - La accin enviar en el mdulo contacto

publicfunction executeEnviar($request) { $this->forward404Unless($request->isMethod('post')); $parametros = array( 'nombre' =>$request->getParameter('nombre'), 'email' =>$request->getParameter('email'), 'mensaje' =>$request->getParameter('mensaje'), ); $this->redirect('contacto/gracias?'.http_build_query($parametros)); } publicfunction executeGracias() { } // apps/frontend/modules/contacto/templates/graciasSuccess.php <ul> <li>Nombre: <?phpecho$sf_params->get('nombre') ?></li> <li>Email: <?phpecho$sf_params->get('email') ?></li> <li>Mensaje: <?phpecho$sf_params->get('mensaje') ?></li> </ul>

Nota
http_build_query es una funcin propia de PHP que genera una cadena de texto de tipo

query string a partir de los parmetros pasados a travs de un array y con sus valores correctamente codificados para incluirlos en una URL. El mtodo executeEnviar() realiza tres acciones:
y

Por razones de seguridad, se comprueba que la pgina se ha enviado utilizando el mtodo POST. Si no es as, se redirige al usuario a una pgina de error 404. En la plantilla indexSuccess, se haba declarado que el mtodo de envo del formulario debe ser POST (<form ... method="POST">):

$this->forward404Unless($request->isMethod('post')); y

A continuacin se obtienen los valores introducidos por el usuario y se guardan en un array llamado parametros:

$parametros = array( 'nombre' =>$request->getParameter('nombre'), 'email' =>$request->getParameter('email'), 'mensaje' =>$request->getParameter('mensaje'), ); y

Por ltimo, se redirige al usuario a la pgina de agradecimiento (contacto/gracias) para mostrarle la informacin que ha introducido:

$this->redirect('contacto/gracias?'.http_build_query($parametros));

En vez de redirigir al usuario a otra pgina, se podra haber creado una plantilla llamada enviarSuccess.php. No obstante, aunque es posible hacerlo, despus de un envo por el mtodo POST se recomienda redirigir al usuario a otra pgina, ya que:
y y

Se evita que el usuario enve de nuevo el formulario si recarga la pgina de agradecimiento. El usuario puede volver a la pgina anterior sin que se le muestre el mensaje de enviar el formulario de nuevo.

Sugerencia

Quizs has observado que el mtodo executeEnviar() es diferente de executeIndex(). Cuando se invocan estos dos mtodos, Symfony les pasa automticamente como primer argumento el objeto de tipo sfRequest que representa a la peticin actual. Como en PHP no es necesario recoger todos los parmetros que se pasan a una funcin, el mtodo executeIndex() no incluye el parmetro $request en su definicin, ya que no lo utiliza para nada. La figura 1-5 muestra el flujo de trabajo de todos los mtodos que intervienen en la interaccin del usuario.

Figura 1.5. Flujo de trabajo de los mtodos


Nota

Cuando se vuelve a mostrar la informacin del usuario en la plantilla, se corre el riesgo de sufrir un ataque de tipo XSS (Cross-Site Scripting). Puedes consultar el captulo sobre la parte de la vista del libro oficial de Symfony para conocer ms detalles sobre cmo evitar los ataques XSS. Despus de enviar el formulario, deberas ver la pgina de la figura 1-6.

Figura 1.6. Pgina que se muestra despus de enviar el formulario Por otra parte, en vez de crear el array parametros, sera ms sencillo recoger la informacin del usuario directamente en un array. El listado 1-6 modifica el atributo name de HTML de los widgets para guardar los valores en un array llamado contacto. Listado 1-6 - Modificando el atributo name de HTML de los widgets
class ContactoForm extends sfForm { publicfunction configure() { $this->setWidgets(array( 'nombre' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(), 'mensaje' =>new sfWidgetFormTextarea(), )); $this->widgetSchema->setNameFormat('contacto[%s]'); } }

El mtodo setNameFormat() permite modificar el atributo name en todos los widgets. Cuando se genera el formulario, el valor %s se reemplaza automticamente por el nombre de cada campo. Si se toma como ejemplo el campo email, su atributo name de HTML ser contacto[email]. Si se utilizan estos nombres, PHP crea automticamente un array que incluye todos los valores enviados en la peticin. De esta forma, los valores de los campos se pueden acceder mediante el array contacto. Ahora en la accin se puede obtener el array contacto directamente a partir del objeto de la peticin, tal y como se muestra en el listado 1-7. Listado 1-7 - Nuevo formato de los atributos name de los widgets
publicfunction executeEnviar($request) { $this->forward404Unless($request->isMethod('post')); $this->redirect('contacto/gracias?'.http_build_query($request>getParameter('contacto'))); }

Si observas el cdigo HTML del formulario generado, vers que Symfony no slo crea un atributo name a partir del nombre y formato de cada widget, sino que tambin crea un

atributo id. En realidad, el atributo id es una copia del atributo name en la que se han sustituido los caracteres problemticos por un guin bajo (_):
Nombre del widget
nombre email mensaje

Atributo name

Atributo id

contacto[nombre] contacto_nombre contacto[email]

contacto_email

contacto[mensaje] contacto_mensaje

1.2.6. Una solucin alternativa

En el ejemplo anterior se utilizan dos acciones para gestionar el formulario: accin index para mostrarlo y accin enviar para enviarlo. Como el formulario se visualiza con el mtodo GET y se enva con el mtodo POST, se pueden fusionar los dos mtodos en una nica accin index como se muestra en el listado 1-8. Listado 1-8 - Fusionando las dos acciones utilizadas en el formulario
class contactoActions extends sfActions { publicfunction executeIndex($request) { $this->formulario = new ContactoForm(); if($request->isMethod('post')) { $this->redirect('contacto/gracias?'.http_build_query($request>getParameter('contacto'))); } } }

Para que el cdigo anterior funcione correctamente, no olvides modificar el atributo action del formulario en la plantilla indexSuccess.php:
<formaction="<?php echo url_for('contacto/index') ?>" method="POST">

Como se ver ms adelante, es mejor utilizar esta sintaxis porque es ms breve y hace que el cdigo resultante sea ms coherente y fcil de entender.

1.3. Configurando los widgets


1.3.1. Opciones de los widgets

Si el sitio web que muestra el formulario de contacto dispone de varios responsables, puede ser interesante aadir una lista desplegable con diferentes temas de contacto para redirigir cada mensaje al responsable adecuado (ver figura 1-7). El listado 1-9 aade un nuevo campo llamado asunto mediante una lista desplegable creada con un widget de tipo sfWidgetFormSelect.

Figura 1.7. Aadiendo un campo asunto en el formulario Listado 1-9 - Aadiendo un campo asunto en el formulario
class ContactoForm extends sfForm { protected static$asuntos = array('Asunto A', 'Asunto B', 'Asunto C'); publicfunction configure() { $this->setWidgets(array( 'nombre' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(), 'asunto' =>new sfWidgetFormSelect(array('choices' => self::$asuntos)), 'mensaje' =>new sfWidgetFormTextarea(), )); $this->widgetSchema->setNameFormat('contacto[%s]'); } }

La opcin ''choices'' del widget ''sfWidgetFormSelect''

PHP no considera diferentes los arrays normales y los arrays asociativos, por lo que el array utilizado en la opcin asunto del cdigo anterior es idntico al siguiente:
$asuntos = array(0 =>'Asunto A', 1 =>'Asunto B', 2 =>'Asunto C');

El widget generado toma la clave del array como el atributo value de la etiqueta <option> y el valor del array como el contenido de la etiqueta:
<selectname="contacto[asunto]"id="contacto_asunto"> <optionvalue="0">Asunto A</option> <optionvalue="1">Asunto B</option> <optionvalue="2">Asunto C</option> </select>

Si quieres modificar el valor de los atributos value, debes aadir claves en el array utilizado:
$asuntos = array('A' =>'Asunto A', 'B' =>'Asunto B', 'C' =>'Asunto C');

Si se utiliza el array anterior, el cdigo HTML generado es el siguiente:


<selectname="contacto[asunto]"id="contacto_asunto"> <optionvalue="A">Asunto A</option> <optionvalue="B">Asunto B</option> <optionvalue="C">Asunto C</option> </select>

El widget sfWidgetFormSelect, como todos los dems widgets, acepta como primer argumento una lista de opciones. A pesar de su nombre, las opciones pueden ser opcionales u obligatorios. El widget sfWidgetFormSelect dispone de una opcin obligatoria llamada choices. A continuacin se muestran las opciones disponibles para los widgets que ya hemos utilizado:
Widget Opciones obligatorias Otras opciones
type: tipo de campo (por defecto, text) sfWidgetFormInput

is_hidden: indica si el campo es oculto

(por defecto, false)


sfWidgetFormSelect choices: opciones de la multiple: la lista permite selecciones

lista desplegable

mltiples (por defecto, false) -

sfWidgetFormTextarea -

Sugerencia

Si quieres conocer todas las opciones de un widget, puedes consultar la documentacin de la API disponible online en http://www.symfony-project.org/api/1_2/. En la API se

explican todas las opciones y todos sus valores por defecto. Para consultar por ejemplo las opciones del widget sfWidgetFormSelect debes acceder a http://www.symfonyproject.org/api/1_2/sfWidgetFormSelect
1.3.2. Los atributos HTML de los widgets

Los widgets tambin aceptan como segundo argumento una lista de atributos HTML. De esta forma, esta opcin permite personalizar las etiquetas HTML generadas para el formulario. El listado 1-10 muestra como aadir un atributo class al campo email. Listado 1-10 - Definiendo atributos HTML para un widget
$widgetEmail = new sfWidgetFormInput(array(), array('class' =>'email')); // Cdigo HTML generado <input type="text" name="contacto[email]"class="email" id="contacto_email" />

Los atributos HTML tambin permiten redefinir los identificadores generados automticamente, como se muestra en el listado 1-11. Listado 1-11 - Redefiniendo el atributo id
$widgetEmail = new sfWidgetFormInput(array(), array('class' =>'email', 'id' =>'email')); // Cdigo HTML generado <input type="text" name="contacto[email]"class="email" id="email" />

Tambin es posible establecer el valor que muestran por defecto los campos utilizando el atributo value como se muestra en el listado 1-12. Listado 1-12 - Establecer valores por defecto de los widgets utilizando atributos HTML
$widgetEmail = new sfWidgetFormInput(array(), array('value' =>'Escribe tu email')); // Cdigo HTML generado <input type="text" name="contacto[email]" value="Escribe tu email" id="contacto_email" />

Esta opcin funciona bien para los widgets de tipo input, pero es difcil de aplicar en los widgets de tipo checkbox o radio e incluso es imposible para uno de tipo textarea. La clase sfForm incluye mtodos especficos para definir los valores por defecto de cada campo de una forma uniforme para cualquier tipo de widget.
Nota

Una buena prctica consiste en definir los atributos de HTML dentro de la plantilla y no en el formulario (donde tambin es posible hacerlo), para mantener la separacin de las capas tal y como se ver en el captulo 3.
1.3.3. Definir los valores por defecto de los campos

En ocasiones es conveniente definir un valor por defecto para cada campo. El ejemplo tpico es el mensaje de ayuda que se muestra en cada campo del formulario y que se oculta cuando el usuario se situa en ese campo. El listado 1-13 muestra cmo definir los valores por defecto a travs de los mtodos setDefault() y setDefaults(). Listado 1-13 - Establecer los valores por defecto en los widgets mediante los mtodos setDefault() y setDefaults()
class ContactoForm extends sfForm { publicfunction configure() { // ... $this->setDefault('email', 'Escribe tu email'); $this->setDefaults(array('email' =>'Escribe tu email', 'nombre' =>'Escribe tu nombre')); } }

Los mtodos setDefault() y setDefaults() son muy tiles para definir los mismos valores por defecto para cada instancia del mismo formulario. Si se quiere modificar un objeto existente utilizando un formulario, los valores por defecto dependen de la instancia y por tanto, deben ser dinmicos. El listado 1-14 muestra cmo el constructor de la clase sfForm dispone de un primer argumento que establece dinmicamente los valores por defecto. Listado 1-14 - Establecer los valores por defecto de los widgets mediante el constructor de sfForm
publicfunction executeIndex($peticion) { $this->formulario = new ContactoForm(array('email' =>'Escribe tu email', 'nombre' =>'Escribe tu nombre')); // ... }

Proteccin frente a XSS (Cross-Site Scripting) Cuando se establecen atributos HTML para los widgets o cuando se definen valores por defecto, la clase sfForm protege automticamente estos valores contra ataques XSS al generar el cdigo HTML. Esta proteccin no depende de la opcin escaping_strategy

configurada en el archivo settings.yml. Si el contenido ya ha sido protegido por otro mtodo, no se le aplica ninguna otra proteccin. La clase sfForm tambin protege los caracteres ' y ", ya que pueden provocar que el cdigo HTML generado no sea vlido. A continuacin se muestra un ejemplo de esta proteccin:
$widgetEmail = new sfWidgetFormInput(array(), array( 'value' =>'Hola "Mundo"!', 'class' =>'<script>alert("hola")</script>', )); // Cdigo HTML generado <input value="Hola &quot;Mundo!&quot;" class="&lt;script&gt;alert(&quot;hola&quot;)&lt;/script&gt;" type="text" name="contacto[email]" id="contacto_email" />

Captulo 2. Validacin de formularios


En el captulo 1, se mostr cmo crear y visualizar un formulario de contacto. En este captulo se explica cmo gestionar la validacin del formulario.

2.1. Antes de comenzar


El formulario de contacto creado en el captulo 1 todava no es muy til. Qu sucede si un usuario enva una direccin de email invlida o si el mensaje enviado est vaco? En estos casos, lo habitual es mostrar mensajes de error que le indiquen al usuario que tiene que corregir los datos enviados, como se muestra en la figura 2-1.

Figura 2.1. Mostrando mensajes de error A continuacin se describen las reglas de validacin que se van a incluir en el formulario de contacto:
y y y y nombre: opcional email: obligatorio, debe ser una direccin vlida de correo electrnico asunto: obligatorio, seleccionado entre una lista de valores predefinidos mensaje: obligatorio, debe tener una longitud de al menos cuatro caracteres

Nota Por qu es necesario validar el campo asunto? Al fin y al cabo, su valor se selecciona mediante una etiqueta <select> que obliga al usuario a seleccionar un valor entre una lista de valores predefinidos. Aunque es cierto que los usuarios normales slo podrn seleccionar uno de los valores predefinidos, cualquier usuario con conocimientos medios puede utilizar herramientas como Firebug para manipular los valores de la lista desplegable. Adems, otros usuarios pueden utilizar herramientas como curl o wget para construir peticiones HTTP a medida que incluyan valores arbitrarios. El listado 2-1 muestra la misma plantilla que se utiliza en el captulo 1. Listado 2-1 - La plantilla del formulario Contacto
// apps/frontend/modules/contacto/templates/indexSucces.php <form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <?php echo $formulario ?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>

La figura 2-2 muestra la interaccin entre la aplicacin y el usuario. El primer paso consiste en mostrar la informacin al usuario. Cuando el usuario enva el formulario, si los datos son vlidos se le redirige a la pgina de agradecimiento y si los datos no cumplen alguna de las reglas de validacin, se vuelve a mostrar el formulario con los mensajes de error.

Figura 2.2. Interaccin entre la aplicacin y el usuario

2.2. Validadores
Los formularios de Symfony estn formados por campos. En el captulo 1, se explica cmo se identifican los campos mediante un nombre nico. Para mostrar el formulario al usuario, se asocia un tipo de widget a cada campo. De la misma forma, ahora se van a aplicar las reglas de validacin a cada campo.

2.2.1. La clase sfValidatorBase

La validacin de los campos se realiza mediante objetos que heredan de la clase sfValidatorBase. Para validar el formulario de contacto, se define un objeto validador para cada uno de los cuatro campos: nombre, email, asunto, y mensaje. El listado 2-2 muestra cmo crear estos validadores en la clase del formulario utilizando el mtodo setValidators(). Listado 2-2 - Aadiendo validadores en la clase ContactoForm
// lib/form/ContactoForm.class.php class ContactoForm extends sfForm { protected static$asuntos = array('Asunto A', 'Asunto B', 'Asunto C'); publicfunction configure() { $this->setWidgets(array( 'nombre' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(), 'asunto' =>new sfWidgetFormSelect(array('choices' => self::$asuntos)), 'mensaje' =>new sfWidgetFormTextarea(), )); $this->widgetSchema->setNameFormat('contacto[%s]'); $this->setValidators(array( 'nombre' =>new sfValidatorString(array('required' =>false)), 'email' =>new sfValidatorEmail(), 'asunto' =>new sfValidatorChoice(array('choices' =>array_keys(self::$asuntos))), 'mensaje' =>new sfValidatorString(array('min_length' =>4)), )); } }

El cdigo anterior utiliza tres validadores diferentes:


y y y sfValidatorString: valida una cadena de texto sfValidatorEmail : valida un email sfValidatorChoice: valida que el valor se encuentra entre una lista de valores

predefinidos

Los validadores aceptan como primer argumento una lista de opciones. Al igual que los widgets, a pesar de su nombre algunas opciones son opcionales y otras obligatorias. El validador sfValidatorChoice por ejemplo dispone de una opcin obligatoria llamada choices. Todos los validadores pueden establecer las opciones required y trim, que definen sus valores por defecto en la clase sfValidatorBase:
Opcin Valor por Descripcin

defecto
required true

Indica que es obligatorio rellenar este campo Indica si se deben eliminar los espacios en blanco del principio y del final antes de validar el valor del campo

trim

false

A continuacin se muestran las opciones de los validadores del ejemplo anterior:


Validador Opciones obligatorias Otras opciones
max_length, min_length pattern

sfValidatorString sfValidatorEmail sfValidatorChoice choices

Si se enva ahora el formulario con valores incorrectos, no se produce ningn cambio respecto al comportamiento sin la validacin. El motivo es que se debe modificar el mdulo contacto para que aplique las reglas de validacin, tal y como se muestra en el listado 2-3. Listado 2-3 - Aadiendo la validacin en el mdulo contacto
class contactoActions extends sfActions { publicfunction executeIndex($request) { $this->formulario = new ContactoForm(); if($request->isMethod('post')) { $this->formulario->bind($request->getParameter('contacto')); if($this->formulario->isValid()) { $this->redirect('contacto/gracias?'.http_build_query($this->formulario>getValues())); } } } publicfunction executeGracias() { } }

El listado 2-3 incluye numerosos conceptos nuevos:

Cuando se realiza la peticin GET inicial, se inicializa el formulario y se pasa a la plantilla para mostrarlo al usuario. En este caso, el formulario se encuentra en el estado inicial:

$this->formulario = new ContactoForm(); y

Cuando el usuario enva el formulario mediante una peticin POST, el mtodo bind() asocia el formulario con los datos introducidos por el usuario y ejecuta el mecanismo de validacin. En este caso, el formulario se encuentra en el estado asociado.

if($request->isMethod('post')) { $this->formulario->bind($request->getParameter('contacto')); y

Una vez asociado, el formulario se puede validar mediante el mtodo isValid(): o Si el valor devuelto por el mtodo es true, el formulario es vlido y se redirige al usuario a la pgina de agradecimiento:

if($this->formulario->isValid()) { $this->redirect('contacto/gracias?'.http_build_query($this->formulario>getValues())); }

En caso contrario, se vuelve a mostrar la plantilla indexSuccess como al principio. El proceso de validacin aade los mensajes de error al formulario para que se muestren al usuario.

Nota

Cuando el formulario se encuentra en el estado inicial, el mtodo isValid() siempre devuelve false y el mtodo getValues() siempre devuelve un array vaco. La figura 2-3 muestra el cdigo que se ejecuta durante la interaccin entre la aplicacin y el usuario.

Figura 2.3. Cdigo que se ejecuta durante la interaccin entre la aplicacin y el usuario
2.2.2. La finalidad de los validadores

En el cdigo anterior, durante la redireccin a la pgina de agradecimiento, no se utiliza la instruccin $request->getParameter('contacto') sino que se emplea $this>formulario->getValues(). De hecho, la instruccin $request>getParameter('contacto') devuelve los datos enviados por el usuario y la instruccin $this->formulario->getValues() devuelve los datos validados. Y si el formulario es vlido, por qu las dos instrucciones anteriores no son equivalentes? En realidad, los validadores realizan dos tareas: la tarea de validacin y la tarea de limpieza. Por tanto, el mtodo getValues() devuelve los datos validados y limpios.

A su vez, el proceso de limpieza de datos se compone de dos acciones: normalizacin y conversin de datos. La opcin trim explicada anteriormente es un caso de normalizacin de datos. No obstante, la normalizacin es un proceso mucho ms importante por ejemplo para las fechas. El validador sfValidatorDate se encarga de validar fechas y permite introducir la fecha con muchos formatos (un timestamp, un formato que sigue una expresin regular, etc.). Este validador no devuelve directamente el valor introducido, sino que por defecto lo convierte al formato Y-m-d H:i:s. Por lo tanto, el programador puede estar seguro de obtener siempre el mismo formato para las fechas, independientemente del formato en el que el usuario introdujo la fecha. Este mecanismo ofrece una gran flexibilidad a los usuarios y asegura la consistencia a los programadores. Respecto a la conversin de datos, se muestra a continuacin el caso de un fichero subido. La validacin de los archivos subidos por los usuarios se realiza con el validador sfValidatorFile. Una vez que el archivo se ha subido, este validador no devuelve simplemente el nombre del archivo, sino que devuelve un objeto de tipo sfValidatedFile, que facilita el acceso a la informacin del archivo. Ms adelante en este captulo se muestra cmo utilizar este validador.
Sugerencia

El mtodo getValues() devuelve un array con todos los datos validados y limpios. Sin embargo, como en ocasiones es muy til acceder slo a un valor, tambin existe el mtodo getValue(). Ejemplo de uso: $email = $this->formulario->getValue('email').
2.2.3. Formularios invlidos

Cuando alguno de los campos del formulario contiene informacin invlida, se muestra la plantilla indexSuccess. La figura 2-4 muestra el resultado obtenido cuando se envan datos invlidos.

Figura 2.4. Formulario invlido La instruccin <?php echo $formulario ?> tiene en cuenta los mensajes de error asociados a cada campo y vuelve a mostrar los datos introducidos por el usuario despus de pasar por el proceso de limpieza de datos. Cuando se asocia el formulario con datos externos mediante el mtodo bind(), el formulario pasa al estado asociado y se ejecutan las siguientes acciones:
y y y

Se ejecuta el proceso de validacin Los mensajes de error se almacenan en el formulario para que estn disponibles en la plantilla Los valores iniciales del formulario se reemplazan por los valores resultantes del proceso de limpieza de datos

La informacin necesaria para mostrar los mensajes de error o los datos introducidos por el usuario se puede acceder fcilmente mediante la variable formulario en la plantilla.
Cuidado

Como se explica en el captulo 1, el constructor de la clase del formulario admite que se le pasen los valores por defecto. Despus de enviar un formulario con datos invlidos, estos valores por defecto se sustituyen por los datos enviados, de forma que el usuario pueda ver los errores que ha cometido. Por lo tanto, no se deben utilizar los datos introducidos como si fueran datos por defecto, como por ejemplo en la siguiente instruccin: $this>formulario->setDefaults($peticion->getParameter('contacto'))

2.3. Personalizando los validadores

2.3.1. Personalizar los mensajes de error

Como se aprecia en la figura 2-4 anterior, los mensajes de error por defecto no son muy tiles. A continuacin se muestra cmo personalizarlos para que sean ms intuitivos. Todos los validadores pueden aadir errores en el formulario. Un error est formado por un cdigo de error y un mensaje de error. Cada validador dispone al menos de los errores required y invalid, definidos en la clase sfValidatorBase:
Cdigo Mensaje Descripcin

required Required. Es obligatorio rellenar el campo y ahora est vaco invalid Invalid. La informacin del campo no es vlida

A continuacin se muestran los cdigos de error de los validadores que se han visto hasta ahora:
Validador Cdigo de error

sfValidatorString max_length, min_length sfValidatorEmail sfValidatorChoice -

Para personalizar los mensajes de error, se pasa un segundo argumento a los objetos validadores. El listado 2-4 personaliza varios mensajes de error y la figura 2-5 muestra el resultado. Listado 2-4 - Personalizando mensajes de error
class ContactoForm extends sfForm { protected static$asuntos = array('Asunto A', 'Asunto B', 'Asunto C'); publicfunction configure() { // ... $this->setValidators(array( 'nombre' =>new sfValidatorString(array('required' =>false)), 'email' =>new sfValidatorEmail(array(), array('invalid' =>'La direccin de email no es vlida.')), 'asunto' =>new sfValidatorChoice(array('choices' =>array_keys(self::$asuntos))),

'mensaje' =>new sfValidatorString(array('min_length' =>4), array('required' =>'Es obligatorio escribir un mensaje.')), )); } }

Figura 2.5. Mensajes de error propios La figura 2-6 muestra el mensaje de error que se obtiene si se enva un mensaje muy corto, ya que se ha establecido que su longitud mnima debe ser cuatro caracteres:

Figura 2.6. Error producido porque el mensaje es muy corto

El mensaje de error por defecto asociado con el cdigo de error min_length es diferente de los otros mensajes de error, ya que dispone de dos variables: los datos introducidos por el usuario (foo en el ejemplo anterior) y el nmero mnimo de caracteres de este campo (4 en el ejemplo anterior). El listado 2-5 personaliza an ms el mensaje de error utilizando estas dos vairables y la figura 2-7 muestra el resultado. Listado 2-5 - Personalizando los mensajes de error con variables
class ContactoForm extends sfForm { publicfunction configure() { // ... $this->setValidators(array( 'nombre' =>new sfValidatorString(array('required' =>false)), 'email' =>new sfValidatorEmail(array(), array('invalid' =>'La direccin de email no es vlida.')), 'asunto' =>new sfValidatorChoice(array('choices' =>array_keys(self::$asuntos))), 'mensaje' =>new sfValidatorString(array('min_length' =>4), array( 'required' =>'Es obligatorio escribir un mensaje.', 'min_length' =>'El mensaje "%value%" es demasiado corto. Su longitud debe ser al menos de %min_length% caracteres.', )), )); } }

Figura 2.7. Mensajes de error personalizados con variables Todos los mensajes de error pueden utilizar variables simplemente encerrando su nombre con el carcter del porcentaje (%). Las variables disponibles normalmente son los datos

introducidos por el usuario (que se llama value) y los valores definidos por cada validador (min_length, max_length, etc.)
Sugerencia

Si quieres conocer todos los cdigos de error, opciones y mensajes por defecto de los validadores, puedes consultar la documentacin online de la API en http://www.symfonyproject.org/api/1_2/ Por ejemplo, las opciones, cdigos y mensajes de error del validador sfValidatorString se pueden consultar en http://www.symfonyproject.org/api/1_2/sfValidatorString

2.4. Seguridad de los validadores


Por defecto, los formularios slo son vlidos si todos los campos rellenados por el usuario disponen de un validador. De esta forma, Symfony se asegura que todos los campos tienen establecidas reglas de validacin y tambin se asegura que no sea posible incluir informacin en campos que no estn definidos en el formulario original. Para comprender mejor la seguridad de los formularios, se muestra el ejemplo del listado 26 en el que se utiliza un objeto de tipo usuario. Listado 2-6 - La clase Usuario
class Usuario { protected $nombre = '', $es_administrador = false; publicfunction setCampos($campos) { if(isset($campos['nombre'])) { $this->nombre = $campos['nombre']; } if(isset($campos['es_administrador'])) { $this->es_administrador = $campos['es_administrador']; } } // ... }

El objeto Usuario incluye dos propiedades, el nombre del usuario (nombre) y un valor booleano que indica si el usuario es de tipo administrador (es_administrador). El mtodo setCampos() actualiza el valor de estas dos propiedades. El listado 2-7 muestra el

formulario relacionado con la clase Usuario y que permite modificar solamente la propiedad nombre. Listado 2-7 - Formulario Usuario
class UsuarioForm extends sfForm { publicfunction configure() { $this->setWidgets(array('nombre' =>new sfWidgetFormInputString())); $this->widgetSchema->setNameFormat('usuario[%s]'); $this->setValidators(array('nombre' =>new sfValidatorString())); } }

El listado 2-8 muestra el cdigo de un mdulo llamado usuario que utiliza el formulario anterior para permitir al usuario cambiar el valor del campo nombre. Listado 2-8 - Cdigo del mdulo usuario
class usuarioActions extends sfActions { publicfunction executeIndex($request) { $this->formulario = new UsuarioForm(); if($request->isMethod('post')) { $this->formulario->bind($request->getParameter('usuario')); if($this->formulario->isValid()) { $usuario = // obtener el objeto del usuario $usuario->setCampos($this->formulario->getValues()); $this->redirect('...'); } } } }

Si no se utiliza ninguna proteccin, el cdigo de la aplicacin es vulnerable ya que el usuario podra enviar el formulario con los campos nombre y es_administrador rellenos. Modificar el cdigo HTML de los formularios para enviar cualquier informacin es muy sencillo gracias a herramientas como Firebug. Adems, como el campo es_administrador no tiene asociado ningn validador, su valor siempre es vlido. Por lo tanto, sea cual sea su valor, el mtodo setCampos() no slo actualiza la propiedad nombre sino que tambin modifica la propiedad es_administrador. Si se prueba el cdigo anterior con un formulario que incluya los campos nombre y es_administrador, se produce un error global y se muestra el mensaje de error "Extra

field es_administrador, tal y como se observa en la figura 2-8. El error se produce porque algunos de los campos enviados no tienen ningn validador asociado, ya que el campo es_administrador no est definido en el formulario UsuarioForm.

Figura 2.8. Error producido porque no se ha definido un validador Todos los validadores utilizados hasta el momento generan errores asociados con sus propios campos. Por lo tanto, de dnde proviene este error global? Cuando se emplea el mtodo setValidators(), Symfony crea un objeto de tipo sfValidatorSchema que a su vez define una coleccin de validadores. Invocar el mtodo setValidators() es equivalente al siguiente cdigo:
$this->setValidatorSchema(new sfValidatorSchema(array( 'email' =>new sfValidatorEmail(), 'asunto' =>new sfValidatorChoice(array('choices' =>array_keys(self::$asuntos))), 'mensaje' =>new sfValidatorString(array('min_length' =>4)), )));

El objeto sfValidatorSchema incluye dos reglas de validacin activadas por defecto para proteger la coleccin de validadores. Estas reglas se pueden configurar con las opciones allow_extra_fields y filter_extra_fields. La opcin allow_extra_fields, que vale false por defecto, comprueba si todos los campos enviados disponen de un validador. En caso negativo, genera el error global "Extra field <nombre_del_campo>.", como suceda en el ejemplo anterior. Cuando se desarrolla la aplicacin esta opcin es muy til porque avisa a los programadores sobre todos los campos que todava no disponen de un validador. Volviendo al formulario de contacto, vamos a modificar sus reglas de validacin y vamos a establecer que el campo nombre es obligatorio. Como el valor por defecto de la opcin required es true, se puede establecer el validador de nombre simplemente como:
$validadorNombre = new sfValidatorString();

Como el validador no establece ni la opcin min_length ni max_length, no tiene efecto sobre el texto introducido. De hecho, se podra reemplazar por un validador vaco:
$validadorNombre = new sfValidatorPass();

En el ejemplo anterior, en vez de utilizar un validador vaco, se puede eliminar por completo el validador, pero la proteccin por defecto del formulario impide eliminar el validador. El listado 2-9 muestra cmo deshabilitar la proteccin por defecto mediante la opcin allow_extra_fields. Listado 2-9 - Deshabilitando la proteccin allow_extra_fields
class ContactoForm extends sfForm { publicfunction configure() { // ... $this->setValidators(array( 'email' =>new sfValidatorEmail(), 'asunto' =>new sfValidatorChoice(array('choices' =>array_keys(self::$asuntos))), 'mensaje' =>new sfValidatorString(array('min_length' =>4)), )); $this->validatorSchema->setOption('allow_extra_fields', true); } }

Con el cdigo anterior, ya es posible validar el formulario como se muestra en la figura 2-9.

Figura 2.9. Validando el formulario con la opcin allow_extra_fields activada Si observas con atencin la imagen anterior, vers que aunque el formulario es vlido, el valor del campo nombre est vaco en la pgina de agradecimiento, independientemente del valor que se haya escrito el usuario en ese campo. De hecho, el valor enviado no existe ni siquiera en el array que se obtiene mediante $this->formulario->getValues(). Deshabilitar la opcin allow_extra_fields evita que se muestre el error debido a la falta del validador, pero como la otra opcin llamada filter_extra_fields vale true por defecto, se filtran todos los campos que no tienen validador, por lo que sus valores no se encuentran entre los valores vlidos. El listado 2-10 muestra cmo modificar este comportamiento. Listado 2-10 - Deshabilitando la proteccin de filter_extra_fields
class ContactoForm extends sfForm { publicfunction configure() { // ...

$this->setValidators(array( 'email' =>new sfValidatorEmail(), 'asunto' =>new sfValidatorChoice(array('choices' =>array_keys(self::$asuntos))), 'mensaje' =>new sfValidatorString(array('min_length' =>4)), )); $this->validatorSchema->setOption('allow_extra_fields', true); $this->validatorSchema->setOption('filter_extra_fields', false); } }

Ahora ya es posible validar el formulario y obtener el valor introducido por el usuario para mostrarlo en la pgina de agradecimiento. En el captulo 4 se muestra cmo utilizar estas protecciones para serializar de forma segura los objetos Propel a partir de los valores del formulario.

2.5. Validadores lgicos


Un nico campo puede definir varios validadores utilizando los validadores lgicos:
y y sfValidatorAnd: para ser vlido, el campo debe pasar todos los validadores sfValidatorOr: para ser vlido, el campo debe pasar al menos un validador

Los constructores de los validadores lgicos aceptan como primer argumento una lista de validadores. El listado 2-11 emplea sfValidatorAnd para establecer dos validadores obligatorios en el campo nombre. Listado 2-11 - Empleando el validador sfValidatorAnd
class ContactoForm extends sfForm { public function configure() { // ... $this->setValidators(array( // ... 'nombre' => new sfValidatorAnd(array( new sfValidatorString(array('min_length' => 5)), new sfValidatorRegex(array('pattern' => '/[\w- ]+/')), )), )); } }

Cuando se enva el formulario, el valor del campo nombre debe tener al menos cinco caracteres y debe cumplir con la expresin regular [\w- ]+

Como los validadores lgicos tambin son validadores, se pueden combinar para crear validaciones tan complejas como las mostradas en el listado 2-12. Listado 2-12 - Combinando varios validadores lgicos
class ContactoForm extends sfForm { public function configure() { // ... $this->setValidators(array( // ... 'nombre' => new sfValidatorOr(array( new sfValidatorAnd(array( new sfValidatorString(array('min_length' => 5)), new sfValidatorRegex(array('pattern' => '/[\w- ]+/')), )), new sfValidatorEmail(), )), )); } }

2.6. Validadores globales


Los validadores mostrados hasta el momento siempre estn asociados a un campo especfico del formulario y slo permite validar un valor. Adems, estos validadores son independientes del resto de informacin enviada por el usuario. No obstante, en ocasiones la validacin de un campo depende del contexto o depende del valor de muchos otros campos. Los ejemplos tpicos de validadores globales son los dos campos de contrasea que deben ser iguales o el campo de fecha de inicio que debe ser anterior que la fecha de finalizacin. En cualquiera de estos casos, se debe utilizar un validador global que valide los datos introducidos teniendo en cuenta su contexto. Los validadores globales se pueden establecer antes o despus de la validacin individual de los campos mediante los pre-validadores y los post-validadores respectivamente. Normalmente es mejor utilizar un post-validador porque as los datos ya estn validados y limpios, por lo que su formato tambin se ha normalizado. El listado 2-13 muestra cmo comparar el valor de dos contraseas mediante el validador sfValidatorSchemaCompare. Listado 2-13 - Utilizando el validador sfValidatorSchemaCompare
$this->validatorSchema->setPostValidator(new sfValidatorSchemaCompare('password1', sfValidatorSchemaCompare::EQUAL, 'password2'));

A partir de Symfony 1.2, tambin puedes utilizar los operadores habituales de PHP para realizar las comparaciones en vez de las constantes de la clase sfValidatorSchemaCompare. Por tanto, el ejemplo anterior es equivalente a:
$this->validatorSchema->setPostValidator(new sfValidatorSchemaCompare('password1', '==', 'password2'));

Sugerencia

Al igual que el resto de validadores globales, la clase sfValidatorSchemaCompare hereda del validador sfValidatorSchema. De hecho, el propio sfValidatorSchema es un validador global, ya que valida todos los datos introducidos por el usuario pasando el valor de cada campo a su validador asociado. El listado 2-14 muestra cmo emplear un nico validador para validar que una fecha de inicio sea anterior a la fecha de finalizacin y adems aade un mensaje de error personalizado. Listado 2-14 - Utilizando el validador sfValidatorSchemaCompare
$this->validatorSchema->setPostValidator( new sfValidatorSchemaCompare('fecha_inicio', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'fecha_fin', array(), array('invalid' =>'La fecha de inicio ("%left_field%") debe ser anterior a la fecha de finalizacin ("%right_field%")') ) );

Utilizar un post-validador asegura que la comparacin entre las dos fechas es precisa. Independientemente del formato en el que se introduce cada fecha, la validacin de los campos fecha_inicio y fecha_fin asegura que sus valores se convierten en un formato fcilmente comparable (Y-m-d H:i:s por defecto). Por defecto, los pre-validadores y los post-validadores devuelven errores de tipo global al formulario. No obstante, algunos de esos validadores pueden asociar un error a un campo determinado. La opcin throw_global_error por ejemplo del validador sfValidatorSchemaCompare permite elegir entre un error global (figura 2-10) y un error asociado al primer campo (figura 2-11). El listado 2-15 muestra cmo utilizar la opcin throw_global_error. Listado 2-15 - Utilizando la opcin throw_global_error
$this->validatorSchema->setPostValidator( new sfValidatorSchemaCompare('fecha_inicio', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'fecha_fin', array('throw_global_error' =>true), array('invalid' =>'La fecha de inicio ("%left_field%") debe ser anterior a la fecha de finalizacin ("%right_field%")') )

);

Figura 2.10. Error global de un validador global

Figura 2.11. Error local de un validador global Por ltimo, si se utiliza un validador lgico es posible combinar varios post-validadores como muestra el listado 2-16. Listado 2-16 - Combinando varios post-validadores mediante un validador lgico
$this->validatorSchema->setPostValidator(new sfValidatorAnd(array( new sfValidatorSchemaCompare('fecha_inicio', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'fecha_fin'), new sfValidatorSchemaCompare('password1', sfValidatorSchemaCompare::EQUAL, 'password2'), )));

2.7. Subiendo archivos


La gestin de los archivos subidos con PHP, al igual que en otros lenguajes de programacin orientados a la web, implica el uso de cdigo HTML y de programacin en el servidor. En esta seccin se muestran las herramientas ofrecidas por el framework para simplificar el trabajo del programador y tambin se muestra cmo evitar los errores habituales.

A continuacin se modifica el formulario de contacto para permitir adjuntar archivos al mensaje. Para ello, se aade un campo llamado archivo, tal y como muestra el listado 217. Listado 2-17 - Aadiendo un campo archivo en el formulario ContactoForm
// lib/form/ContactoForm.class.php class ContactoForm extends sfForm { protected static$asuntos = array('Asunto A', 'Asunto B', 'Asunto C'); publicfunction configure() { $this->setWidgets(array( 'nombre' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(), 'asunto' =>new sfWidgetFormSelect(array('choices' => self::$asuntos)), 'mensaje' =>new sfWidgetFormTextarea(), 'archivo' =>new sfWidgetFormInputFile(), )); $this->widgetSchema->setNameFormat('contacto[%s]'); $this->setValidators(array( 'nombre' =>new sfValidatorString(array('required' =>false)), 'email' =>new sfValidatorEmail(), 'asunto' =>new sfValidatorChoice(array('choices' =>array_keys(self::$asuntos))), 'mensaje' =>new sfValidatorString(array('min_length' =>4)), 'archivo' =>new sfValidatorFile(), )); } }

Cuando se utiliza un widget de tipo sfWidgetFormInputFile para permitir la subida de archivos, se debe aadir el atributo enctype en la etiqueta <form>, tal y como se muestra en el listado 2-18. Listado 2-18 - Modificando la plantilla para tener en cuenta el campo archivo
<form action="<?php echo url_for('contacto/index') ?>" method="POST" enctype="multipart/form-data"> <table> <?phpecho$formulario?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>

Nota

Si generas de forma dinmica las plantillas asociadas a los formularios, puedes emplear el mtodo isMultipart() del objeto del formulario, ya que devuelve true si el formulario requiere el atributo enctype. La informacin de los archivos subidos no se almacena junto con el resto de informacin enviada. Por eso, es necesario modificar la llamada al mtodo bind() para pasar esa informacin como segundo parmetro, tal y como se muestra en el listado 2-19. Listado 2-19 - Pasando los archivos subidos al mtodo bind()
class contactoActions extends sfActions { publicfunction executeIndex($request) { $this->formulario = new ContactoForm(); if($request->isMethod('post')) { $this->formulario->bind($request->getParameter('contacto'), $request>getFiles('contacto')); if($this->formulario->isValid()) { $valores = $this->formulario->getValues(); // hacer cosas con los valores // ... } } } publicfunction executeGracias() { } }

Aunque el formulario ya es completamente funcional, es necesario modificar la accin para almacenar el archivo subido en un directorio. Como se explica al principio de este captulo, el validador sfValidatorFile convierte toda la informacin relacionada con el archivo subido en un objeto de tipo sfValidatedFile. El listado 2-20 muestra cmo utilizar este objeto para almacenar el archivo subido en el directorio web/uploads. Listado 2-20 - Utilizando el objeto sfValidatedFile
if($this->formulario->isValid()) { $archivo = $this->formulario->getValue('archivo'); $nombreArchivo = 'subido_'.sha1($archivo->getOriginalName()); $extension = $archivo->getExtension($archivo->getOriginalExtension()); $archivo>save(sfConfig::get('sf_upload_dir').'/'.$nombreArchivo.$extension);

// ... }

La siguiente tabla muestra todos los mtodos del objeto sfValidatedFile:


Mtodo
save() isSaved() getSavedName() getExtension() getOriginalName()

Descripcin Guarda el archivo subido Devuelve true si el archivo se ha guardado Devuelve el nombre del archivo guardado Devuelve la extensin del archivo, en funcin de su tipo MIME Devuelve el nombre del archivo subido

getOriginalExtension() Devuelve la extensin del nombre del archivo subido getTempName() getType() getSize()

Devuelve la ruta del archivo temporal Devuelve el tipo MIME del archivo Devuelve el tamao del archivo

Sugerencia

El tipo MIME que proporciona el navegador por cada archivo subido no es fiable. Para asegurar la mxima seguridad, durante la validacin del archivo se ejecutan de forma secuencial las funciones finfo_open y mime_content_type y la herramienta file. Si ninguna de esas funciones es capaz de determinar el tipo MIME del archivo y si el sistema tampoco es capaz de proporcionarlo, se utiliza como ltimo recurso el tipo MIME ofrecido por el navegador. Para aadir o modificar las funciones que tratan de averiguar el tipo MIME, se utiliza la opcin mime_type_guessers en el constructor de sfValidatorFile.

Captulo 3. Formularios para diseadores web


Los captulos 1 y 2 muestran cmo crear formularios mediante los widgets y las reglas de validacin. En esos captulos, los formularios se muestran mediante la instruccin <?php echo $formulario ?>. Esta instruccin permite a los programadores centrarse en la lgica de la aplicacin sin pensar en su aspecto. De esta forma, no es necesario modificar la plantilla cada vez que se modifica o se aade un campo. Por todo ello, esta instruccin es muy til cuando se crea el prototipo de una aplicacin o cuando se est en las fases iniciales en las que el programador slo se tiene que centrar en el modelo y en la lgica de negocio.

Una vez que el desarrollo de la aplicacin se estabiliza y despus de crear la gua de estilo del sitio, el diseador web puede aplicar otro formato a todos los formularios de la aplicacin. Antes de leer este captulo es obligatorio que conozcas y domines el sistema de plantillas y la capa de la vista de Symfony. Para ello, puedes leer el captulo 7 del libro oficial de Symfony 1.1.
Nota

El sistema de formularios de Symfony cumple con el modelo MVC. El patrn MVC permite desacoplar las tareas del equipo de desarrollo: los programadores crean los formularios y se encargan de gestionar su lgica y los diseadores web aplican formato y estilos a los formularios. No obstante, la separacin de responsabilidades no implica que ya no sea necesaria una comunicacin fluida entre todos los miembros del equipo.

3.1. Antes de comenzar


En los siguientes ejemplos se va a utilizar el mismo formulario de contacto utilizado en los captulos 1 y 2 (ver figura 3-1). Para aquellos diseadores web que slo van a leer este captulo, a continuacin se describen sus caractersticas:
y y y

El formulario dispone de cuatro campos: nombre, email, asunto y mensaje. El formulario se incluye en el mdulo contacto. La accin index pasa a la plantilla una variable llamada formulario que representa al formulario completo.

A lo largo de este captulo se explican las distintas posibilidades que existen para personalizar la plantilla que muestra el formulario (listado 3-1).

Figura 3.1. El formulario de contacto Listado 3-1 - La plantilla del prototipo utilizado para mostrar el formulario de contacto
// apps/frontend/modules/contacto/templates/indexSuccess.php <form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <?php echo $formulario ?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>

Subiendo archivos Si se incluye en el formulario un campo para subir archivos, es necesario aadir el atributo enctype en la etiqueta <form>:
<form action="<?php echo url_for('contacto/index') ?>" method="POST" enctype="multipart/data">

El mtodo isMultipart() del objeto $formulario devuelve true si el formulario requiere incluir ese atributo:
<form action="<?php echo url_for('contacto/index') ?>" method="POST" <?php if($formulario->isMultipart()) { echo 'enctype="multipart/formdata"' } ?>>

3.2. La plantilla del prototipo


Por el momento, para generar el cdigo HTML que muestra el formulario se ha empleado la instruccin <?php echo $formulario ?> en la plantilla del prototipo. Los formularios estn compuestos por campos. A su vez, en la plantilla cada campo est formado por tres elementos:
y y y

El ttulo del campo o label La etiqueta del campo (<input>, <select>, etc.) Opcionalmente, los mensajes de error del campo

La instruccin <?php echo $formulario ?> genera automticamente el cdigo HTML de todos esos elementos, como muestra el listado 3-2 en el caso de un formulario enviado con datos no vlidos.

Listado 3-2 - Plantilla generada automticamente cuando se enva un formulario con datos no vlidos
<form action="/frontend_dev.php/contacto" method="POST"> <table> <tr> <th><label for="contacto_nombre">Nombre</label></th> <td><input type="text" nombre="contacto[nombre]" id="contacto_nombre" /></td> </tr> <tr> <th><label for="contacto_email">Email</label></th> <td> <ul class="error_list"> <li>This email address is invalid.</li> </ul> <input type="text" nombre="contacto[email]" value="fabien" id="contacto_email" /> </td> </tr> <tr> <th><label for="contacto_asunto">Asunto</label></th> <td> <select nombre="contacto[asunto]" id="contacto_asunto"> <option value="0" selected="selected">Asunto A</option> <option value="1">Asunto B</option> <option value="2">Asunto C</option> </select> </td> </tr> <tr> <th><label for="contacto_mensaje">Mensaje</label></th> <td> <ul class="error_list"> <li>The mensaje "foo" is too short. It must be of 4 characters at least.</li> </ul> <textarea rows="4" cols="30" nombre="contacto[mensaje]" id="contacto_mensaje">foo</textarea> </td> </tr> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>

A continuacin se explica de forma detallada el cdigo anterior. La figura 3-2 muestra las filas de tabla (etiqueta <tr>) que se generan por cada campo.

Figura 3.2. Cdigo HTML generado para cada campo del formulario Para cada campo de la figura 3-3 se generan tres trozos de cdigo HTML, correspondientes a los tres elementos que forman cada campo. Para el campo email se generan por ejemplo el siguiente cdigo HTML:
y

El ttulo o label:

<label for="contact_email">Email</label> y

La etiqueta del campo:

<input type="text" nombre="contacto[email]" value="fabien" id="contacto_email" /> y

Los mensajes de error:

<ul class="error_list"> <li>The email address is invalid.</li> </ul>

Figura 3.3. Descomposicin del campo del email Sugerencia Todos los campos disponen de un atributo id generado automticamente, lo que permite a los programadores aadirles estilos CSS o comportamientos JavaScript de forma muy sencilla.

3.3. Personalizando la plantilla del prototipo


La instruccin <?php echo $formulario ?> puede ser suficiente para los formularios sencillos como el formulario de contacto. En realidad, la instruccin anterior es un atajo de la instruccin <?php echo $formulario->render() ?> Utilizar de forma explcita el mtodo render() permite pasar como argumentos los atributos HTML de cada campo. El listado 3-3 muestra cmo aadir un atributo class al campo email. Listado 3-3 - Aadiendo atributos HTML propios con el mtodo render()
<?phpecho$formulario->render(array('email' =>array('class' =>'email')))?> // Cdigo HTML generado

<input type="text" nombre="contacto[email]" value="" id="contacto_email"class="email" />

Aunque este mtodo permite personalizar el estilo del formulario, no ofrece la flexibilidad necesaria para modificar la estructura o layout del formulario.

3.4. Personalizando el diseo


Ms all de la personalizacin global que permite el mtodo render(), a continuacin se explica cmo mostrar uno a uno los campos del formulario para obtener la mxima flexibilidad.
3.4.1. Utilizando el mtodo renderRow() en cada campo

La primera forma de conseguir la mxima flexibilidad consiste en generar cada campo de forma individual. De hecho, la instruccin <?php echo $formulario ?> es equivalente a llamar al mtodo renderRow() cuatro veces, como se muestra en el listado 3-4. Listado 3-4 - Utilizando el mtodo renderRow()
<form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <?phpecho$formulario['nombre']->renderRow()?> <?phpecho$formulario['email']->renderRow()?> <?phpecho$formulario['asunto']->renderRow()?> <?phpecho$formulario['mensaje']->renderRow()?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>

El acceso a cada campo se realiza utilizando el objeto $formulario como si fuera un array. El campo email se puede acceder mediante $formulario['email']. El mtodo renderRow() muestra cada campo como una fila de una tabla HTML. La instruccin $formulario['email']->renderRow() genera por tanto una fila de tabla para mostrar el campo email. Para mostrar el formulario completo, se repite el mismo tipo de cdigo para los otros tres campos asunto, nombre y mensaje. Cmo es posible que un objeto se comporte como un array? Desde la versin PHP 5, los objetos puede comportarse de forma similar a los arrays. La clase sfForm implementa el comportamiento ArrayAccess para permitir el acceso a cada campo mediante una sintaxis corta y muy sencilla. La clave del array es el nombre del campo y el valor devuelto es el objeto de tipo widget asociado:

<?phpecho$formulario['email']?> // Sintaxis que habra que utilizar si sfForm no implementara la interfaz ArrayAccess <?phpecho$formulario->getField('email')?>

No obstante, como en la plantilla todas las variables deben ser de slo lectura, si intentas modificar el valor del campo se lanza una excepcin de tipo LogicException:
<?php$formulario['email'] = ... ?> <?phpunset($formulario['email'])?>

La plantilla actual y la plantilla original son funcionalmente indnticas. No obstante, aunque su aspecto es idntico, ahora es mucho ms fcil personalizar ese aspecto. El mtodo renderRow() acepta dos argumentos: un array con los atributos HTML y el nombre del ttulo o label. El listado 3-5 utiliza estos dos argumentos para personalizar el formulario y la figura 3-4 muestra el resultado. Listado 3-5 - Utilizando los argumentos del mtodo renderRow() para personalizar el aspecto del formulario
<form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <?phpecho$formulario['nombre']->renderRow()?> <?phpecho$formulario['email']->renderRow(array('class' =>'email'))?> <?phpecho$formulario['asunto']->renderRow()?> <?phpecho$formulario['mensaje']->renderRow(array(), 'Tu mensaje')?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>

Figura 3.4. Personalizando el aspecto del formulario con el mtodo renderRow() Para generar el campo emaill slo se utiliza el primer argumento de renderRow():
y array('class' => 'email') aade la clase emaill a la etiqueta <input> generada.

El campo mensaje se genera de forma similar, pero utiliza los dos argumentos:
y y array() significa que no se quieren aadir atributos HTML propios a la etiqueta <textarea> generada. "Tu mensaje" permite sustituir el ttulo o label original

Todos los argumentos de renderRow() son opcionales, por lo que no es obligatorio indicarlos, tal y como sucede en los campos nombre y asunto del ejemplo anterior. Aunque el mtodo renderRow() permite personalizar los elementos que forman cada campo, sus posibilidades estn limitadas por el cdigo HTML que se utiliza para decorar cada elemento, como se muestra en la figura 3-5.

Figura 3.5. Estructura HTML utilizada por renderRow() y render() Cmo modificar la estructura utilizada en el prototipo Symfony utiliza por defecto una tabla HTML para mostrar el formulario. Este comportamiento se puede modificar utilizando otros formatos especficos, ya sean formatos definidos por Symfony o formatos creados de forma expresa para el proyecto. En el captulo 5 se muestra que para crear un nuevo formato es preciso crear una clase. Para crear una estructura completamente diferente, existen mtodos para generar cada uno de los elementos de los campos, como muestra la figura 3-6:
y y renderLabel(): genera el ttulo (etiqueta <label> asociada al campo) render(): genera el campo (etiqueta <input>, <select>, <textarea>, etc.)

renderError(): genera los mensajes de error (en forma de lista de elementos <ul class="error_list">)

Figura 3.6. Mtodos disponibles para personalizar un campo Cada uno de estos campos se explican detalladamente al final de este captulo.
3.4.2. Utilizando el mtodo ''render()'' en cada campo

Imagina que ahora se quiere mostrar el formulario en dos columnas. Como se ve en la figura 3-7, los campos nombre y email se muestran en la misma fila, mientras que los campos asunto y mensaje se muestran cada uno en su propia fila.

Figura 3.7. Mostrando un formulario en varias columnas Para conseguir esa estructura es necesario generar de forma individual cada campo del formulario. Como ya se ha comentado, el objeto formulario permite el acceso a cada campo como si fuera un array asociativo. El campo email por ejemplo se puede acceder mediante $formulario['email']. El listado 3-6 muestra cmo crear un formulario con dos columnas.

Listado 3-6 - Mostrar el formulario en dos columnas


<form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <tr> <th>Nombre:</th> <td><?php echo$formulario['nombre']->render() ?></td> <th>Email:</th> <td><?php echo$formulario['email']->render() ?></td> </tr> <tr> <th>Asunto:</th> <td colspan="3"><?php echo$formulario['asunto']->render() ?></td> </tr> <tr> <th>Mensaje:</th> <td colspan="3"><?php echo$formulario['mensaje']->render() ?></td> </tr> <tr> <td colspan="4"> <input type="submit" /> </td> </tr> </table> </form>

De la misma forma que no es obligatorio indicar explcitamente el mtodo render() sobre el formulario, tampoco es obligatorio indicarlo sobre cada campo, de forma que se puede reescribir la plantilla como muesta el listado 3-7. Listado 3-7 - Simplificando el formulario a dos columnas
<form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <tr> <th>Nombre:</th> <td><?php echo$formulario['nombre'] ?></td> <th>Email:</th> <td><?php echo$formulario['email'] ?></td> </tr> <tr> <th>Asunto:</th> <td colspan="3"><?php echo$formulario['asunto'] ?></td> </tr> <tr> <th>Mensaje:</th> <td colspan="3"><?php echo$formulario['mensaje'] ?></td> </tr> <tr> <td colspan="4"> <input type="submit" /> </td> </tr> </table> </form>

Al igual que el formulario, cada campo se puede personalizar pasando un array con atributos HTML al mtodo render(). El listado 3-8 muestra cmo modificar el atributo class del campo email. Listado 3-8 - Modificando los atributos HTML con el mtodo render()
<?phpecho$formulario['email']->render(array('class' =>'email'))?> // Cdigo HTML generado <input type="text" nombre="contacto[email]"class="email" id="contacto_email" />

3.4.3. Utilizando el mtodo ''renderLabel()'' en cada campo

En el ejemplo anterior no se han generado ttulos o labels durante la personalizacin del formulario. El listado 3-9 utiliza el mtodo renderLabel() para generar un ttulo para cada campo. Listado 3-9 - Utilizando el mtodo renderLabel()
<form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <tr> <th><?php echo$formulario['nombre']->renderLabel()?>:</th> <td><?php echo$formulario['nombre'] ?></td> <th><?php echo$formulario['email']->renderLabel()?>:</th> <td><?php echo$formulario['email'] ?></td> </tr> <tr> <th><?php echo$formulario['asunto']->renderLabel()?>:</th> <td colspan="3"><?php echo$formulario['asunto'] ?></td> </tr> <tr> <th><?php echo$formulario['mensaje']->renderLabel()?>:</th> <td colspan="3"><?php echo$formulario['mensaje'] ?></td> </tr> <tr> <td colspan="4"> <input type="submit" /> </td> </tr> </table> </form>

El valor de la etiqueta <label> se genera automticamente a partir del nombre del campo. No obstante, tambin es posible utilizar un ttulo propio si se pasa como argumento al mtodo renderLabel(), como se muestra en el listado 3-10. Listado 3-10 - Modificando el ttulo del campo
<?phpecho$formulario['mensaje']->renderLabel('Tu mensaje')?>

// Cdigo HTML generado <label for="contacto_mensaje">Tu mensaje</label>

Por qu se utiliza el mtodo renderLabel() pasndole el ttulo como argumento? Por qu no se utiliza una etiqueta <label> de HTML en su lugar? El motivo es que el mtodo renderLabel() genera una etiqueta <label> y le aade de forma automtica un atributo for para asociarlo al campo de formulario mediante el atributo id. De esta forma te aseguras que el campo sea accesible ya que cuando se pulsa sobre la etiqueta, el campo se selecciona de forma automtica:
<labelfor="contacto_email">Email</label> <inputtype="text" nombre="contacto[email]"id="contacto_email" />

Adems, se pueden aadir atributos HTML pasando un segundo argumento al mtodo renderLabel():
<?phpecho$formulario['enviar_notificacion']->renderLabel(null, array('class' =>'inline'))?> // Cdigo HTML generado <label for="contacto_enviar_notificacion"class="inline">Enviar notificacion</label>

En el ejemplo anterior, el primer argumento del mtodo es null, por lo que se utiliza la generacin automtica del texto del ttulo.
3.4.4. Utilizando el mtodo ''renderError()'' en cada campo

La plantilla, tal y como est definida ahora mismo, no tiene en cuenta los mensajes de error. El listado 3-11 vuelve a mostrar los errores utilizando el mtodo renderError(). Listado 3-11 - Mostrando los mensjes de error con el mtodo renderError()
<form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <tr> <th><?php echo$formulario['nombre']->renderLabel()?>:</th> <td> <?phpecho$formulario['nombre']->renderError()?> <?phpecho$formulario['nombre']?> </td> <th><?php echo$formulario['email']->renderLabel()?>:</th> <td> <?phpecho$formulario['email']->renderError()?> <?phpecho$formulario['email']?> </td> </tr> <tr> <th><?php echo$formulario['asunto']->renderLabel()?>:</th> <td colspan="3"> <?phpecho$formulario['asunto']->renderError()?>

<?phpecho$formulario['asunto']?> </td> </tr> <tr> <th><?php echo$formulario['mensaje']->renderLabel()?>:</th> <td colspan="3"> <?phpecho$formulario['mensaje']->renderError()?> <?phpecho$formulario['mensaje']?> </td> </tr> <tr> <td colspan="4"> <input type="submit" /> </td> </tr> </table> </form>

3.4.5. Personalizando los mensajes de error hasta el ltimo detalle

El mtodo renderError() genera una lista con los errores asociados al campo del formulario. Slo genera cdigo HTML si el campo tiene al menos un error. Por defecto, la lista generada es de tipo lista no ordenada de HTML (<ul>). Aunque este comportamiento es adecuado la mayora de las veces, existen dos mtodos llamados hasError() y getError() que permiten el acceso directo a todos los errores. El listado 3-2 muestra cmo personalizar los mensajes de error del campo email. Listado 3-12 - Accediendo a los mensajes de error
<?phpif($formulario['email']->hasError()): ?> <ul class="error_list"> <?phpforeach($formulario['email']->getError()as$error): ?> <li><?php echo$error ?></li> <?phpendforeach; ?> </ul> <?phpendif; ?>

El cdigo del ejemplo anterior genera exactamente el mismo cdigo HTML que la llamada al mtodo renderError().
3.4.6. Trabajando con campos ocultos

En los siguientes ejemplos se supone que existe en el formulario un campo oculto obligatorio llamado origen. Este campo almacena la pgina desde la que ha llegado el usuario hasta el formulario. La instruccin <?php echo $formulario ?> genera el cdigo HTML de los campos ocultos y lo aade despus del ltimo campo visible, tal y como se muestra en el listado 3-13. Listado 3-13 - Generando el cdigo HTML de los campos ocultos

<tr> <th><label for="contacto_mensaje">Mensaje</label></th> <td> <textarea rows="4" cols="30" nombre="contacto[mensaje]" id="contacto_mensaje"></textarea> <input type="hidden" nombre="contacto[origen]" id="contacto_origen" /> </td> </tr>

Como se observa en el cdigo generado para el campo oculto origen, slo se genera la parte correspondiente a la etiqueta <input type="hidden">. Obviamente, no tiene sentido generar el ttulo o label de un campo oculto. En cuanto a los posibles errores de este campo, aunque se trata de un campo oculto, es posible que se produzcan errores debidos a cualquier problema en el cdigo. Aunque estos errores no se asocian directamente al campo origen, se aaden a los errores globales. En el captulo 5 se muestran otros casos en los que se utilizan errores globales. La figura 3-8 muestra cmo se visualizan los mensajes de error que se producen en el campo origen y el listado 3-14 muestra el cdigo generado para esos errores.

Figura 3.8. Mostrando los mensajes de error globales Listado 3-14 - Generando mensajes de error globales
<tr> <tdcolspan="2"> <ulclass="error_list"> <li>Origen: Required.</li> </ul> </td> </tr>

Cuidado

Cuando personalices el diseo de un formulario, no olvides incluir los campos ocultos y los mensajes de error globales.
3.4.7. Trabajando con los errores globales

Los formularios disponen de tres tipos de error:


y y y

Errores asociados a un campo especfico Errores globales Errores de campos ocultos (<input type="hidden">) o de campos que no se muestran en el formulario. Estos errores se aaden a los errores globales.

En las secciones anteriores ya se ha visto cmo tratar los errores asociados a los campos y el listado 3-15 muestra cmo tratar los mensajes de error globales. Listado 3-15 - Trabajando con mensajes de error globales
<form action="<?php echo url_for('contacto/index') ?>" method="POST"> <table> <tr> <td colspan="4"> <?phpecho$formulario->renderGlobalErrors()?> </td> </tr> // ... </table>

La llamada al mtodo renderGlobalErrors() muestra la lista de errores globales. Tambin es posible acceder de forma individual a los errores globales utilizando los mtodos hasGlobalErrors() y getGlobalErrors(), como se muestra en el listado 3-16. Listado 3-16 - Personalizando los errores globales con los mtodos hasGlobalErrors() y getGlobalErrors()
<?phpif($formulario->hasGlobalErrors()): ?> <tr> <td colspan="4"> <ul class="error_list"> <?phpforeach($formulario->getGlobalErrors()as$nombre =>$error): ?> <li><?php echo$nombre.': '.$error ?></li> <?phpendforeach; ?> </ul> </td> </tr> <?phpendif; ?>

Cada error global dispone de un nombre ($nombre en el cdigo anterior) y un mensaje ($error en el cdigo anterior). Si el error es realmente global, el nombre siempre est

vaco, pero si el error global tiene su origen en un campo oculto, el nombre es realmente el ttulo o label del campo oculto. Aunque la plantilla actual (figura 3-8) es equivalente a la plantilla con la que empez este captulo, el diseo de la nueva plantilla es completamente personalizable.

Figura 3.9. Formulario personalizado que utiliza los mtodos de los campos

3.5. Internacionalizacin
Todos los elementos del formulario, desde los ttulos hasta los mensajes de error, utilizan de forma automtica el sistema de internacionalizacin de Symfony. Por lo tanto, el diseador web no tiene que aadir nada especial a las plantillas para poder internacionalizar los formularios, incluso aunque establezca el ttulo de los campos con el mtodo renderLabel(). La traduccin de todos los textos se realiza de forma automtica. El captulo 9 incluye informacin mucho ms detallada de la internacionalizacin de los formularios.

3.6. Trabajando con el programador


Para concluir este captulo, se describe el escenario de trabajo tpico cuando se desarrollan formularios con Symfony:
y

El equipo de desarrollo crea la clase del formulario y su accin. La plantilla no es nada ms que la instruccin <?php echo $formulario ?>

Mientras tanto, los diseadores crean la gua de estilo de la aplicacin y las reglas que se van a aplicar a los formularios: estructura global, visualizacin de los mensajes de error, etc. Una vez que la lgica de negocio est terminada y que la gua de estilo ha sido aprobada, el equipo de diseo modifica las plantillas de los formularios. Lo nico que deben conocer los diseadores es el nombre de los campos y la accin que se encarga de procesar el formulario.

Una vez concluida esta primera fase, ya es posible modificar la lgica de negocio y el aspecto de las plantillas de forma simultnea. El equipo de desarrollo puede realizar varias tareas sin que afecten a las plantillas y por tanto, sin la necesidad de que intervenga un diseador:
y y y

Modificar los widgets del formulario Personalizar los mensajes de error Aadir, modificar o borrar reglas de validacin

Igualmente, el equipo de diseo puede realizar todos los cambios estticos que desee sin requerir la intervencin del equipo de desarrollo. No obstante, las siguientes acciones requieren la coordinacin de los dos equipos:
y y

Modificar el nombre de un campo Aadir o eliminar campos

La coordinacin en las tareas anteriores es lgica ya que implican cambios tanto en la programacin como en la visualizacin del formulario. Como se indica al inicio de este captulo, el mecanismo de formularios permite la divisin de las tareas, pero la comunicacin entre los equipos de desarrollo y diseo sigue siendo imprescindible.

Captulo 4. Integracin con Propel


En los proyectos web, la mayora de formularios se utilizan para crear o modificar objetos del modelo de datos. Este tipo de objetos luego se guardan en una base de datos mediante un ORM. El sistema de formularios de Symfony dispone de una capa adicional que sirve de interfaz con Propel, el ORM incluido en Symfony. Gracias a esta interfaz, es muy sencillo crear formularios basados en estos objetos del modelo. Este captulo explica detalladamente cmo integrar los formularios y los objetos Propel. Por ese motivo, es muy recomendable que conozcas y domines el uso de Propel y su integracin con Symfony. Para ello, puedes leer el captulo 8 del libro oficial de Symfony 1.1.

4.1. Antes de comenzar

En los ejemplos de este captulo se crea un sistema para gestionar artculos (como por ejemplo los artculos de un blog). A continuacin se muestra el esquema de la base de datos, que est formado por cinco tablas: articulo, autor, categoria, etiqueta y articulo_etiqueta. Listado 4-1 - Esquema de la base de datos
// config/schema.yml propel: articulo: id: ~ titulo: { type: varchar(255), required: true } slug: { type: varchar(255), required: true } contenido: longvarchar estado: varchar(255) autor_id: { type: integer, required: true, foreignTable: autor, foreignReference: id, onDelete: cascade } categoria_id: { type: integer, required: false, foreignTable: categoria, foreignReference: id, onDelete: setnull } fecha_publicacion: timestamp created_at: ~ updated_at: ~ _uniques: unique_slug: [slug] autor: id: nombre: apellidos: email: activo: categoria: id: nombre: etiqueta: id: nombre: ~ varchar(20) varchar(20) { type: varchar(255), required: true } boolean ~ { type: varchar(255), required: true } ~ { type: varchar(255), required: true } foreignTable: articulo, onDelete: cascade } foreignTable: etiqueta, onDelete: cascade }

articulo_etiqueta: articulo_id: { type: integer, foreignReference: id, primaryKey: true, etiqueta_id: { type: integer, foreignReference: id, primaryKey: true,

Las relaciones entre las tablas son las siguientes:


y y y

Relacin 1-n entre las tablas articulo y autor: cada artculo est escrito por uno y slo un autor. Relacin 1-n entre las tablas articulo y categoria: cada artculo est asociado a cero o una categora. Relacin n-n entre las tablas articulo y etiqueta.

4.2. Generando las clases del formulario


En los siguientes ejemplos se va a modificar la informacin de las tablas articulo, autor, categoria y etiqueta. Para ello, se crean formularios asociados a cada una de las tablas y se configuran los widgets y validadores relacionados con el esquema de la base de datos. Aunque es posible crear estos formularios a mano, se trata de una tarea tediosa y pesada, adems de que obliga a repetir la misma informacin en varios archivos (nombre de columans y campos, mximo tamao de columnas y campos, etc.) Adems, si los formularios se hacen a mano, cada vez que se modifica el modelo, se debe modificar la clase del formulario asociado. Afortunadamente, el plugin de Propel incluye una tarea llamada propel_build-forms que automatiza todo este proceso y genera los formularios relacionados con un objeto del modelo de datos:
$ ./symfony propel:build-forms

Cuando se generan los formularios, esta tarea crea una clase por cada tabla y le aade los validadores y widgets necesarios para cada columna teniendo en cuenta la informacin disponible en el modelo y la relacin entre las tablas.
Nota

Las tareas propel:build-all y propel:build-all-load tambin actualizan las clases de los formularios, ya que invocan de forma automtica la clase propel:build-forms. Despus de ejecutar esta tarea, se crea una estructura de archivos en el directorio lib/form/. Considerando el modelo de datos del ejemplo anterior, se crea la siguiente estructura de archivos:
lib/ form/ BaseFormPropel.class.php ArticuloForm.class.php ArticuloEtiquetaForm.class.php AutorForm.class.php CategoriaForm.class.php EtiquetaForm.class.php base/ BaseArticuloForm.class.php BaseArticuloEtiquetaForm.class.php BaseAutorForm.class.php BaseCategoriaForm.class.php BaseEtiquetaForm.class.php

La tarea propel:build-forms genera dos clases para cada tabla del esquema, una en el directorio lib/form/base y la otra en el directorio lib/form/. Para la tabla autor por ejemplo, se generan las clases BaseAutorForm y AutorForm que se guardan respectivamente en los archivos lib/form/base/BaseAutorForm.class.php y lib/form/AutorForm.class.php.

Directorio de generacin de los formularios La tarea propel:build-forms genera sus archivos con una estructura similar a la estructura utilizada por Propel. El atributo package del esquema de datos de Propel permite agrupar de forma lgica diferentes tablas. El paquete por defecto es lib.model, por lo que Propel genera sus archivos en el directorio lib/model/ y las clases de los formularios se generan en el directorio lib/form. si se emplea el valor lib.model.cms en el atributo package como se muestra en el siguiente ejemplo, las clases de Propel se generan en el directorio lib/model/cms/ y las clases del formulario se generan en el directorio lib/form/cms/.
propel: _attributes: { noXsd: false, defaultIdMethod: none, package: lib.model.cms } # ...

Como se ver en el captulo 5, los paquetes son muy tiles para dividir el esquema de la base de datos y para incluir formularios en los plugins. El captulo 8 del libro oficial de Symfony 1.1 dispone de ms informacin sobre los paquetes de Propel. La siguiente tabla muestra la jerarqua de las diferentes clases relacionadas con la definicin del formulario AutorForm.
Clase Paquete Quin trabaja con ella El programador Descripcin Redefine el formulario generado automticamente Basado en el esquema y generado cada vez que se ejecuta la tarea propel:build-forms Permite personalizar de forma global los formularios Propel Base de todos los formularios Propel Base de todos los formularios Symfony

AutorForm

Proyecto

BaseAutorForm Proyecto

Symfony

BaseFormPropel Proyecto

El programador

sfFormPropel

Plugin de Propel Symfony

Symfony Symfony

sfForm

Para crear o modificar un objeto de la clase Autor, se utiliza la clase AutorForm que se muestra en el listado 4-2. Como se puede observar, esta clase no contiene ningn mtodo, ya que hereda de BaseAutorForm, que es la clase que se genera automticamente en

funcin de la configuracin. La clase AutorForm es la clase que se utiliza para personalizar y redefinir la configuracin del formulario. Listado 4-2 - Clase AutorForm
class AutorForm extends BaseAutorForm { publicfunction configure() { } }

El listado 4-3 muestra el contenido de la clase BaseAutorForm con los validadores y widgets que se han creado automticamente mediante la introspeccin de la tabla autor del modelo de datos. Listado 4-3 - Clase BaseAutorForm que representa el formulario de la tabla autor
class BaseAutorForm extends BaseFormPropel { publicfunction setup() { $this->setWidgets(array( 'id' =>new sfWidgetFormInputHidden(), 'nombre' =>new sfWidgetFormInput(), 'apellidos' =>new sfWidgetFormInput(), 'email' =>new sfWidgetFormInput(), )); $this->setValidators(array( 'id' =>new sfValidatorPropelChoice(array('model' =>'Autor', 'column' =>'id', 'required' =>false)), 'nombre' =>new sfValidatorString(array('max_length' =>20, 'required' =>false)), 'apellidos' =>new sfValidatorString(array('max_length' =>20, 'required' =>false)), 'email' =>new sfValidatorString(array('max_length' =>255)), )); $this->widgetSchema->setNameFormat('autor[%s]'); $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema); parent::setup(); } publicfunction getModelName() { return'Autor'; } }

La clase generada automticamente es muy parecida a los formularios que se han creado en los captulos anteriores, con las siguientes excepciones:
y y y

La clase base es BaseFormPropel en vez de sfForm La configuracin de los validadores y de los widgets se realiza en el mtodo setup(), en vez de en el mtodo configure() El mtodo getModelName() devuelve el nombre de la clase Propel relacionada con el formulario

Personalizar los formularios Propel de forma global Adems de las clases generadas para cada tabla, la tarea propel:build-forms genera una clase llamada BaseFormPropel. Esta clase inicialmente est vaca y es la clase base de todas las dems clases que se encuentran en el directorio lib/form/base/. Gracias a esta clase es posible configurar de forma global el comportamiento de todos los formularios de Propel. El siguiente ejemplo muestra cmo modificar el formato de todos los formularios Propel:
abstract class BaseFormPropel extends sfFormPropel { publicfunction setup() { sfWidgetFormSchema::setDefaultFormFormatterName('div'); } }

Adems, la clase BaseFormPropel hereda de la clase sfFormPropel. Esta ltima clase incluye funcionalidades especficas de Propel, como por ejemplo el proceso de almacenar en la base de datos la informacin enviada por el usuario.
Sugerencia

Las clases base utilizan el mtodo setup() en vez de configure() para realizar su configuracin. De esta forma es posible redefinir la configuracin de las clases generadas vacas sin tener que incluir la instruccin parent::configure() Los nombres de los campos del formulario son idnticos a los nombres de las columnas del esquema de datos: id, nombre, apellidos y email. Para cada columna de la tabla autor, la tarea propel:build-forms genera un widget y un validador que cumplan con la definicin del esquema de datos. Esta tarea siempre genera los validadores ms seguros que sean posible. Si se considera por ejemplo el campo id, se podra validar solamente que sea un nmero entero. Sin embargo, el validador generado automticamente tambin comprueba que ese identificador existe (cuando se quiere modificar un objeto existente) o que ese identificador no exista (cuando se quiere crear un nuevo objeto). De esta forma, el validador utilizado es mucho ms seguro que simplemente comprobar que es un nmero entero.

Los formularios generados de forma automtica estn listos para utilizarse sin realizar ningn cambio. Si se aade la instruccin <?php echo $formulario ?> en la plantilla, ya se dispone de un formulario completamente funcional y con validacin de datos sin tener que escribir ni una sola lnea de cdigo. Adems de la posibilidad de crear prototipos de forma muy rpida, los formularios generados automticamente son fciles de modificar sin tener que editar las clases generadas. La razn es la herencia de las clases base y las clases del formulario. Cada vez que se modifica el esquema de la base de datos, esta tarea permite volver a generar los formularios para que tengan en cuenta todas las modificaciones realizadas y sin perder toda la configuracin realizada en los formularios.

4.3. El generador CRUD


Despus de generar todas las clases del formulario, ahora se muestra cmo crear un mdulo de Symfony que permita trabajar con todos los objetos a travs del navegador web. En los siguientes ejemplos se muestra cmo crear, modificar y borrar objetos de las clases Articulo, Autor, Categoria y Etiqueta. En primer lugar se crea el modelo de la clase Autor. Aunque el mdulo se puede crear a mano, el plugin de Propel incluye una tarea llamada propel:generate-crud que genera un mdulo de tipo CRUD basado en una clase del modelo de objetos de Propel:
$ ./symfony propel:generate-crud frontend autor Autor

La tarea propel:generate-crud requiere tres argumentos:


y y y frontend: nombre de la aplicacin en la que se va a crear el mdulo autor: nombre del mdulo que se va a crear Autor: nombre de la clase del modelo en la que se basa el mdulo

Nota

El acrnimo CRUD significa Creation / Retrieval / Update / Deletion (Crear / Obtener / Modificar / Borrar) que son las cuatro operaciones bsicas que se realizan sobre la informacin del modelo de datos. En el listado 4-4 se muestran las cinco acciones que genera la tarea sobre la clase Autor: listar la informacin (index), crear nuevos objetos (create), modificar los objetos existentes (edit), guardar los cambios realizados (update) y borrar los objetos (delete). Listado 4-4 - La clase autorActions generada por la tarea
// apps/frontend/modules/autor/actions/actions.class.php class autorActions extends sfActions

{ publicfunction executeIndex() { $this->autorList = AutorPeer::doSelect(new Criteria()); } publicfunction executeCreate() { $this->form = new AutorForm(); $this->setTemplate('edit'); } publicfunction executeEdit($request) { $this->form = new AutorForm(AutorPeer::retrieveByPk($request>getParameter('id'))); } publicfunction executeUpdate($request) { $this->forward404Unless($request->isMethod('post')); $this->form = new AutorForm(AutorPeer::retrieveByPk($request>getParameter('id'))); $this->form->bind($request->getParameter('autor')); if($this->form->isValid()) { $autor = $this->form->save(); $this->redirect('autor/edit?id='.$autor->getId()); } $this->setTemplate('edit'); } publicfunction executeDelete($request) { $this->forward404Unless($autor = AutorPeer::retrieveByPk($request>getParameter('id'))); $autor->delete(); $this->redirect('autor/index'); } }

En este mdulo, el flujo de trabajo del formulario est definido por los mtodos create, edit y update. Tambin es posible indicar a la tarea propel:generate-crud que genere slo un mtodo que incluya las tres funcionalidades anteriores, mediante la opcin --nonatomic-actions:
$ ./symfony propel:generate-crud frontend autor Autor --non-atomicactions

El cdigo generado mediante la opcin --non-atomic-actions (ver listado 4-5) es mucho ms conciso. Listado 4-5 - La clase autorActions generada con la opcin --non-atomic-actions
class autorActions extends sfActions { publicfunction executeIndex() { $this->autorList = AutorPeer::doSelect(new Criteria()); } publicfunction executeEdit($request) { $this->form = new AutorForm(AutorPeer::retrieveByPk($request>getParameter('id'))); if($request->isMethod('post')) { $this->form->bind($request->getParameter('autor')); if($this->form->isValid()) { $autor = $this->form->save(); $this->redirect('autor/edit?id='.$autor->getId()); } } } publicfunction executeDelete($request) { $this->forward404Unless($autor = AutorPeer::retrieveByPk($request>getParameter('id'))); $autor->delete(); $this->redirect('autor/index'); } }

Esta tarea tambin genera dos plantillas, indexSuccess y editSuccess. La plantilla editSuccess generada no utiliza la instruccin <?php echo $form ?>. Para modificar este comportamiento, se utiliza la opcin --non-verbose-templates:
$ ./symfony propel:generate-crud frontend autor Autor --non-verbosetemplates

Esta opcin es muy til mientras se construye el prototipo de la aplicacin, tal y como muestra el listado 4-6. Listado 4-6 - La plantilla editSuccess
// apps/frontend/modules/autor/templates/editSuccess.php

<?php$autor = $form->getObject()?> <h1><?php echo$autor->isNew() ? 'New' : 'Edit'?> Autor</h1> <form action="<?php echo url_for('autor/edit'.(!$autor->isNew() ? '?id='.$autor->getId() : '')) ?>" method="post"<?php$form->isMultipart() and print'enctype="multipart/form-data" ' ?>> <table> <tfoot> <tr> <td colspan="2"> &nbsp;<a href="<?php echo url_for('autor/index') ?>">Cancel</a> <?phpif(!$autor->isNew()): ?> &nbsp;<?php echo link_to('Delete', 'autor/delete?id='.$autor->getId(), array('post' =>true, 'confirm' =>'Are you sure?'))?> <?phpendif; ?> <input type="submit" value="Save" /> </td> </tr> </tfoot> <tbody> <?phpecho$form?> </tbody> </table> </form>

Sugerencia

La opcin --with-show permite generar una accin y una plantilla para visualizar objetos (slo en modo lectura). Si ahora se accede a la direccin /frontend_dev.php/autor, ya se puede utilizar el mdulo generado (figuras 4-1 y 4-2). Juega un poco con la interfaz y aade algo de informacin. El mdulo generado permite listar, aadir, modificar y borrar autores. Adems, puedes comprobar que las reglas de validacin tambin se aplican a los formularios.

Figura 4.1. Listado de autores

Figura 4.2. Modificando los datos de un autor con errores de validacin A continuacin se realiza la misma operacin sobre la clase Articulo:
$ ./symfony propel:generate-crud frontend articulo Articulo --nonverbose-templates --non-atomic-actions

El cdigo generado es muy parecido al cdigo de la clase Autor. Sin embargo, si se intenta crear un artculo nuevo, el cdigo lanza un error fatal que se muestra en la figura 4-3.

Figura 4.3. Las tablas relacionadas deben definir el mtodo __toString() El formulario ArticuloForm utiliza el widget sfWidgetFormPropelSelect para representar la relacin entre los objetos Articulo y Autor. Este widget crea una lista desplegable con todos los autores disponibles. Para mostrar los autores, los objetos de tipo Autor se convierten en una cadena de texto mediante el mtodo mgico __toString(), que debe estar definido en la clase Autor, como se muestra en el listado 4-7. Listado 4-7 - Aadiendo el mtodo __toString() en la clase Autor
class Autor extends BaseAutor { publicfunction __toString() { return$this->getNombre().' '.$this->getApellidos(); } }

Siguiendo el ejemplo de la clase Autor, tambin se pueden crear mtodos __toString() en las clases Articulo, Categoria y Etiqueta.
Sugerencia

La opcin method del widget sfWidgetFormPropelSelect permite establecer el nombre del mtodo que se utiliza para convertir el objeto en texto. La figura 4-4 muestra cmo crear un artculo despus de incluir el mtodo __toString() en la clase Autor.

Figura 4.4. Creando un artculo


Puedes enviarnos un comentario con sugerencias, crticas o para informarnos de algn error.

4.4. Personalizando los formularios generados automticamente


Las tareas propel:build-forms y propel:generate-crud permite crear mdulos de Symfony preparados para listar, crear, modificar y borrar objetos del modelo. Adems, estos modelos tienen en cuenta las reglas de validacin del modelo y tambin la relacin entre tablas. Lo mejor de todo es que el programador no tiene que escribir ni una sola lnea de cdigo. El siguiente paso consiste en personalizar el cdigo generado automticamente. Aunque las clases del formulario ya tienen en cuenta muchos elementos, normalmente es necesario personalizar algunos aspectos del formulario.

4.5. Configurando los validadores y los widgets


En esta seccin se muestra cmo configurar los validadores y los widgets generados por defecto.

El formulario ArticuloForm dispone de un campo llamado slug. Un slug es una cadena de caracteres que representa de forma nica al artculo dentro de la URL. Si se dispone por ejemplo de un artculo cuyo ttulo es "Optimizando la programacin con Symfony" y cuyo id es 12, su slug es 12-optimizando-la-programacion-con-symfony. Este campo se determina automticamente a partir del campo titulo cada vez que se guarda el objeto, pero tambin se permite al usuario establecerlo de forma explcita. Aunque este campo es obligatorio segn el esquema de datos, no puede ser un campo obligatorio en el formulario. Por este motivo se modifica el validador para hacer el campo opcional, como se muestra en el listado 4-8. Adems, se personaliza el campo contenido aumentando su tamao y obligando al usuario a escribir al menos cinco caracteres. Listado 4-8 - Personalizando los validadores y los widgets
class ArticuloForm extends BaseArticuloForm { publicfunction configure() { // ... $this->validatorSchema['slug']->setOption('required', false); $this->validatorSchema['contenido']->setOption('min_length', 5); $this->widgetSchema['contenido']->setAttributes(array('rows' =>10, 'cols' =>40)); } }

En el cdigo anterior se utilizan los objetos validatorSchema y widgetSchema como si fueran arrays de PHP. Estos arrays aceptan el nombre del campo como clave y devuelven respectivamente el objeto del validador y el objeto del widget. Por lo tanto, es posible personalizar cada campo y cada widget.
Nota

Para poder acceder a los objetos como si fueran arrays de PHP, las clases sfValidatorSchema y sfWidgetFormSchema implementan la interfaz ArrayAccess, disponible desde la versin PHP 5. Para asegurar que dos artculos no tengan el mismo slug, la definicin del esquema incluye la restriccin para hacer el slug nico. Esta restriccin a nivel de base de datos, se refleja en el formulario ArticuloForm a travs del validador sfValidatorPropelUnique. Este validador se puede emplear para asegurar que el valor de un campo sea nico en la base de datos, lo que puede ser til para campos como el login o el email. El listado 4-9 muestra cmo utilizarlo en el formulario ArticuloForm. Listado 4-9 - Utilizando el validador sfValidatorPropelUnique para asegurar que el valor de un campo sea nico
class BaseArticuloForm extends BaseFormPropel

{ publicfunction setup() { // ... $this->validatorSchema->setPostValidator( new sfValidatorPropelUnique(array('model' =>'Articulo', 'column' =>array('slug'))) ); } }

El validador sfValidatorPropelUnique se establece como post-validador para que se ejecute despus de comprobar que toda la informacin enviada por el usuario es vlida. Para validar que el campo slug sea nico, el validador no slo tiene que accceder a su valor, sino que tambin debe acceder al valor de la clave o claves primarias. Adems, las reglas de validacin son diferentes en la creacin y en la modificacin del objeto, ya que el slug puede permanecer invariante durante la modificacin de un artculo. A continuacin se modifica el campo activo de la tabla autor, que se utiliza para determinar si un usuario es activo o no. El listado 4-10 muestra cmo se pueden excluir los autores inactivos en el formulario ArticuloForm modificando la opcin criteria del widget sfWidgetPropelSelect asociado al campo autor_id. La opcin criteria toma como valor un objeto de tipo Criteria de Propel, lo que permite restringir el nmero de elementos de la lista desplegable. Listado 4-10 - Personalizando el widget sfWidgetPropelSelect
class ArticuloForm extends BaseArticuloForm { publicfunction configure() { // ... $autorCriteria = new Criteria(); $autorCriteria->add(AutorPeer::ACTIVO, true); $this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria); } }

El cdigo anterior slo modifica la lista de opciones disponibles en el widget, pero tambin se deben restringir las opciones en el validador, tal y como se muestra en el listado 4-11. Al igual que el widget sfWidgetProperSelect, el validador sfValidatorPropelChoice acepta una opcin llamada criteria para restringir las opciones vlidas para este campo. Listado 4-11 - Personalizando el validador sfValidatorPropelChoice
class ArticuloForm extends BaseArticuloForm { publicfunction configure()

{ // ... $autorCriteria = new Criteria(); $autorCriteria->add(AutorPeer::ACTIVO, true); $this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria); $this->validatorSchema['autor_id']->setOption('criteria', $autorCriteria); } }

En el ejemplo anterior se define el objeto Criteria directamente en el mtodo configure(). En un proyecto real, este objeto Criteria puede ser muy til en otras partes del cdigo, por lo que es mejor crear un mtodo llamado getCriteriaAutoresActivos() dentro de la clase AutorPeer y llamar a este mtodo desde ArticuloForm como muestra el listado 4-12. Listado 4-12 - Refactorizando el objeto Criteria en la clase del modelo
class AutorPeer extends BaseAutorPeer { staticpublicfunction getCriteriaAutoresActivos() { $criteria = new Criteria(); $criteria->add(AutorPeer::ACTIVO, true); return$criteria; } } class ArticuloForm extends BaseArticuloForm { publicfunction configure() { $autorCriteria = AutorPeer::getCriteriaAutoresActivos(); $this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria); $this->validatorSchema['autor_id']->setOption('criteria', $autorCriteria); } }

Sugerencia

Mientras que el widget sfWidgetPropelSelect y el validador sfValidatorPropelChoice representan una relacin 1-n entre dos tablas, el widget sfWidgetFormPropelSelectMany y el validador sfValidatorPropelChoiceMany representan una relacin de tipo n-n y aceptan las mismas opciones. En el formulario ArticuloForm, estas clases se emplean para representar la relacin entre las tablas articulo y etiqueta.

4.5.1. Modificando el validador

El campo email se define en el esquema como varchar(255), por lo que Symfony crea un validador de tipo sfValidatorString() para restringir su longitud a un mximo de 255 caracteres. Como este campo requiere que el valor introducido sea una direccin vlida de correo eletrnico, el listado 4-14 reemplaza el validador generado por un validador de tipo sfValidatorEmail. Listado 4-13 - Modificando el validador del campo email del formulario AutorForm
class AutorForm extends BaseAutorForm { publicfunction configure() { $this->validatorSchema['email'] = new sfValidatorEmail(); } }

4.5.2. Aadiendo un validador

En la seccin anterior se explica cmo modificar un validador generado automticamente. Pero en el caso del campo emaill, tambin es necesario mantener el validador que limita el mximo nmero de caracteres. El listado 4-14 utiliza el validador sfValidatorAnd para garantizar que la direccin de email sea vlida y que su longitud sea igual o inferior al mximo establecido. Listado 4-14 - Utilizando un validador mltiple
class AutorForm extends BaseAutorForm { publicfunction configure() { $this->validatorSchema['email'] = new sfValidatorAnd(array( new sfValidatorString(array('max_length' =>255)), new sfValidatorEmail(), )); } }

El ejemplo anterior tiene un inconveniente, ya que si se modifica el tamao del campo email en el esquema de la base de datos, es preciso acordarse de que tambin hay que modificarlo en el formulario. Por lo tanto, en vez de reemplazar el validador generado automticamente, es mejor aadir otro validador como se muestra en el listado 4-15. Listado 4-15 - Aadiendo un validador
class AutorForm extends BaseAutorForm { publicfunction configure() {

$this->validatorSchema['email'] = new sfValidatorAnd(array( $this->validatorSchema['email'], new sfValidatorEmail(), )); } }

4.5.3. Modificando el widget

El campo estado de la tabla articulo se define en el esquema como una cadena de caracteres. Sus posibles valores se definen en la clase ArticuloPeer, como se muestra en el listado 4-16. Listado 4-16 - Definiendo los posibles valores del estado en la clase ArticuloPeer
class ArticuloPeer extends BaseArticuloPeer { static protected $estados = array('borrador', 'online', 'offline'); staticpublicfunction getEstados() { return self::$estados; } // ... }

Cuando se modifica un artculo, el campo estado se debe representar como una lista desplegable en vez de como un cuadro de texto. Para ello, se modifica el widget de la forma que se muestra en el listado 4-17. Listado 4-17 - Modificando el widget del campo estado
class ArticuloForm extends BaseArticuloForm { publicfunction configure() { $this->widgetSchema['estado'] = new sfWidgetFormSelect(array('choices' => ArticuloPeer::getEstados())); } }

Por ltimo, tambin es necesario modificar el validador para asegurar que el estado seleccionado se encuentre dentro de la lista de estados posibles (ver listado 4-18). Listado 4-18 - Modificando el validador del campo estado
class ArticuloForm extends BaseArticuloForm { publicfunction configure() { $estados = ArticuloPeer::getEstados();

$this->widgetSchema['estado'] = new sfWidgetFormSelect(array('choices' =>$estados)); $this->validatorSchema['estado'] = new sfValidatorChoice(array('choices' =>array_keys($estados))); } }

4.5.4. Eliminando un campo

La tabla articulo tiene dos columnas especiales llamadas created_at y updated_at, cuyos valores gestiona Propel de forma automtica. Por lo tanto, para evitar que el usuario modifique sus valores, es necesario eliminar estos dos campos del formulario, tal y como muestra el listado 4-19. Listado 4-19 - Eliminando un campo
class ArticuloForm extends BaseArticuloForm { publicfunction configure() { unset($this->validatorSchema['created_at']); unset($this->widgetSchema['created_at']); unset($this->validatorSchema['updated_at']); unset($this->widgetSchema['updated_at']); } }

Eliminar un campo consiste en eliminar su validador y su widget. El listado 4-20 muestra cmo borrar los dos de forma simultnea mediante una sola instruccin, accediendo al formulario como si fuera un array de PHP. Listado 4-20 - Accediendo al formulario como si fuera un array para eliminar un campo
class ArticuloForm extends BaseArticuloForm { publicfunction configure() { unset($this['created_at'], $this['updated_at']); } }

4.5.5. Resultado final

Los listados 4-21 y 4-22 muestran respectivamente las versiones finales de los formularios ArticuloForm y AutorForm despus de su personalizacin. Listado 4-21 - Formulario ArticuloForm

class ArticuloForm extends BaseArticuloForm { publicfunction configure() { $autorCriteria = AutorPeer::getCriteriaAutoresActivos(); // widgets $this->widgetSchema['contenido']->setAttributes(array('rows' =>10, 'cols' =>40)); $this->widgetSchema['estado'] = new sfWidgetFormSelect(array('choices' => ArticuloPeer::getEstados())); $this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria); // validators $this->validatorSchema['slug']->setOption('required', false); $this->validatorSchema['contenido']->setOption('min_length', 5); $this->validatorSchema['estado'] = new sfValidatorChoice(array('choices' =>array_keys(ArticuloPeer::getEstados()))); $this->validatorSchema['autor_id']->setOption('criteria', $autorCriteria); unset($this['created_at']); unset($this['updated_at']); } }

Listado 4-22 - Formulario AutorForm


class AutorForm extends BaseAutorForm { publicfunction configure() { $this->validatorSchema['email'] = new sfValidatorAnd(array( $this->validatorSchema['email'], new sfValidatorEmail(), )); } }

La tarea propel:build-forms genera de forma automtica la mayora de elementos de los formularios a partir de la informacin del modelo de datos. Este proceso automtico es muy til porque:
y

Simplifica el trabajo del programador, evitndole las tareas ms tediosas y repetitivas. De esta forma, el programador se centra en la configuracin de los widgets y validadores para que sigan la lgica de la aplicacin. Adems, cada vez que se actualiza el esquema de la base de datos, tambin se actualizan los formularios generados de forma automtica. El nico trabajo del programador consiste en ajustar la configuracin realizada.

La siguiente seccin describe la personalizacin y configuracin de las acciones y plantillas generadas con la tarea propel:generate-crud.

4.6. Serializando formularios


La seccin anterior explica cmo personalizar los formularios generados automticamente con la tarea propel:build-forms. En esta seccin se personaliza el flujo de trabajo del formulario, empezando por el cdigo generado mediante la tarea propel:generate-crud.
4.6.1. Valores por defecto

Una instancia de un formulario Propel siempre est asociada a un objeto Propel. Este objeto asociado siempre es de la clase devuelta por el mtodo getModelName(). El formulario AutorForm por ejemplo slo puede estar asociado a objetos que sean de la clase Autor. Este objeto puede ser un objeto vaco (una nueva instancia de la clase Autor) o el objeto que se indica como primer argumento del constructor. A diferencia del constructor de un formulario normal, al que se le pasa como primer argumento un array de valores, al constructor de un formulario Propel se le pasa un objeto Propel. Este objeto se utiliza para determinar el valor por defecto de cada campo del formulario. El mtodo getObject() devuelve el objeto relacionado con la actual instancia del formulario y el mtodo isNew() permite descubrir si el objeto se ha obtenido a travs del constructor:
// Crear un nuevo objeto $autorForm = new AutorForm(); print$autorForm->getObject()->getId(); // Muestra null print$autorForm->isNew(); // Muestra true // Modificar un objeto existente $autor = AutorPeer::retrieveByPk(1); $autorForm = new AutorForm($autor); print$autorForm->getObject()->getId(); // Muestra 1 print$autorForm->isNew(); // Muestra false

4.6.2. Modificar el flujo de trabajo

Como se explica al principio de este captulo, la accin edit mostrada en el listado 4-23 gestiona el flujo de trabajo del formulario. Listado 4-23 - El mtodo executeEdit del mdulo autor
// apps/frontend/modules/autor/actions/actions.class.php class autorActions extends sfActions { // ... publicfunction executeEdit($request) { $autor = AutorPeer::retrieveByPk($request->getParameter('id')); $this->form = new AutorForm($autor); if($request->isMethod('post'))

{ $this->form->bind($request->getParameter('autor')); if($this->form->isValid()) { $autor = $this->form->save(); $this->redirect('autor/edit?id='.$autor->getId()); } } } }

Aunque la accin edit se parece a las acciones mostradas en los captulos anteriores, en realidad presenta diferencias importantes:
y

El primer argumento del constructor del formulario es un objeto Propel de la clase Autor:

$autor = AutorPeer::retrieveByPk($request->getParameter('id')); $this->form = new AutorForm($autor); y

El formato del atributo name de los widgets se modifica automticamente para que sea posible obtener la informacin del usuario en un array PHP que se llama igual que la tabla relacionada (autor en este caso):

$this->form->bind($request->getParameter('autor')); y

Si el formulario es vlido, slo es necesario ejecutar el mtodo save() para crear o actualizar el objeto Propel relacionado con el formulario:

$autor = $this->form->save();

4.6.3. Creando y modificando objetos Propel

El listado 4-23 utiliza un solo mtodo para crear y modificar objetos de la clase Autor:
y

Crear un nuevo objeto de tipo Autor: o Se ejecuta la accin index sin un parmetro id ($request>getParameter('id') es null) o La llamada al mtodo retrieveByPk() devuelve null o El objeto form se asocia con un objeto Propel vaco de tipo Autor. o La instruccin $this->form->save() crea por tanto un nuevo objeto de tipo Autor siempre que los datos del formulario sean vlidos. Modificar un objeto de tipo Autor: o Se ejecuta la accin index con un parmetro id obtenido mediante la instruccin $request->getParameter('id') y que representa la clave primaria del objeto Autor que se va a modificar. o La llamada al mtodo retriveByPk() devuelve el objeto de tipo Autor relacionado con la clave primaria indicada.

o o

El objeto form se asocia al objeto de tipo Autor que se ha obtenido de la base de datos. La instruccin $this->form->save() actualiza los datos del objeto Autor siempre que los datos del formulario sean vlidos.

4.6.4. El mtodo save()

Cuando los datos de un formulario Propel son vlidos, el mtodo save() actualiza el objeto asociado y lo almacena en la base de datos. Este mtodo no slo guarda el objeto principal, sino que tambin guarda todos los objetos relacionados. El formulario ArticuloForm por ejemplo actualiza las etiquetas relacionadas con el artculo. La relacin entre las tablas articulo y etiqueta es de tipo n-n y las etiquetas relacionadas con un artculo se guardan en la tabla articulo_etiqueta utilizando el mtodo saveArticuloEtiquetaList(). Para asegurar que los datos guardados son consistentes, el mtodo save() realiza todas las actualizaciones en una transaccin.
Nota

En el captulo 9 se explica que el mtodo save() tambin actualiza de forma automtica todas las tablas internacionalizadas. Empleando el mtodo bindAndSave() El mtodo bindAndSave() asocia al formulario los datos enviados por el usuario, valida el formulario y actualiza todos los objetos relacionados, todo ello en una nica operacin:
class articuloActions extends sfActions { publicfunction executeCreate(sfWebRequest $request) { $this->form = new ArticuloForm(); if($request->isMethod('post')&&$this->form->bindAndSave($request>getParameter('articulo'))) { $this->redirect('articulo/created'); } } }

4.6.5. Trabajando con archivos subidos

El mtodo save() actualiza de forma automtica los objetos Propel, pero no puede encargarse de otras cosas importantes como la gestin de los archivos subidos. A continuacin se muestra cmo adjuntar un archivo a cada artculo. Los archivos se almacenan en el directorio web/uploads/ y en el campo archivo de la tabla articulo de la base de datos se guarda la ruta hasta el archivo, tal y como muestra el listado 4-24.

Listado 4-24 - Esquema de la tabla articulo con archivos adjuntos


// config/schema.yml propel: articulo: // ... archivo: varchar(255)

Despus de actualizar el esquema, es necesario actualizar el modelo de objetos, la base de datos y todos los formularios relacionados:
$ ./symfony propel:build-all

Cuidado

Ten en cuenta que la tarea propel:build-all borra todas las tablas del esquema y las vuelve a crear. Por lo tanto, se borran todos los datos de todas las tablas. Este es el motivo por el que se crean archivos con datos de prueba (llamados fixtures) y que se pueden cargar en la base de datos cada vez que se modifica el modelo. El listado 4-25 muestra cmo modificar la clase ArticuloForm para asociar un widget y un validador al campo archivo. Listado 4-25 - Modificando el campo archivo del formulario ArticuloForm
class ArticuloForm extends BaseArticuloForm { publicfunction configure() { // ... $this->widgetSchema['archivo'] = new sfWidgetFormInputFile(); $this->validatorSchema['archivo'] = new sfValidatorFile(); } }

Como sucede en todos los formularios que permite subir archivos, no olvides aadir el atributo enctype a la etiqueta <form> de la plantilla (en el captulo 2 se explica detalladamente el trabajo con los archivos subidos). El listado 4-26 muestra las modificaciones necesarias en la accin que guarda los datos del formulario para guardar el archivo en el servidor y para guardar la ruta hasta el archivo en el objeto articulo. Listado 4-26 - Guardando el objeto articulo y el archivo subido
publicfunction executeEdit($request) { $autor = ArticuloPeer::retrieveByPk($request->getParameter('id')); $this->form = new ArticuloForm($autor);

if($request->isMethod('post')) { $this->form->bind($request->getParameter('articulo'), $request>getFiles('articulo')); if($this->form->isValid()) { $archivo = $this->form->getValue('archivo'); $nombreArchivo = sha1($archivo->getOriginalName()).$archivo>getExtension($archivo->getOriginalExtension()); $archivo->save(sfConfig::get('sf_upload_dir').'/'.$nombreArchivo); $articulo = $this->form->save(); $this->redirect('articulo/edit?id='.$articulo->getId()); } } }

Despus de guardar el archivo subido en el servidor, el objeto sfValidatedFile ya conoce la ruta hasta el archivo. En el mtodo save(), se utilizan los valores de cada campo para actualizar el objeto asociado. Adems, el objeto sfValidatedFile se convierte en una cadena de texto utilizando el mtodo __toString(), que devuelve la ruta completa hasta el archivo. La columna archivo de la tabla articulo almacena esta ruta completa.
Sugerencia

Si se quiere almacenar la ruta del archivo de forma relativa respecto al directorio sfConfig::get('sf_upload_dir'), se puede crear una clase que herede de sfValidatedFile y se puede utilizar la opcin validated_file_class para indicar al validador sfValidatorFile el nombre de la nueva clase. De esta forma, el validador devuelve una instancia de esa nueva clase. En lo que queda de captulo se muestra otra forma de conseguirlo, que consiste en modificar el valor de la columna archivo antes de guardar el objeto en la base de datos.
4.6.6. Personalizando el mtodo save()

En la seccin anterior se explica cmo guardar un archivo subido en la accin edit. Por otra parte, uno de los principios de la programacin orientada a objetos es la reutilizacin del cdigo cuando se encapsula en clases. De esta forma, en vez de duplicar el cdigo necesario para guardar los archivos subidos en todas las acciones del formulario ArticuloForm, se guarda ese cdigo en la propia clase ArticuloForm. El listado 4-27 muestra cmo redefinir el mtodo save() para que tambin guarde el archivo subido y para que pueda borrar un archivo existente. Listado 4-27 - Redefiniendo el mtodo save() de la clase ArticuloForm
class ArticuloForm extends BaseFormPropel { // ...

publicfunction save($con = null) { if(file_exists($this->getObject()->getArchivo())) { unlink($this->getObject()->getArchivo()); } $archivo = $this->getValue('archivo'); $nombreArchivo = sha1($archivo->getOriginalName()).$archivo>getExtension($archivo->getOriginalExtension()); $archivo->save(sfConfig::get('sf_upload_dir').'/'.$nombreArchivo); return parent::save($con); } }

Despus de incluir todo el cdigo en el propio formulario, la nueva accin edit es idntica a la que genera automticamente la tarea propel:generate-crud. Refactorizando el cdigo en el modelo del formulario Normalmente, las acciones generadas de forma automtica por la tarea propel:generatecrud no se modifican. Todo el cdigo que puede ser necesario aadir en la accin edit, especialmente el cdigo relacionado con la serializacin del formulario, debe ser incluido realmente en las clases del modelo o en las clases del formulario. En los ejemplos anteriores se muestra cmo refactorizar la clase del formulario para tener en cuenta los archivos subidos. A continuacin se muestra otro ejemplo relacionado con el modelo. El formulario ArticuloForm dispone de un campo llamado slug. Como se explic anteriormente, este campo se genera automticamente a partir del valor del campo titulo, que puede ser modificado por el usuario. Esta lgica no depende del formulario sino que realmente est relacionada con el modelo, como se muestra en el siguiente cdigo:
class Articulo extends BaseArticulo { publicfunction save($con = null) { if(!$this->getSlug()) { $this->setSlugFromTitulo(); } return parent::save($con); } protection function setSlugFromTitulo() { // ... } }

El objetivo final de estas refactorizaciones es la separacin de la aplicacin en diferentes capas y la posibilidad de reutilizar trozos de cdigo.
4.6.7. Personalizando el mtodo doSave()

Como se ha comentado en las secciones anteriores, la actualizacin de los objetos se realiza mediante transacciones que aseguran que todas las operaciones relacionadas con esa actualizacin se realizan correctamente. Cuando se redefine el mtodo save() (como por ejemplo en la seccin anterior para guardar los archivos subidos) el cdigo que se ejecuta no se incluye en esta transaccin. El listado 4-28 muestra cmo utilizar el mtodo doSave() para incluir en la transaccin global el cdigo propio que guarda los archivos subidos. Listado 4-28 - Redefiniendo el mtodo doSave() en el formulario ArticuloForm
class ArticuloForm extends BaseFormPropel { // ... publicfunction doSave($con = null) { if(file_exists($this->getObject()->getArchivo())) { unlink($this->getObject()->getArchivo()); } $archivo = $this->getValue('archivo'); $nombreArchivo = sha1($archivo->getOriginalName()).$archivo>getExtension($archivo->getOriginalExtension()); $archivo->save(sfConfig::get('sf_upload_dir').'/'.$nombreArchivo); return parent::doSave($con); } }

De esta forma, si el mtodo save() del objeto archivo produce una excepcin, el objeto articulo tampoco se guarda porque el cdigo propio se ha incluido dentro del mtodo doSave() que realiza la transaccin.
4.6.8. Personalizando el mtodo ''updateObject()''

En ocasiones es necesario modificar el objeto asociado al formulario entre la accin de actualizar sus datos y la accin de guardarlo en la base de datos. En el ejemplo anterior de los archivos subidos, en vez de guardar en la columna archivo la ruta completa hasta el archivo subido, slo se guarda la ruta relativa respecto al directorio sfConfig::get('sf_upload_dir').

El listado 4-29 muestra cmo redefinir el mtodo updateObject() del formulario ArticuloForm para modificar el valor de la columna archivo despus de actualizar automticamente el objeto pero antes de guardarlo en la base de datos. Listado 4-29 - Redefiniendo el mtodo updateObject() y la clase ArticuloForm
class ArticuloForm extends BaseFormPropel { // ... publicfunction updateObject() { $objeto = parent::updateObject(); $objeto->setArchivo(str_replace(sfConfig::get('sf_upload_dir').'/', '', $objeto->getArchivo())); return$objeto; } }

El mtodo updateObject() se invoca desde el mtodo doSave() antes de guardar el objeto en la base de datos.

Captulo 8. Internacionalizacin y localizacin


Muchas aplicaciones web populares est disponibles en varios idiomas e incluso algunas de ellas modifican sus caractersticas en funcin de la cultura del usuario. Symfony incluye utilidades para facilitar la gestin de estas caractersticas (como se explica en el captulo 13 del libro oficial de Symfony 1.1). La parte de los formularios tambin incluye soporte para traducir la interfaz de usuario y permite gestionar de forma sencilla los objetos internacionalizados.

8.1. Internacionalizacin de formularios


Los formularios de Symfony son internacionalizables por defecto. La traduccin de los ttulos o labels, los mensajes de ayuda y los mensajes de error se realiza mediante la traduccin de los ficheros XLIFF, gettext o cualquier otro formato soportado por Symfony. El listado 8-1 muestra el formulario de contacto desarrollado en los captulos anteriores.

Listado 8-1 - Formularios de contacto


class FormularioContacto extends sfForm { publicfunction configure() { $this->setWidgets(array( 'nombre' =>new sfWidgetFormInput(), // el ttulo o label por defecto es "Nombre" 'email' =>new sfWidgetFormInput(), // el ttulo o label por defecto es "Email" 'mensaje' =>new sfWidgetFormTextarea(), // el ttulo o label por defecto es "Mensaje" )); // Modificar el ttulo del widget del campo email $this->widgetSchema->setLabel('email', 'Direccin de email'); } }

El listado 8-2 muestra cmo traducir los ttulos anteriores al francs utilizando un archivo en formato XLIFF. Listado 8-2 - Archivo de traduccin en formato XLIFF
// apps/frontend/i18n/messages.fr.xml <?xmlversion="1.0"?> <xliffversion="1.0"> <fileorginal="global"source-language="en"datatype="plaintext"> <body> <trans-unit> <source>Name</source> <target>Nom</target> </trans-unit> <trans-unit> <source>Email address</source> <target>Adresse email</target> </trans-unit> <trans-unit> <source>Body</source> <target>Message</target> </trans-unit> </body> </file> </xliff>

8.1.1. Indicar el catlogo utilizado para la traduccin

Si utilizas la opcin de los catlogos de la parte de internacionalizacin de Symfony, puedes asociar un formulario con un catlogo existente. El listado 8-3 asocia el fomulario FormularioContacto con el catlogo formulario_contacto. Por lo tanto, las traducciones de los elementos de ese formulario se buscarn en el archivo formulario_contacto.fr.xml.

Listado 8-3 - Estableciendo el catlogo utilizado para la traduccin


class FormularioContacto extends sfForm { publicfunction configure() { // ... $this->widgetSchema->getFormFormatter()>setTranslationCatalogue('formulario_contacto'); } }

Nota

Utilizar catlogos permite organizar mejor las traducciones, ya que por ejemplo se puede utilizar un archivo para cada formulario.
8.1.2. Internacionalizacin de los mensajes de error

En ocasiones, los mensajes de error incluyen el valor enviado por el usuario, como por ejemplo: "La direccin de email usuario@dominio no es vlida.". El el captulo 2 se explic lo fcil que es hacerlo definiendo mensajes de error personalizados en la clase del formulario y utilizando referencias a los valores enviados por el usuario. Las referencias siguen el patron %nombre_parametro%. El listado 8-4 muestra cmo aplicar este principio al campo nombre del formulario de contacto. Listado 8-4 - Internacionalizacin de los mensajes de error
class FormularioContacto extends sfForm { publicfunction configure() { // ... $this->validatorSchema['nombre'] = new sfValidatorEmail( array('min_length' =>2, 'max_length' =>45), array('min_length' =>'El nombre "%value%" debe tener al menos %min_length% caracteres.', 'max_length' =>'El nombre "%value%" no puede tener ms de %max_length% caracteres.', ), ); } }

Para traducir los mensajes de error anteriores, slo es necesario editar el archivo en formato XLIFF como muestra el listado 8-5. Listado 8-5 - Archivo de traduccin en formato XLIFF para los mensajes de error

<trans-unit> <source>El nombre "%value%" debe tener al menos %min_length% caracteres</source> <target>Le nom "%value%" doit comporter un minimum de %min_length% caractres</target> </trans-unit> <trans-unit> <source>El nombre "%value%" no puede tener ms de %max_length% caracteres</source> <target>Le nom "%value%" ne peut comporter plus de %max_length% caractres</target> </trans-unit>

8.2. Personalizando el objeto traductor


Si quieres utilizar los formularios de Symfony sin la parte de internacionalizacin de Symfony, debes crear un objeto traductor propio. Un objeto traductor es simplemente un elemento PHP que se puede ejecutar (un PHP callable), por lo que puede ser cualquiera de estos tres elementos:
y y

Una cadena de texto que representa el nombre de una funcin, como por ejemplo
mi_funcion

Un array con el nombre de la instancia de una clase (es decir, el nombre de un objeto) y el nombre de uno de sus mtodos, como por ejemplo array($unObjeto,
'nombreDeUnoDeSusMetodos')

Una instancia de sfCallable. Esta clase se utiliza para encapsular de forma consistente un elemento ejecutable de PHP.

Nota

Un elemento ejecutable de PHP (tambin llamado PHP callable) es una referencia a una funcin o al mtodo de un objeto. Tambin puede ser una variable PHP que devuelve true cuando se pasa a la funcin is_callable(). En el siguiente ejemplo se considera que se est migrando un proyecto que ya dispone de su propio mecanismo de internacionalizacin a travs de la clase que se muestra en el listado 8-6. Listado 8-6 - Clase propia para la internacionalizacin
class miI18n { static protected $cultura_defecto = 'en'; static protected $mensajes = array('fr' =>array( 'Nombre' =>'Nom', 'Email' =>'Courrier lectronique', 'Asunto' =>'Sujet', 'Mensaje' =>'Message', ));

staticpublicfunction traducirTexto($texto) { $cultura = isset($_SESSION['cultura']) ? $_SESSION['cultura'] : self::$cultura_defecto; if(array_key_exists($cultura, self::$mensajes) &&array_key_exists($texto, self::$mensajes[$cultura])) { return self::$mensajes[$_SESSION['cultura']][$texto]; } return$texto; } } // Ejemplos de uso de la clase anterior $miI18n = new miI18n(); $_SESSION['cultura'] = 'es'; echo$miI18n->translateText('Asunto'); // => muestra "Asunto" $_SESSION['cultura'] = 'fr'; echo$miI18n->translateText('Asunto'); // => muestra "Sujet"

Como se muestra en el listado 8-7, cada formulario puede definir el elemento PHP que se encarga de traducir los elementos del formulario. Listado 8-7 - Redefiniendo el mtodo de internacionalizacin de un formulario
class FormularioContacto extends sfForm { publicfunction configure() { // ... $this->widgetSchema->getFormFormatter()->setTranslationCallable(array(new miI18n(), 'traducirTexto')); } }

8.2.1. Parmetros del objeto traductor

El objeto traductor puede recibir hasta tres parmetros:


y y

El texto que debe traducir. Un array asociativo con los elementos que se deben sustituir en el texto original, normalmente utilizados para reemplazar argumentos dinmicos como se ha visto previamente en este captulo. El nombre del catlogo que se debe utilizar para traducir el texto.

A continuacin se muestra la llamada que realiza el mtodo sfFormWidgetSchemaFormatter::translate() para ejecutar el objeto traductor:
returncall_user_func(self::$translationCallable, $texto, $parametros, $catalogo);

El elemento self::$translationCallable es una referencia al objeto traductor. Por lo tanto, el cdigo anterior es equivalente al siguiente:
$miI18n->traducirTexto($texto, $parametros, $catalogo);

Seguidamente se muestra la versin actualizada de la clase MiI18n que soporta estos argumentos adicionales:
class miI18n { static protected $cultura_defecto = 'en'; static protected $mensajes = array('fr' =>array( 'mensajes' =>array( 'Nombre' =>'Nom', 'Email' =>'Courrier lectronique', 'Asunto' =>'Sujet', 'Mensaje' =>'Message', ), )); staticpublicfunction traducirTexto($texto, $parametros = array(), $catalogo = 'mensajes') { $cultura = isset($_SESSION['cultura']) ? $_SESSION['cultura'] : self::$cultura_defecto; if(array_key_exists($cultura, self::$mensajes)&& array_key_exists($mensajes, self::$mensajes[$cultura]&& array_key_exists($texto, self::$mensajes[$cultura][$mensajes])) { $texto = self::$mensajes[$_SESSION['cultura']][$mensajes][$texto]; $texto = strtr($texto, $parametros); } return$texto; } }

Por qu se utiliza sfWidgetFormSchemaFormatter para personalizar el mecanismo de traduccin? Como se muestra en el captulo 2, el mecanismo de formularios sigue la arquitectura MVC y la clase sfWidgetFormSchemaFormatter corresponde a la capa de la vista. Esta clase se encarga de mostrar todo el texto, por lo que tiene acceso a todas las cadenas de texto y puede traducirlas de forma dinmica.

8.3. Internacionalizacin de los objetos Propel


Los formularios de Symfony incluyen el soporte de los objetos Propel internacionalizados. Para mostrar su funcionamiento se utiliza el siguiente modelo de datos de ejemplo con soporte de internacionalizacin:
propel:

articulo: id: autor: varchar(255) created_at: articulo_i18n: titulo: varchar(255) contenido: longvarchar

Utilizando los siguientes comandos se pueden generar las clases Propel y las clases del formulario:
$ php symfony build:model $ php symfony build:forms

Los comandos anteriores generan varios archivos en el proyecto de Symfony:


lib/ form/ ArticuloForm.class.php ArticuloI18nForm.class.php BaseFormPropel.class.php model/ Articulo.php ArticuloPeer.php ArticuloI18n.php ArticuloI18nPeer.php

El listado 8-8 muestra cmo configurar el formulario ArticuloForm para poder editar en un mismo formulario las versiones en ingls y francs de un artculo. Listado 8-8 - Formularios I18n de un objeto Propel internacionalizado
class ArticuloForm extends BaseArticuloForm { publicfunction configure() { $this->embedI18n(array('en', 'fr')); } }

Tambin es posible personalizar el nombre de los idiomas del formulario aadiendo en el mtodo configure() el cdigo mostrado en el listado 8-9. Listado 8-9 - Personalizando el nombre de los idiomas
$this->widgetSchema->setLabel('en', 'Ingls'); $this->widgetSchema->setLabel('fr', 'Francs');

Figura 8-1 - Formulario Propel internacionalizado

Figura 8.1. Formulario Propel internacionalizado Y eso es todo. Cuando se ejecuta el mtodo save() del objeto del formulario, se guardan de forma automtica el objeto Propel asociado y todos los objetos de tipo i18n. Cmo se pasa la cultura del usuario al formulario? Si se quiere asociar un formulario con la cultura del usuario, se puede utilizar una opcin llamada culture cuando se crea el formulario:
class articuloActions extends sfActions { publicfunction executeCrear($peticion) { $this->form = new ArticuloForm(null, array('culture' =>$this->getUser()>getCulture())); if($peticion->isMethod('post')&&$this->form->bindAndSave($peticion>getParameter('articulo'))) { $this->redirect('articulo/creado'); } } }

En la clase ArticuloForm se puede obtener el valor de la cultura mediante el array options:


class ArticuloForm extends BaseArticuloForm { publicfunction configure() { $this->embedI18n(array($this->getCurrentCulture())); } publicfunction getCurrentCulture() {

returnisset($this->options['culture']) ? $this->options['culture'] : 'en'; } }

8.4. Traduccin de Widgets


Los formularios de Symfony incluyen algunos widgets preparados para su internacionalizacin. De esta forma se pueden adaptar a la cultura establecida por el usuario.
8.4.1. Selectores de fechas

Los widgets disponibles para traducir y adaptar las fechas son los siguientes:
y

El widget sfWidgetFormI18nDate permite introducir fechas (da, mes y ao):

$this->widgetSchema['publicado_en'] = new sfWidgetFormI18nDate(array('culture' =>'fr')); y

Tambin se puede modificar el formato del mes, gracias a la opcin month_format que permite elegir entre tres formatos: o name, muestra el nombre del mes (este es el formato por defecto) o short_name, muestra la abreviatura del nombre del mes o number, muestra el nmero del mes (desde el 1 hasta el 12) El widget sfWidgetFormI18nTime permite introducir valores de tiempo (horas, minutos y segundos):

$this->widgetSchema['publicado_en'] = new sfWidgetFormI18nTime(array('culture' =>'fr')); y

El widget sfWidgetFormI18nDateTime permite introducir fechas y horas:

$this->widgetSchema['publicado_en'] = new sfWidgetFormI18nDateTime(array('culture' =>'fr'));

8.4.2. Selector de pas

El widget sfWidgetFormI18nSelectCountry muestra una lista desplegable con el nombre de todos los pases. El nombre de los pases se muestra en el idioma correspondiente a la cultura del usuario:
$this->widgetSchema['pais'] = new sfWidgetFormI18nSelectCountry(array('culture' =>'fr'));

Si no se quiere mostrar la lista completa de todos los pases del mundo, se pueden restringir sus elementos con la opcin countries:
$paises = array('fr', 'en', 'es', 'de', 'nl'); $this->widgetSchema['pais'] = new sfWidgetFormI18nSelectCountry(array('culture' 'countries' =>$paises));

=>'fr',

8.4.3. Selector de idioma

El widget sfWidgetFormI18nSelectLanguage muestra una lista desplegable con el nombre de varios idiomas del mundo. El nombre de cada idioma se muestra en el idioma correspondiente a la cultura del usuario:
$this->widgetSchema['idioma'] = new sfWidgetFormI18nSelectLanguage(array('culture' =>'fr'));

Si no se quiere mostrar la lista completa de todos los idiomas del mundo, se pueden restringir sus elementos con la opcin languages:
$idiomas = array('fr', 'en', 'es', 'de', 'nl'); $this->widgetSchema['idioma'] = new sfWidgetFormI18nSelectLanguage(array('culture' 'languages' =>$idiomas));

=>'fr',

Captulo 11. Integrando Doctrine


En los proyectos web, la mayora de formularios se utilizan para crear y modificar los objetos del modelo. Normalmente, el contenido de los objetos se guarda en una base de datos mediante un ORM. Los formularios de Symfony incluyen una capa adicional para interactuar con Doctrine, uno de los dos ORM incluidos en Symfony, de forma que se simplifica la creacin de formularios que utilizan estos objetos del modelo. En este captulo se detalla cmo integrar los formularios de Symfony con los modelos que utilizan objetos de Doctrine. Por este motivo, para leer este captulo es imprescindible disponer de conocimientos previos de Doctrine y de su integracin con Symfony. Si no es tu caso, puedes leer el captulo 8 (El Modelo) del libro oficial de Symfony 1.1.

11.1. Antes de comenzar


A lo largo de este captulo se crea un gestor de artculos como ejemplo de uso de Doctrine y los formularios de Symfony. En primer lugar se define el esquema de la base de datos, compuesto por las cinco tablas mostradas en el listado 11-1: article, author, category, tag y article_tag. Listado 11-1 - Esquema de la base de datos

// config/doctrine/schema.yml Article: actAs: [Sluggable, Timestampable] columns: title: type: string(255) notnull: true content: type: clob status: string(255) author_id: integer category_id: integer published_at: timestamp relations: Author: foreignAlias: Articles Category: foreignAlias: Articles Tags: class: Tag refClass: ArticleTag foreignAlias: Articles Author: columns: first_name: string(20) last_name: string(20) email: string(255) active: boolean Category: columns: name: string(255) Tag: columns: name: string(255) ArticleTag: columns: article_id: type: integer primary: true tag_id: type: integer primary: true relations: Article: onDelete: CASCADE Tag: onDelete: CASCADE

A continuacin se muestra la relacin entre las tablas:


y y y

Relacin 1-n entre las tablas article y author, ya que cada artculo est escrito por uno y slo un autor. Relacin 1-n entre las tablas article y category, ya que cada artculo pertenece a una o ninguna categora. Relacin n-n entre las tablas article y tag.

11.2. Generando las clases del formulario


El gestor de artculos permite modificar la informacin de las tablas article, author, category y tag. Para ello, es preciso crear formularios asociados a cada tabla y configurar los widgets y validadores necesarios para cumplir las restricciones del esquema de la base de datos. Aunque los formularios se pueden crear a mano, se trata de una tarea repetitiva y tediosa que requiere repetir la misma informacin una y otra vez en diferentes archivos (el nombre de las columnas y los campos, el tamao mximo de las columnas y campos, etc.) Adems, cada vez que se modifica el modelo, es necesario modificar la clase del formulario asociado. Afortunadamente, el plugin de Doctrine incluye una tarea llamada doctrine:build-forms que automatiza todo este proceso y genera los formularios relacionados con el modelo de objetos:
$ ./symfony doctrine:build-forms frontend

La tarea de generacin automtica de formularios crea una clase para cada tabla y aade los validadores y widgets necesarios para cada columna mediante la introspeccin del modelo y teniendo en cuenta las relaciones entre tablas. Nota Las tareas doctrine:build-all y doctrine:build-all-load tambin actualizan las clases de los formularios porque invocan automticamente la tarea doctrine:buildforms. Despus de ejecutar estas tareas se crea una estructura de archivos en el directorio lib/form/. El siguiente listado muestra los archivos creados para el esquema utilizado en los ejemplos anteriores:
lib/ form/ doctrine/ BaseFormDoctrine.class.php ArticleForm.class.php ArticleTagForm.class.php AuthorForm.class.php CategoryForm.class.php TagForm.class.php base/ BaseArticleForm.class.php BaseArticleTagForm.class.php BaseAuthorForm.class.php BaseCategoryForm.class.php BaseFormDoctrine.class.php BaseTagForm.class.php

La tarea doctrine:build-forms genera dos clases para cada tabla del esquema, una clase base en el directorio lib/form/base y otra clase en el directorio lib/form/. Si se considera por ejemplo la tabla author, se crean las clases BaseAuthorForm y AuthorForm

que se guardan en los archivos lib/form/base/BaseAuthorForm.class.php y lib/form/AuthorForm.class.php. La siguiente tabla muestra la jerarqua de las diferentes clases relacionadas con el formulario AuthorForm. Clase
AuthorForm BaseAuthorForm

Paquete (package)
project project

Quin utiliza Descripcin la clase Programador Redefine el formulario generado Se basa en el esquema y se genera cada Symfony vez que se ejecuta la tarea
doctrine:build-forms

BaseFormDoctrine project sfFormDoctrine sfForm

Programador Symfony Symfony

Permite personalizar los formularios de Doctrine de forma global Base de los formularios de Doctrine Base de los formularios de Symfony

Plugin Doctrine
symfony

El listado 11-2 muestra el cdigo de la clase AuthorForm, utilizada para crear y modificar los objetos de la clase Author. Como se puede observar, esta clase no contiene ningn mtodo, ya que hereda de la clase BaseAuthorForm que se genera a partir de la configuracin. La clase AuthorForm se utiliza para personalizar y redefinir la configuracin del formulario. Listado 11-2 - La clase AuthorForm
class AuthorForm extends BaseAuthorForm { public function configure() { } }

El listado 11-3 muestra el cdigo de la clase BaseAuthorForm con los validadores y widgets generados automticamente mediante la instrospeccin del modelo de la tabla author. Listado 11-3 - Clase BaseAuthorForm que representa al formulario de la tabla author
class BaseAuthorForm extends BaseFormDoctrine { public function setup() { $this->setWidgets(array( 'id' => new sfWidgetFormInputHidden(), 'first_name' => new sfWidgetFormInput(), 'last_name' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(),

)); $this->setValidators(array( 'id' => new sfValidatorDoctrineChoice(array('model' 'Author', 'column' => 'id', 'required' => false)), 'first_name' => new sfValidatorString(array('max_length' => 'required' => false)), 'last_name' => new sfValidatorString(array('max_length' => 'required' => false)), 'email' => new sfValidatorString(array('max_length' => )); $this->widgetSchema->setNameFormat('author[%s]'); $this->errorSchema = new sfValidatorErrorSchema($this>validatorSchema); parent::setup(); } public function getModelName() { return 'Author'; } } => 20, 20, 255)),

La clase generada es muy similar a la de los formularios creados en los captulos anteriores, salvo por las siguientes excepciones:
y y y

La clase base es BaseFormDoctrine en vez de sfForm La configuracin de los validadores y widgets se realiza en el mtodo setup(), en vez de en el mtodo configure() El mtodo getModelName() devuelve la clase de Doctrine relacionada con el formulario

Personalizando de forma global los formularios de Doctrine Adems de las clases generadas para cada tabla, la tarea doctrine:build-forms tambin genera la clase BaseFormDoctrine. Esta clase est vaca inicialmente y es la clase base de todas las clases del directorio lib/form/base/, por lo que permite modificar globalmente el comportamiento de todos los formularios de Doctrine. El siguiente ejemplo muestra cmo modificar el formato de salida de todos los formularios de Doctrine:
abstract class BaseFormDoctrine extends sfFormDoctrine { public function setup() { sfWidgetFormSchema::setDefaultFormFormatterName('div'); } }

Si te fijas vers que la clase BaseFormDoctrine hereda a su vez de la clase sfFormDoctrine. Esta ltima clase incluye funcionalidades especficas de Doctrine y entre otras cosas, incluye todo el cdigo necesario para almacenar en la base de datos los objetos con los valores enviados a travs de los formularios. Sugerencia Las clases base utilizan el mtodo setup() para su configuracin en vez del mtodo configure(). De esta forma es posible redefinir la configuracin de las clases vacas generadas automticamente sin utilizar una llamada a parent::configure(). Los nombres de los campos del formulario son idnticos a los nombres de las columnas del esquema de datos: id, first_name, last_name y email. La tarea doctrine:build-forms genera un widget y un validador para cada columna de la tabla author cumpliendo las restricciones del esquema de datos. Esta tarea siempre genera los validadores ms seguros posible. Si se considera el campo id, se podra comprobar simplemente si es un nmero entero vlido. Sin embargo, el validador generado tambin comprueba que ese identificador exista en la base de datos (para modificar un objeto existente) o sea vaco (para poder crear un nuevo objeto). La validacin generada automticamente es mucho ms segura que comprobar simplemente que sea un nmero entero. La mayor ventaja de los formularios generados es que se pueden utilizar inmediatamente. Si se aade la instruccin <?php echo $form ?> en la pgina, ya se dispone de un formulario completamente funcional sin haber escrito ni una sola lnea de cdigo. Adems de la posibilidad de crear prototipos rpidamente, los formularios generados se pueden modificar sin necesidad de cambiar las clases generadas automticamente, gracias a la herencia entre las clases base y las clases de los formularios. Por ltimo, cada vez que se modifica el esquema de la base de datos, esta tarea genera de nuevo todos los formularios para tener en cuenta las modificaciones manteniendo las modificaciones realizadas en las otras clases.

11.3. El generador CRUD


Una vez generadas las clases de los formularios, a continuacin se crea un mdulo de Symfony que permita trabajar con los objetos a travs de un navegador. El objetivo es crear, modificar y borrar objetos de las clases Article, Author, Category y Tag. En primer lugar se crea el mdulo correspondiente a la clase Author. Aunque este mdulo se puede crear manualmente, el plugin de Doctrine incluye la tarea doctrine:generatecrud para crear un mdulo de tipo CRUD basado en la clase de un objeto del modelo. Si se emplea el mismo formulario que en la seccin anterior:

$ ./symfony doctrine:generate-crud frontend author Author

La tarea doctrine:generate-crud requiere tres argumentos:


y y y frontend: nombre de la aplicacin en la que se crea el mdulo author: nombre del mdulo creado Author: nombre de la clase del modelo para la que se crea el mdulo

Nota CRUD es el acrnimo de las palabras inglesas "Creation / Retrieval / Update / Deletion" (Crear, Obtener, Actualizar y Borrar) que resumen las cuatro operaciones bsicas que se realizan con los datos del modelo. El listado 11-4 muestra que la tarea genera cinco acciones que permiten listar (index), crear (create), modificar (edit), guardar (update) y borrar (delete) los objetos de la clase Author. Listado 11-4 - La clase authorActions generada por la tarea
// apps/frontend/modules/author/actions/actions.class.php class authorActions extends sfActions { public function executeIndex() { $this->authorList = $this->getAuthorTable()->findAll(); } public function executeCreate() { $this->form = new AuthorForm(); $this->setTemplate('edit'); } public function executeEdit($request) { $this->form = $this->getAuthorForm($request->getParameter('id')); } public function executeUpdate($request) { $this->forward404Unless($request->isMethod('post')); $this->form = $this->getAuthorForm($request->getParameter('id')); $this->form->bind($request->getParameter('author')); if ($this->form->isValid()) { $author = $this->form->save(); $this->redirect('author/edit?id='.$author->get('id'));

} $this->setTemplate('edit'); } public function executeDelete($request) { $this->forward404Unless($author = $this->getAuthorById($request>getParameter('id'))); $author->delete(); $this->redirect('author/index'); } private function getAuthorTable() { return Doctrine::getTable('Author'); } private function getAuthorById($id) { return $this->getAuthorTable()->find($id); } private function getAuthorForm($id) { $author = $this->getAuthorById($id); if ($author instanceof Author) { return new ArticleForm($author); } else { return new ArticleForm(); } } }

El flujo de trabajo del formulario de este mdulo se controla mediante los mtodos create, edit y update. Por tanto, la tarea doctrine:generate-crud tambin puede generar un nico mtodo que se encargue de estas funcionalidades mediante la opcin --non-atomicactions:
$ ./symfony doctrine:generate-crud frontend author Author --non-atomicactions

El cdigo generado con la opcin --non-atomic-actions (ver listado 11-5) es mucho ms conciso. Listado 11-5 - La clase authorActions generada con la opcin --non-atomic-actions
class authorActions extends sfActions

{ public function executeIndex() { $this->authorList = $this->getAuthorTable()->findAll(); } public function executeEdit($request) { $this->form = new AuthorForm(Doctrine::getTable('Author')>find($request->getParameter('id'))); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('author')); if ($this->form->isValid()) { $author = $this->form->save(); $this->redirect('author/edit?id='.$author->getId()); } } } public function executeDelete($request) { $this->forward404Unless($author = Doctrine::getTable('Author')>find($request->getParameter('id'))); $author->delete(); $this->redirect('author/index'); } }

Esta tarea tambin genera dos plantillas, indexSuccess y editSuccess. La plantilla editSuccess generada no utiliza la instruccin <?php echo $form ?>. Se puede modificar este comportamiento con la opcin --non-verbose-templates:
$ ./symfony doctrine:generate-crud frontend author Author --non-verbosetemplates

Esta ltima opcin es muy til cuando se estn creando los prototipos, tal y como muestra el listado 11-6. Listado 11-6 - La plantilla editSuccess
// apps/frontend/modules/author/templates/editSuccess.php <?php $author = $form->getObject() ?> <h1><?php echo $author->isNew() ? 'New' : 'Edit' ?> Author</h1> <form action="<?php echo url_for('author/edit'.(!$author->isNew() ? '?id='.$author->getId() : '')) ?>" method="post" <?php $form>isMultipart() and print 'enctype="multipart/form-data" ' ?>> <table>

<tfoot> <tr> <td colspan="2"> &nbsp;<a href="<?php echo url_for('author/index') ?>">Cancel</a> <?php if (!$author->isNew()): ?> &nbsp;<?php echo link_to('Delete', 'author/delete?id='.$author->getId(), array('post' => true, 'confirm' => 'Are you sure?')) ?> <?php endif; ?> <input type="submit" value="Save" /> </td> </tr> </tfoot> <tbody> <?php echo $form ?> </tbody> </table> </form>

Sugerencia La opcin --with-show permite generar una accin y una plantilla especficas para visualizar los datos de un objeto. Esta plantilla solamente permite visualizar los datos, no modificarlos. Si accedes ahora a la direccin /frontend_dev.php/author con un navegador, puedes visualizar el mdulo generado automticamente (ver figuras 11-1 y 11-2). Prueba a listar los autores, crear nuevos autores, modificar sus datos e incluso borrarlos gracias a la interfaz generada. Como puedes comprobar, las reglas de validacin tambin se tienen en cuenta al crear o modificar los datos.

Figura 11.1. Listado de autores

Figura 11.2. Errores de validacin al modificar los datos de un autor Si ahora se repite la operacin con la clase Article:
$ ./symfony doctrine:generate-crud frontend article Article --nonverbose-templates --non-atomic-actions

El cdigo generado es muy parecido al cdigo de la clase Author. No obstante, si intentas crear un nuevo artculo, la aplicacin produce el error fatal que se muestra en la figura 11-3.

Figura 11.3. Las tablas relacionadas deben definir el mtodo __toString() El formulario ArticleForm utiliza el widget sfWidgetFormDoctrineSelect para representar la relacin entre los objetos Article y Author. Este widget crea una lista desplegable con todos los autores disponibles. Cuando la aplicacin trata de mostrar los autores, los objetos de tipo Author se convierten en una cadena de texto gracias al mtodo mgico __toString(). Por lo tanto, es obligatorio definir el mtodo __toString() en la clase Author tal y como muestra el listado 11-7. Listado 11-7 - Definiendo el mtodo __toString() en la clase Author
class Author extends BaseAuthor { public function __toString()

{ return $this->getFirstName().' '.$this->getLastName(); } }

De la misma forma que en la clase Author, se pueden crear mtodos __toString() en las otras clases del modelo: Article, Category y Tag. Nota
sfDoctrineRecord intenta adivinar cul es el mtodo __toString() adecuado si no lo encuentra. Para ello comprueba si existen columnas llamadas title, name, subject, etc. Si

existe alguna de esas columnas, la utiliza como representacin del objeto en forma de cadena de texto. Sugerencia La opcin method del widget sfWidgetFormDoctrineSelect establece el mtodo utilizado para obtener la representacin del objeto en forma de cadena de texto. La figura 11-4 muestra cmo crear un artculo despus de haber includo el mtodo __toString().

Figura 11.4. Creando un artculo

11.4. Personalizando los formularios generados


Las tareas doctrine:build-forms y doctrine:generate-crud permiten crear mdulos de Symfony totalmente funcionales para listar, crear, editar y borrar objetos del modelo de datos. Estos mdulos tienen en cuenta las reglas de validacin y las relaciones entre tablas. Adems, todo esto se consigue sin escribir ni una sola lnea de cdigo. El siguiente paso consiste en personalizar el cdigo generado automticamente. Como los formularios incluyen muchos elementos, algunos aspectos de cada formulario deben ser modificados.
11.4.1. Configurando los widgets y validadores

En primer lugar se configuran los widgets y validadores generados automticamente. El formulario ArticleForm incluye un campo llamado slug. Un slug es una cadena de caracteres que representan de forma nica a cada artculo dentro de la URL. Si se considera por ejemplo un artculo titulado "Optimiza las aplicaciones creadas con Symfony", su slug sera 12-optimiza-las-aplicaciones-creadas-con-symfony, siendo 12 el valor del campo id de ese artculo. Normalmente el slug se genera automticamente a partir del ttulo del objeto cada vez que se guarda el objeto en la base de datos. En el siguiente ejemplo se considera que el usuario tambin puede indicar de forma explcita el valor del slug de un artculo. En este caso, aunque es obligatorio que el slug tenga un valor vlido, no puede ser un campo obligatorio del formulario. Por este motivo se modificar su validador para que sea opcional, tal y como muestra el listado 11-8. Adems, se modifica el campo content para aumentar su tamao en pantalla y para forzar al usuario a escribir al menos cinco caracteres. Listado 11-8 - Personalizando widgets y validadores
class ArticleForm extends BaseArticleForm { publicfunction configure() { // ... $this->validatorSchema['slug']->setOption('required', false); $this->validatorSchema['content']->setOption('min_length', 5); $this->widgetSchema['content']->setAttributes(array('rows' =>10, 'cols' =>40)); } }

El cdigo anterior utiliza los objetos validatorSchema y widgetSchema como si fueran arrays de PHP. Estos arrays asociativos admiten como clave el nombre de un campo del formulario y devuelven respectivamente el objeto validador y el objeto del widget. Mediante estos objetos se personalizan los campos y widgets individualmente.
Nota

Para poder utilizar los objetos como si fueran arrays de PHP, las clases sfValidatorSchema y sfWidgetFormSchema implementan la interfaz ArrayAccess, disponible en PHP desde la versin 5. Para asegurar que dos artculos no tengan el mismo valor en su campo slug, la definicin del esquema incluye una restriccin que exige que el valor del campo sea nico. Esta restriccin de la base de datos se refleja en el formulario ArticleForm mediante el validador sfValidatorDoctrineUnique. Este validador permite asegurar que el valor de un campo del formulario sea nico en la base de datos. Se puede emplear este validador, entre otras cosas, para asegurar que una direccin de email o un login sean nicos. El listado 11-9 muestra como lo utiliza el formulario ArticleForm. Listado 11-9 - Utilizando el validador sfValidatorDoctrineUnique para asegurar que un valor sea nico
class BaseArticleForm extends BaseFormDoctrine { publicfunction setup() { // ... $this->validatorSchema->setPostValidator( new sfValidatorDoctrineUnique(array('model' =>'Article', 'column' =>array('slug'))) ); } }

El validador sfValidatorDoctrineUnique es de tipo postValidator, ya que se ejecuta sobre los datos completos del formulario despus de que cada campo individual haya sido validado. De hecho, para validar que el valor del campo slug sea nico, el validador no slo debe acceder al propio valor del campo slug, sino que tambin debe conocer el valor de la clave primaria. Adems, las reglas de validacin son diferentes en la fase de creacin de datos y en la de modificacin, ya que el slug puede permanecer invariante mientras se modifica un artculo. A continuacin se modifica el campo active de la tabla author, que indica si un autor se encuentra activo. El listado 11-10 muestra cmo excluir a los autores inactivos en el formulario ArticleForm, modificando la opcin query del widget sfWidgetDoctrineSelect asociado con el campo author_id. La opcin query acepta un

objeto de tipo consulta de Doctrine, lo que permite restringir los valores mostrados en la lista desplegable asociada. Listado 11-10 - Personalizando el widget sfWidgetDoctrineSelect
class ArticleForm extends BaseArticleForm { publicfunction configure() { // ... $query = Doctrine_Query::create() ->from('Author a') ->where('a.active = ?', true); $this->widgetSchema['author_id']->setOption('query', $query); } }

Personalizar el widget permite restringir las opciones que se muestran en la lista desplegable, pero no olvides que tambin es necesario realizar esta modificacin en el validador, tal y como muestra el listado 11-11. Al igual que el widget sfWidgetProperSelect, el validador sfValidatorDoctrineChoice acepta una opcin llamada query que permite restringir los valores vlidos para un campo del formulario. Listado 11-11 - Personalizando el validador sfValidatorDoctrineChoice
class ArticleForm extends BaseArticleForm { publicfunction configure() { // ... $query = Doctrine_Query::create() ->from('Author a') ->where('a.active = ?', true); $this->widgetSchema['author_id']->setOption('query', $query); $this->validatorSchema['author_id']->setOption('query', $query); } }

En el ejemplo anterior, se define el objeto Query directamente en el mtodo configure(). En el proyecto que se est realizando, esta consulta puede ser til en muchas otras circunstancias, por lo que es mejor crear un mtodo llamado getActiveAuthorsQuery() en la clase AuthorPeer e invocar este mtodo desde ArticleForm, tal y como muestra el listado 11-12. Listado 11-12 - Refactorizando la consulta con Query en el modelo
class AuthorTable extends Doctrine_Table {

publicfunction getActiveAuthorsQuery() { $query = Doctrine_Query::create() ->from('Author a') ->where('a.active = ?', true); return$query; } } class ArticleForm extends BaseArticleForm { publicfunction configure() { $authorQuery = Doctrine::getTable('Author')->getActiveAuthorsQuery(); $this->widgetSchema['author_id']->setOption('query', $authorQuery); $this->validatorSchema['author_id']->setOption('query', $authorQuery); } }

Sugerencia

De la misma forma que el widget sfWidgetDoctrineSelect y el validador sfValidatorDoctrineChoice representan una relacin 1-n entre dos tablas, el widget sfWidgetDoctrineSelectMany y el validador sfValidatorDoctrineChoiceMany representan una relacin n-n con las mismas opciones que los anteriores. El formulario ArticleForm utiliza estas clases para representar la relacin entre las tablas article y tag.
11.4.2. Modificando el validador

El esquema de datos define el campo email como un dato string(255), por lo que Symfony crea un validador de tipo sfValidatorString() que restringe su longitud mxima a 255 caracteres. Como este campo tambin debe cumplir la condicin de que sea una direccin de correo electrnico vlida, el listado 11-14 reemplaza el validador generado automticamente por un validador de tipo sfValidatorEmail. Listado 11-13 - Modificando el validador del campo email en la clase AuthorForm
class AuthorForm extends BaseAuthorForm { publicfunction configure() { $this->validatorSchema['email'] = new sfValidatorEmail(); } }

11.4.3. Aadiendo un validador

En la seccin anterior se modific un validador generado automticamente. Sin embargo, en el caso del campo email, lo ideal sera mantener el validador que controla su longitud mxima. En el listado 11-14 se emplea el validador sfValidatorAnd para asegurar que el

email proporcionado sea vlido y que su longitud no sea mayor que la longitud mxima permitida en ese campo. Listado 11-14 - Utilizando un validador mltiple
class AuthorForm extends BaseAuthorForm { publicfunction configure() { $this->validatorSchema['email'] = new sfValidatorAnd(array( new sfValidatorString(array('max_length' =>255)), new sfValidatorEmail(), )); } }

El cdigo del ejemplo anterior no es ideal porque si ms adelante se modifica el tamao del campo email en el esquema de la base de datos, es necesario modificarlo tambin en el formulario. Por lo tanto, en vez de reemplazar el validador generado automticamente, es mejor aadir uno nuevo, tal y como muestra el listado 11-15. Listado 11-15 - Aadiendo un validador
class AuthorForm extends BaseAuthorForm { publicfunction configure() { $this->validatorSchema['email'] = new sfValidatorAnd(array( $this->validatorSchema['email'], new sfValidatorEmail(), )); } }

11.4.4. Modificando un widget

En el esquema de la base de datos, la tabla article almacena el estado de cada artculo en forma de cadena de caracteres en el campo status. Los posibles valores del estado se definen en la clase ArticePeer, tal y como muestra el listado 11-16. Listado 11-16 - Definiendo los posibles estados en la clase ArticlePeer
class ArticlePeer extends BaseArticlePeer { static protected $statuses = array('draft', 'online', 'offline'); staticpublicfunction getStatuses() { return self::$statuses; } // ...

Cuando se editan los datos de un artculo, el campo status se debera representar en forma de lista desplegable en vez de como un cuadro de texto. Para ello, se modifica el widget utilizado hasta el momento mediante el cdigo mostrado en el listado 11-17. Listado 11-17 - Modificando el widget del campo status
class ArticleForm extends BaseArticleForm { publicfunction configure() { $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses())); } }

Para completar la modificacin, tambin se debe cambiar el validador para asegurar que el estado seleccionado pertenece a alguna de las posibles opciones de la lista (ver listado 1118). Listado 11-18 - Modificando el validador del campo status
class ArticleForm extends BaseArticleForm { publicfunction configure() { $statuses = ArticlePeer::getStatuses(); $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' =>$statuses)); $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' =>array_keys($statuses))); } }

11.4.5. Eliminando un campo

La tabla article dispone de dos columnas especiales llamadas created_at y updated_at, para las cuales Doctrine actualiza automticamente sus valores. Para evitar que el usuario las modifique, el listado 11-19 muestra cmo se pueden eliminar del formulario. Listado 11-19 - Eliminando un campo
class ArticleForm extends BaseArticleForm { publicfunction configure() { unset($this->validatorSchema['created_at']); unset($this->widgetSchema['created_at']);

unset($this->validatorSchema['updated_at']); unset($this->widgetSchema['updated_at']); } }

Para eliminar un campo es preciso eliminar su validador y su widget. El listado 11-20 muestra cmo borrar los dos con una nica instruccin accediendo al formulario como si fuera un array de PHP. Listado 11-20 - Eliminando un campo accediendo al formulario como si fuera un array de PHP
class ArticleForm extends BaseArticleForm { publicfunction configure() { unset($this['created_at'], $this['updated_at']); } }

11.4.6. Resumiendo

Los listados 11-21 y 11-22 muestran el cdigo definitivo de los formularios ArticleForm y AuthorForm despus de personalizarlos. Listado 11-21 - Formulario ArticleForm
class ArticleForm extends BaseArticleForm { publicfunction configure() { $authorQuery = Doctrine::getTable('Author')->getActiveAuthorsQuery(); // widgets $this->widgetSchema['content']->setAttributes(array('rows' =>10, 'cols' =>40)); $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses())); $this->widgetSchema['author_id']->setOption('query', $authorQuery); // validators $this->validatorSchema['slug']->setOption('required', false); $this->validatorSchema['content']->setOption('min_length', 5); $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' =>array_keys(ArticlePeer::getStatuses()))); $this->validatorSchema['author_id']->setOption('query', $authorQuery); unset($this['created_at']); unset($this['updated_at']); } }

Listado 11-22 - Formulario AuthorForm


class AuthorForm extends BaseAuthorForm { publicfunction configure() { $this->validatorSchema['email'] = new sfValidatorAnd(array( $this->validatorSchema['email'], new sfValidatorEmail(), )); } }

La tarea doctrine:build-forms permite generar automticamente la mayora de elementos de los formularios mediante la introspeccin del modelo de objetos. Las principales ventajas de esta automatizacin son:
y

Mejora la productividad del programador, evitndole todo el trabajo repetitivo y redundante. De esta forma el programador slo se encarga de personalizar los validadores y los widgets en funcin de la lgica de negocio de la aplicacin. Si se actualiza el esquema de datos, los formularios generados tambin se actualizan automticamente. Una vez ms, el programador slo se encarga de ajustar la personalizacin realizada anteriormente.

La siguiente seccin trata de la modificacin de las acciones y plantillas generadas por la tarea doctrine:generate-crud.

11.5. Serializacin de formularios


Las secciones anteriores explican cmo personalizar los formularios generados automticamente por la tarea doctrine:build-forms. En esta seccin, se personaliza el flujo de trabajo de los formularios, comenzando por el cdigo generado mediante la tarea doctrine:generate-crud.
11.5.1. Valores iniciales

Cada instancia de un formulario de Doctrine siempre est conectada con un objeto de Doctrine. El objeto de Doctrine relacionado siempre pertenece a la clase que devuelve el mtodo getModelName(). El formulario AuthorForm de los ejemplos anteriores slo puede estar relacionado con objetos de la clase Author. El objeto relacionado o es un objeto vaco (una instancia nueva de la clase Author) o es el objeto utilizado como primer argumento del constructor. Mientras el constructor de un formulario tpico utiliza como primer argumento un array de valores, el constructor de un formulario de Doctrine siempre utiliza un objeto de Doctrine. Este objeto es el que se emplea para obtener el valor inicial de cada campo del formulario. El mtodo getObject() devuelve el objeto asociado con la actual instancia del formulario y el mtodo isNew() permite averiguar si el objeto se ha enviado mediante el constructor:

// creando un nuevo objeto $authorForm = new AuthorForm(); print$authorForm->getObject()->getId(); // muestra null print$authorForm->isNew(); // muestra true // modificando un objeto existente $author = Doctrine::getTable('Author')->find(1); $authorForm = new AuthorForm($author); print$authorForm->getObject()->getId(); // muestra 1 print$authorForm->isNew(); // muestra false

11.5.2. Flujo de trabajo

Como se explic al principio de este captulo, la accin edit mostrada en el listado 11-23 es la encargada de gestionar el flujo de trabajo del formulario. Listado 11-23 - El mtodo executeEdit del mdulo author
// apps/frontend/modules/author/actions/actions.class.php class authorActions extends sfActions { // ... publicfunction executeEdit($request) { $author = Doctrine::getTable('Author')->find($request>getParameter('id')); $this->form = new AuthorForm($author); if($request->isMethod('post')) { $this->form->bind($request->getParameter('author')); if($this->form->isValid()) { $author = $this->form->save(); $this->redirect('author/edit?id='.$author->getId()); } } } }

Aunque la accin edit se parece a las acciones que se han descrito en los captulos anteriores, existen varias diferencias:
y

El primer argumento del constructor del formulario es un objeto Doctrine de la clase Author:

$author = Doctrine::getTable('Author')->find($request>getParameter('id')); $this->form = new AuthorForm($author);

El formato del atributo name del widget se adapta automticamente para poder obtener los datos enviados por el usuario mediante un array de PHP con el mismo nombre que la tabla relacionada (author):

$this->form->bind($request->getParameter('author')); y

Si el formulario es vlido, un simple llamada al mtodo save() crea o actualiza el objeto Doctrine relacionado con el formulario:

$author = $this->form->save();

11.5.3. Creando y modificando objetos Doctrine

El cdigo del listado 11-23 dispone de un nico mtodo para crear y modificar objetos de la clase Author:
y

Crear nuevos objetos de tipo Author: o Se invoca la accin index sin ningn parmetro id ($request>getParameter('id') es null) o El mtodo find() devuelve null o El objeto form se asocia con un objeto Doctrine vaco de tipo Author o Si el formulario es vlido, la llamada a $this->form->save() crea un nuevo objeto de tipo Author Modificar objetos de tipo Author existentes: o Se invoca la accin index con un parmetro id ($request>getParameter('id') es la clave primaria del objeto de tipo Author que se quiere modificar) o El mtodo find() devuelve el objeto de tipo Author relacionado con esa clave primaria o El objeto form se asocia con el objeto anterior o Si el formulario es vlido, la llamada a $this->form->save() actualiza el objeto de tipo Author

11.5.4. El mtodo save()

Cuando un formulario de Doctrine es vlido, el mtodo save() actualiza el objeto relacionado y lo almacena en la base de datos. En realidad, este mtodo no slo guarda el objeto principal sino que tambin almacena todos los objetos relacionados. El formulario ArticleForm por ejemplo tambin actualiza las etiquetas asociadas con el artculo. Como la relacin entre las tablas article y tag es de tipo n-n, las etiquetas relacionadas con un artculo se guardan en la tabla article_tag (utilizando el mtodo saveArticleTagList() generado automticamente). Para asegurar la integridad de los datos guardados, el mtodo save() realiza todas las actualizaciones en una transaccin

Nota

Como se explica en el captulo 9, el mtodo save() tambin actualiza las tablas internacionalizadas. Utilizando el mtodo ''bindAndSave()'' El mtodo bindAndSave() asocia los datos enviados por el usuario con el formulario, valida el formulario completo y actualiza los objetos relacionados en la base de datos, todo ello en una nica operacin:
class articleActions extends sfActions { publicfunction executeCreate(sfWebRequest $request) { $this->form = new ArticleForm(); if($request->isMethod('post')&&$this->form->bindAndSave($request>getParameter('article'))) { $this->redirect('article/created'); } } }

11.5.5. Trabajando con archivos subidos

El mtodo save() actualiza automticamente los objetos Doctrine, pero no se encarga de los elementos relacionados como los archivos subidos. A continuacin se adjunta un archivo a cada artculo. Los archivos subidos se almacenan en el directorio web/uploads y en el campo file de la tabla article se almacena la ruta hasta el archivo, tal y como muestra el listado 11-24. Listado 11-24 - Esquema de la tabla article con un archivo adjunto
// config/schema.yml doctrine: article: // ... file: string(255)

Cada vez que se actualiza el esquema de datos es necesario actualizar el modelo de objetos, la base de datos y los formularios:
$ ./symfony doctrine:build-all

Cuidado

Debes tener en cuenta que la tarea doctrine:build-all borra todas las tablas de la base de datos antes de volver a crearlas. Por lo tanto, se pierde toda la informacin existente en

las tablas. Este es el motivo por el que se recomienda crear archivos con datos de prueba (fixtures) para cargarlos cada vez que se modifica el modelo de datos. El listado 11-25 muestra cmo modificar la clase ArticleForm para asociar un widget y un validador con el campo file. Listado 11-25 - Modificando el campo file del formulario ArticleForm
class ArticleForm extends BaseArticleForm { publicfunction configure() { // ... $this->widgetSchema['file'] = new sfWidgetFormInputFile(); $this->validatorSchema['file'] = new sfValidatorFile(); } }

No olvides que todos los formularios que permiten adjuntar archivos deben incluir un atributo llamado enctype en la etiqueta <form>. En el captulo 2 se explica cmo modificar la etiqueta <form> de la plantilla para gestionar los archivos subidos. El listado 11-26 muestra las modificaciones necesarias para guardar el archivo subido en el servidor y para almacenar su ruta en el objeto article. Listado 11-26 - Guardando el objeto article y el archivo subido en la accin
publicfunction executeEdit($request) { $author = Doctrine::getTable('Author')->find($request>getParameter('id')); $this->form = new ArticleForm($author); if($request->isMethod('post')) { $this->form->bind($request->getParameter('article'), $request>getFiles('article')); if($this->form->isValid()) { $file = $this->form->getValue('file'); $filename = sha1($file->getOriginalName()).$file->getExtension($file>getOriginalExtension()); $file->save(sfConfig::get('sf_upload_dir').'/'.$filename); $article = $this->form->save(); $this->redirect('article/edit?id='.$article->getId()); } } }

Despus de guardar el archivo subido en algn directorio, el objeto sfValidatedFile ya conoce la ruta absoluta del archivo. Cuando se invoca el mtodo save(), se emplean los valores de cada campo para actualizar el objeto. En el caso del campo file, el objeto sfValidatedFile se convierte en una cadena de caracteres mediante el mtodo __toString() y se devuelve el valor de la ruta absoluta del archivo. A continuacin, la columna file de la tabla article almacena esta ruta absoluta.
Sugerencia

Si slo quieres almacenar la ruta relativa desde el directorio sfConfig::get('sf_upload_dir'), se puede crear una clase que herede de sfValidatedFile y que utilice la opcin validated_file_class para enviar el nombre de la nueva clase al validador sfValidatorFile. De esta forma, el validador devuelve una instancia de tu clase. En lo que resta de captulo se muestra otra forma de hacerlo, que consiste en modificar el valor de la columna file antes de guardar el objeto en la base de datos.
11.5.6. Personalizando el mtodo ''save()''

En la seccin anterior se explica cmo guardar en la accin edit un archivo subido. Uno de los principios de la programacin orientada a objetos es la reutilizacin del cdigo mediante su encapsulacin en clases. Por tanto, en vez de duplicar en cada accin del formulario ArticleForm el cdigo que guarda un archivo, es mejor mover ese cdigo a la clase ArticleForm. El listado 11-27 muestra como redefinir el mtodo save() para almacenar los archivos subidos y para borrar un archivo existente. Listado 11-27 - Redefiniendo el mtodo save() de la clase ArticleForm
class ArticleForm extends BaseFormDoctrine { // ... publicfunction save($con = null) { if(file_exists($this->getObject()->getFile())) { unlink($this->getObject()->getFile()); } $file = $this->getValue('file'); $filename = sha1($file->getOriginalName()).$file->getExtension($file>getOriginalExtension()); $file->save(sfConfig::get('sf_upload_dir').'/'.$filename); return parent::save($con); } }

Despus de mover el cdigo al formulario, la accin edit es idntica al cdigo generado automticamente por la tarea doctrine:generate-crud. Refactorizando el cdigo del modelo en el formulario Normalmente, las acciones generadas por la tarea doctrine:generate-crud no se modifican. El cdigo que se podra aadir a la accin edit, especialmente el cdigo relacionado con la serializacin del formulario, normalmente se coloca en las clases del modelo o del formulario. Despus de haber refactorizado la clase del formulario para gestionar los archivos subidos, a continuacin se muestra otro ejemplo relacionado con el modelo. El formulario ArticleForm dispone de un campo llamado slug. Como se coment anteriormente, el valor de este campo se debe calcular automticamente a partir del campo title y tambin puede ser definido directamente por el usuario. Esta lgica no depende del formulario sino del modelo, tal y como muestra el siguiente cdigo:
class Article extends BaseArticle { publicfunction save($con = null) { if(!$this->getSlug()) { $this->setSlugFromTitle(); } return parent::save($con); } protection function setSlugFromTitle() { // ... } }

El principal objetivo de estas refactorizaciones es el respeto a la separacin entre las diferentes capas de la aplicacin y la posibilidad de reutilizar el cdigo de las aplicaciones.
11.5.7. Personalizando el mtodo ''doSave()''

Como se vio en las secciones anteriores, cuando se guarda un objeto se realiza una transaccin para asegurar que todas las operaciones del proceso de guardado se realizan correctamente. Cuando se redefine el mtodo save() como en la seccin anterior al guardar un archivo subido, el cdigo se ejecuta de forma independiente a esa transaccin. El listado 11-28 muestra cmo utilizar el mtodo doSave() para incluir en la transaccin global el cdigo encargado de guardar el archivo subido. Listado 11-28 - Redefiniendo el mtodo doSave() en el formulario ArticleForm
class ArticleForm extends BaseFormDoctrine

{ // ... publicfunction doSave($con = null) { if(file_exists($this->getObject()->getFile())) { unlink($this->getObject()->getFile()); } $file = $this->getValue('file'); $filename = sha1($file->getOriginalName()).$file->getExtension($file>getOriginalExtension()); $file->save(sfConfig::get('sf_upload_dir').'/'.$filename); return parent::doSave($con); } }

Como el mtodo doSave() se ejecuta en la transaccin creada por el mtodo save(), si la llamada al mtodo save() del objeto file() lanza una excepcin, el objeto no se guarda.
11.5.8. Personalizando el mtodo ''updateObject()''

En ocasiones es necesario modificar el objeto asociado al formulario despus de su actualizacin automtica pero antes de que se almacene en la base de datos. Siguiendo con el ejemplo de los archivos subidos, en esta ocasin no se quiere almacenar en la columna file la ruta absoluta del archivo subido, sino que slo se guarda la ruta relativa respecto al directorio sfConfig::get('sf_upload_dir'). El listado 11-29 muestra cmo redefinir el mtodo updateObject() del formulario ArticleForm para modificar el valor de la columna file despus de la actualizacin automtica del objeto pero antes de que sea almacenado. Listado 11-29 - Redefiniendo el mtodo updateObject() y la clase ArticleForm
class ArticleForm extends BaseFormDoctrine { // ... publicfunction updateObject($values = null) { $object = parent::updateObject($values); $object->setFile(str_replace(sfConfig::get('sf_upload_dir').'/', '', $object->getFile())); return$object; } }

Captulo 12. Referencia de Widgets 12.1. Introduccin


El framework de formularios de Symfony incluye muchos widgets tiles que cubren las necesidades ms comunes de la mayora de proyectos. En este captulo se describen detalladamente todos los widgets que incluye por defecto Symfony. Tambin se explican los widgets incluidos en los plugins sfFormExtraPlugin, sfPropelPlugin y sfDoctrinePlugin, ya que estos plugins los desarrollan los creadores de Symfony e incluyen muchos widgets tiles.
Sugerencia

Aunque no utilices el framework Symfony, puedes hacer uso de los widgets incluidos en sfFormExtraPlugin, sfPropelPlugin y sfDoctrinePlugin simplemente copiando el directorio widget/ de cada plugin en algn lugar de tu proyecto. Antes de profundizar en los detalles de cada widget, veamos las caractersticas comunes de todos los widgets.
12.1.1. La clase base sfWidget

Todos los widgets de Symfony heredan de la clase base sfWidget, que proporciona las caractersticas comunes de todos los widgets. Los widgets se muestran por defecto mediante XHTML, aunque se puede cambiar a HTML con el mtodo setXhtml():
sfWidget::setXhtml(false);

Los widgets tambin se encargan de aplicar de forma automtica el mecanismo de escape a los atributos HTML y a todos los contenidos potencialmente peligrosos. Para ello, es necesario conocer la codificacin utilizada en el proyecto. Por defecto la codificacin utilizada es UTF-8, pero se puede configurar cualquier otra codificacin mediante el mtodo setCharset():
sfWidget::setCharset('ISO-8859-1');

Nota

Si utilizas los widgets de Symfony dentro de un proyecto Symfony, la codificacin se obtiene directamente a partir de su valor en el archivo de configuracin settings.yml. Si un widget necesita hojas de estilos y/o archivos de JavaScript para funcionar, tambin se pueden redefinir los mtodos getJavaScripts() y getStylesheets():

class Widget extends sfWidget { publicfunction getStylesheets() { // las claves de los arrays son las rutas de los archivos y los // valores son los nombres de los medios CSS separados por comas returnarray( '/ruta/hasta/archivo.css' =>'all', '/otro/archivo.css' =>'screen,print', ); } publicfunction getJavaScripts() { returnarray('/ruta/hasta/archivo.js', '/otro/archivo.js'); } }

12.1.2. La clase base sfWidgetForm

En esta seccin se van a mostrar los widgets de formulario. Todos ellos heredan de la clase base sfWidgetForm, que a su vez extiende la clase sfWidget para proporcionar algunas caractersticas tiles por defecto. Cuando se crea un widget, se le pueden pasar como argumentos opcionales diferentes opciones y atributos HTML:
$w = new sfWidgetFormInput( array('default' =>'Fabien'), array('class' =>'foo') );

Las opciones y los atributos HTML tambin se pueden establecer con los mtodos setOptions() y setAttributes():
$w = new sfWidgetFormInput(); $w->setOptions(array('default' =>'Fabien')); $w->setAttributes(array('class' =>'foo'));

Si se quiere establecer una opcin o atributo HTML individual, se pueden utilizar los mtodos setOption() y setAttribute():
$w = new sfWidgetFormInput(); $w->setOption('default', 'Fabien'); $w->setAttribute('class', 'foo');

Los widgets se muestran invocando el mtodo render():


$w->render('nombre', 'valor', array('class' =>'foo'));

El mtodo render() acepta los siguientes argumentos:

y y y

El nombre del widget El valor del widget Atributos HTML opcionales (estos atributos se unen a los atributos por defecto establecidos al construir el widget)

Nota

Los widgets no almacenan informacin sobre su estado, lo que significa que una sola instancia del widget se puede mostrar tantas veces como necesites con diferentes argumentos. El widget anterior se muestra de la siguiente forma:
<input class="foo" type="text" name="nombre" id="nombre" /> sfWidgetForm define las siguientes opciones por defecto:

Opcin
is_hidden

Descripcin Vale true si el widget debe ser de tipo oculto y false en cualquier otro caso (su valor por defecto es false)

Vale true si el widget requiere que el formulario sea de tipo multipart (por needs_multipart ejemplo para adjuntar archivos) y false en cualquier otro caso (su valor por defecto es false)
default

El valor inicial que debe mostrar el widget El ttulo que se debe utilizar cuando se muestra el widget mediante un esquema de widgets El formato utilizado para generar los atributos id de HTML (su valor por defecto es %s)

label

id_format

Nota

La opcin is_hidden la utilizan las clases del esquema de widgets para mostrar los widgets ocultos sin ninguna decoracin. La opcin needs_multipart la utilizan las clases de formulario para aadir el atributo enctype="multipart/form-data" cuando se muestra una etiqueta <form>. La clase sfWidgetForm tambin incluye mtodos accesores para todas las opciones:
y y is_hidden: mtodos isHidden() y setHidden() needs_multipart: mtodo needsMultipartForm()

y y y

default: mtodos getValue() y setValue() label: mtodos getLabel() y setLabel() id_format: mtodos getIdFormat() y setIdFormat()

12.1.3. Esquema de widgets

Un esquema de widgets de formulario es un widget especial que agrupa uno o varios widgets. En las siguientes secciones, los widgets se han agrupado en categoras para facilitar su explicacin.

12.2. Widgets
A continuacin se muestra el listado completo de todos los widgets de Symfony:
y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y y sfWidgetFormChoice sfWidgetFormDate sfWidgetFormDateRange sfWidgetFormDateTime sfWidgetFormDoctrineChoice sfWidgetFormFilterInput sfWidgetFormFilterDate sfWidgetFormI18nDate sfWidgetFormI18nDateTime sfWidgetFormI18nSelectCountry sfWidgetFormI18nSelectLanguage sfWidgetFormI18nSelectCurrency sfWidgetFormI18nTime sfWidgetFormInput sfWidgetFormInputCheckbox sfWidgetFormInputFile sfWidgetFormInputFileEditable sfWidgetFormInputHidden sfWidgetFormInputPassword sfWidgetFormJQueryAutocompleter sfWidgetFormJQueryDate sfWidgetFormPropelChoice sfWidgetFormReCaptcha sfWidgetFormSchema sfWidgetFormSchemaDecorator sfWidgetFormSelect sfWidgetFormSelectDoubleList sfWidgetFormSelectMany sfWidgetFormSelectCheckbox sfWidgetFormSelectRadio sfWidgetFormTextarea sfWidgetFormTextareaTinyMCE

sfWidgetFormTime

12.3. Widgets de tipo input


12.3.1. sfWidgetFormInput

La etiqueta <input> es seguramente la etiqueta ms sencilla que vas a utilizar en los formularios y se representa mediante la clase sfWidgetFormInput.
Opcin
type

Descripcin El valor del atributo type de HTML (por defecto vale text)

$w = new sfWidgetFormInput(); echo$w->render('nombre'); # <input type="text" name="nombre" id="nombre" />

12.3.2. sfWidgetFormInputCheckbox
sfWidgetFormInputCheckbox es un widget de tipo <input> cuyo atributo type es checkbox. $w = new sfWidgetFormInputCheckbox(); echo$w->render('nombre'); # <input type="checkbox" name="nombre" id="nombre" />

12.3.3. sfWidgetFormInputHidden
sfWidgetFormInputHidden es un widget de tipo <input> cuyo atributo type vale hidden. La opcin is_hidden tambin se establece al valor true. $w = new sfWidgetFormInputHidden(); echo$w->render('nombre'); # <input type="hidden" name="nombre" id="nombre" />

12.3.4. sfWidgetFormInputPassword
sfWidgetFormInputPassword es un widget de tipo <input> cuyo atributo type es password. $w = new sfWidgetFormInputPassword(); echo$w->render('nombre'); # <input type="password" name="nombre" id="nombre" />

12.3.5. sfWidgetFormInputFile

The sfWidgetFormInputFile es un widget de tipo <input> cuyo atributo type vale file. El valor de la opcin needs_multipart se establece automticamente a true.
$w = new sfWidgetFormInputFile(); echo$w->render('nombre'); # <input type="file" name="nombre" id="nombre" />

12.3.6. sfWidgetFormInputFileEditable
sfWidgetFormInputFileEditable es un tipo de widget relacionado con los archivos, que extiende el widget sfWidgetFormInputFile para aadir la posibilidad de mostrar o

eliminar un archivo subido previamente.


Opcin
file_src

Descripcin La ruta web de la imagen actual (esta opcin es obligatoria) Valor booleano que vale true si se ha habilitado la posibilidad de editar el archivo y false en cualquier otro caso Indica si el archivo es una imagen que se puede visualizar

edit_mode

is_image

with_delete Indica si se aade un checkbox que permita borrar el archivo delete_label El ttulo que se muestra para la opcin de borrar el archivo

template

Cdigo HTML de la plantilla utilizada para mostrar este widget. Las variables disponibles en esta plantilla son las siguientes: input (el widget para subir una imagen) delete (el checkbox para borrar el archivo) delete_label (el ttulo de la opcin de borrado) file (la etiqueta del archivo)

Cuidado

En el modo edit, este widget muestra otro widget cuyo nombre se forma con el mismo nombre que el widget para subir el archivo y el sufijo _delete. Por lo tanto, cuando crees un formulario, no olvides aadir tambin un validador para este campo adicional.
12.3.7. sfWidgetFormTextarea
sfWidgetFormTextarea es un widget simple de tipo <textarea>. Los valores de los atributos rows y cols se establecen automticamente porque son obligatorios.

$w = new sfWidgetFormTextarea(); echo$w->render('nombre'); # <textarea rows="4" cols="30" name="nombre" id="nombre"></textarea>

12.3.8. sfWidgetFormTextareaTinyMCE

Al contrario que el widget del <textarea> simple, sfWidgetFormTextareaTinyMCE muestra un editor avanzado de tipo WYSIWYG:
$w = new sfWidgetFormTextareaTinyMCE( array(), array('class' =>'foo') );

Cuidado

Este widget es parte del plugin sfFormExtraPlugin de Symfony. Como los archivos JavaScript del editor TinyMCE no se incluyen con el plugin, debes instalarlos e incluirlos manualmente.
Opcin Descripcin

theme El tema utilizado por Tiny MCE (por defecto se utiliza el tema advanced) width La anchura del editor height La altura del editor config Un array con la configuracin especfica de JavaScript

12.4. Widgets para elecciones


12.4.1. Tipos de elecciones

Cuando el usuario debe elegir un valor entre una lista de posibilidades, HTML ofrece diferentes formas de representar esa eleccin:
y

Una etiqueta <select>:

Figura 12.1. Lista desplegable simple


y

Una etiqueta <select> con el atributo multiple:

Figura 12.2. Lista desplegable que permite seleccionar varias opciones


y

Una lista de etiquetas <input> con el atributo type igual a radio:

Figura 12.3. Grupo de radio buttons


y

Una lista de etiquetas <input> con el atributo type igual a checkbox:

Figura 12.4. Grupo de checkboxes A pesar de sus diferencias, todas ellas permiten al usuario seleccionar una o varias opciones entre una lista finita de posibilidades. El widget sfWidgetFormChoice estandariza y agrupa todas estas variantes en un nico widget. De esta forma, la misma eleccin se puede mostrar de cualquiera de las cuatro formas mostradas anteriormente. Como se ver ms adelante, tambin es posible definir una representacin propia para la eleccin.
sfWidgetFormChoice es un widget especial porque delega la responsabilidad de mostrar

sus contenidos a otro widget. La representacin visual del widget se controla mediante las opciones expanded y multiple:
Valor de expanded Valor de multiple
true true false false true false true false

Widget utilizado
sfWidgetFormSelectCheckbox sfWidgetFormSelectRadio sfWidgetFormSelectMany sfWidgetFormSelect

Nota

Los widgets sfWidgetFormSelect, sfWidgetFormSelectMany, sfWidgetFormSelectCheckbox y sfWidgetFormSelectRadio que utiliza el widget

sfWidgetFormChoice para mostrarse son widgets normales que tambin se pueden utilizar

de forma individual. En esta seccin no se documenta el uso de cada uno de estos widgets porque casi siempre es mejor utilizar el widget sfWidgetFormChoice, que es mucho ms flexible. A continuacin se muestra la representacin HTML de cada widget:
$w = new sfWidgetFormChoice(array( 'choices' =>array('Fabien Potencier', 'Fabian Lange'), ));

Figura 12.5. Lista desplegable simple


$w = new sfWidgetFormChoice(array( 'multiple' =>true, 'choices' =>array('PHP', 'symfony', 'Doctrine', 'Propel', 'model'), ));

Figura 12.6. Lista desplegable que permite seleccionar varias opciones


$w = new sfWidgetFormChoice(array( 'expanded' =>true, 'choices' =>array('published', 'draft', 'deleted'), ));

Figura 12.7. Grupo de radio buttons


$w = new sfWidgetFormChoice(array( 'expanded' =>true, 'multiple' =>true, 'choices' =>array('A week of symfony', 'Call the expert', 'Community'), ));

Figura 12.8. Grupo de checkboxes


12.4.2. Agrupacin de opciones

El widget sfWidgetFormChoice tambin soporta la agrupacin de opciones cuando se pasa un array de arrays como valor de la opcin choices:

$choices = array( 'Europe' =>array('France' =>'France', 'Spain' =>'Spain', 'Italy' =>'Italy'), 'America' =>array('USA' =>'USA', 'Canada' =>'Canada', 'Brazil' =>'Brazil'), ); $w = new sfWidgetFormChoice(array('choices' =>$choices));

Figura 12.9. Agrupacin de opciones Las opciones expanded y multiple siguen funcionando como antes:
$w = new sfWidgetFormChoice(array( 'choices' =>$choices, 'expanded' =>true, ));

Figura 12.10. Agrupacin de opciones expandidas La plantilla utilizada para mostrar el widget tambin se puede personalizar:
$w = new sfWidgetFormChoice(array( 'choices' =>$choices, 'expanded' =>true, 'renderer_options' =>array('template' =>'<strong>%group%</strong> %options%'), ));

Figura 12.11. Agrupacin de opciones expandidas y con formato personalizado A continuacin se muestran otros ejemplos de combinaciones de opciones:
$w = new sfWidgetFormChoice(array( 'choices' =>$choices, 'multiple' =>true, ));

Figura 12.12. Agrupacin de opciones con seleccin mltiple


$w = new sfWidgetFormChoice(array( 'choices' =>$choices, 'multiple' =>true, 'expanded' =>true, 'renderer_options' =>array('template' =>'<strong>%group%</strong> %options%'), ));

Figura 12.13. Agrupacin de opciones expandida y con seleccin mltiple


Nota

Cuando el widget se muestra con una etiqueta <select> simple, se utiliza la etiqueta <optgroup> estndar.
12.4.3. Opciones soportadas

La siguiente tabla muestra todas las opciones soportadas por el widget:


Opcin
choices multiple expanded renderer_class

Descripcin Array con los valores de las opciones (obligatorio)


true si la etiqueta <select> permite selecciones mltiples true para mostrar el widget de forma expandida

La clase que se utiliza en vez de la clase por defecto

renderer_options Las opciones que se pasan a la clase que muestra el widget

renderer

El widget utilizado para mostrar las opciones (si se utiliza esta opcin, se ignoran las opciones expanded y renderer_options) La opcin choices ser: new sfCallable($thisWidgetInstance,
'getChoices')

Los widgets sfWidgetFormSelectCheckbox y sfWidgetFormSelectRadio soportan las siguientes opciones:


Opcin
label_separator

Descripcin El separador que se utiliza entre el <input> del checkbox/radio button y el ttulo de la opcin El atributo class que se utiliza en la etiqueta <ul> principal El separador que se utiliza entre los <input> de cada checkbox/radio button Cdigo que se ejecuta para mostrar las opciones del checkbox. El cdigo recibe como argumentos el widget y el array de etiquetas <input> La plantilla que se utiliza al agrupar las opciones (puede hacer uso de las variables %group% y %options%)

class separator

formatter

template

Sugerencia

El widget sfWidgetFormChoiceMany es en realidad un atajo del widget sfWidgetFormChoice con la opcin multiple establecida automticamente a true.
12.4.4. Representacin mediante una lista doble

Cuando el usuario puede seleccionar varias opciones, normalmente es mejor mostrar las opciones seleccionadas en otra lista. El widget sfWidgetFormSelectDoubleList permite mostrar la seleccin de opciones en forma de lista doble:
$w = new sfWidgetFormChoice(array( 'choices' =>array('PHP', 'symfony', 'Doctrine', 'Propel', 'model'), 'renderer_class' =>'sfWidgetFormSelectDoubleList', ));

Figura 12.14. Lista doble


Cuidado

Este widget forma parte del plugin sfFormExtraPlugin.


Nota

Este widget requiere el uso de JavaScript para su funcionamiento. Si quieres obtener las rutas de los archivos JavaScript, puedes emplear el mtodo getJavaScripts() del widget:
$rutasArchivos = $w->getJavascripts();

Opcin
choices class class_select

Descripcin Array con los valores de las opciones (obligatorio) El atributo class principal del widget El atributo class que se utiliza en las dos etiquetas <select>

label_unassociated El ttulo de las opciones no seleccionadas label_associated unassociate associate

El ttulo de las opciones seleccionadas El cdigo HTML del enlace para deseleccionar una opcin El cdigo HTML del enlace para seleccionar una opcin El cdigo HTML de la plantilla que se utiliza para mostrar el widget. Las variables disponibles en la plantilla son: %label_associated%, %label_unassociated%, %associate%, %unassociate%, %associated%, %unassociated%, %class%

template

12.4.5. Autocompletado

Cuando el usuario debe seleccionar un valor entre un gran nmero de opciones, crear una lista enorme con todas las opciones no es prctico. El widget sfWidgetFormJQueryAutocompleter soluciona este problema convirtiendo una etiqueta <input> simple en un elemento que autocompleta el texto escrito por el usuario.
Cuidado

Este widget es parte del plugin sfFormExtraPlugin. Como los archivos de las libreras JQuery y JQuery UI no se incluyen en sfFormExtraPlugin, debes instalarlos e incluirlos a mano.
$w = new sfWidgetFormChoice(array( 'choices' =>array(), 'renderer_class' =>'sfWidgetFormJQueryAutocompleter', 'renderer_options' =>array('url' =>'/script_autocompletado'), ));

Nota

Este widget requiere el uso de algunas hojas de estilos y archivos JavaScript para su funcionamiento. Los mtodos getJavaScripts() y getStylesheets() permiten obtener las rutas de todos estos archivos. La opcin url es la URL a la que accede el widget para obtener la lista de sugerencias a partir del texto introducido por el usuario. Esta URL incluye dos parmetros:
y y q: la cadena de texto escrita por el usuario limit: el mximo nmero de sugerencias que debe devolver el script

La respuesta del script debe ser una representacin JSON vlida del array de opciones (puedes utilizar la funcin json_encode() de PHP para convertir un array al formato JSON).
Opcin
url config

Descripcin La URL a la que se accede para obtener las opciones (obligatorio) Array de JavaScript que configura el widget de autocompletado de JQuery

value_callback Cdigo que se ejecuta sobre cada valor antes de mostrarlo

Si la lista de sugerencias est relacionada con un modelo de Propel, es decir, con una tabla de la base de datos, puedes utilizar el widget sfWidgetFormPropelJQueryAutocompleter, que est optimizado para bsquedas a partir de la clave primaria:
$w = new sfWidgetFormChoice(array( 'renderer_class' =>'sfWidgetFormPropelJQueryAutocompleter', 'renderer_options' =>array( 'model' =>'Articulo', 'url' =>'/script_autocompletado', ), ));

Opcin
model La clase del modelo (obligatoria)

Descripcin

method

El mtodo que se utiliza para convertir el objeto en una cadena de texto (__toString() por defecto)

12.4.6. Elecciones asociadas con modelos de Propel

Si las opciones estn relacionadas con un modelo de Propel (por ejemplo cuando el usuario puede cambiar una clave externa) puedes utilizar el widget sfWidgetFormPropelChoice:
$w = new sfWidgetFormPropelChoice(array( 'model' =>'Articulo', 'add_empty' =>false, ));

El widget obtiene automticamente las opciones (choices) a partir de la clase del modelo (model). Este widget se puede configurar mediante las siguientes opciones:
Opcin Descripcin

model

La clase del modelo Propel (obligatoria) Indica si se debe mostrar un primer elemento vaco en la lista (false por defecto) Si el valor no es de tipo booleano, se utiliza como texto de la opcin El mtodo utilizado para mostrar el valor de los objetos (__toString por defecto) El mtodo utilizado para mostrar la clave de los objetos (getPrimaryKey por defecto) Array compuesto por dos opciones: 1) La columna por la que se ordenan los resultados (debe indicarse con el formato PhpName) 2) Criterio de ordenacin (asc o desc) Objeto Criteria que se utiliza para obtener los objetos

add_empty

method

key_method

order_by

criteria

connection El nombre de la conexin Propel que se utiliza (null por defecto) multiple true si la etiqueta <select> permite selecciones mltiples

peer_method El mtodo peer utilizado para obtener los objetos

12.4.7. Elecciones asociadas con modelos de Doctrine

Si las opciones estn relacionadas con un modelo de Doctrine (por ejemplo cuando el usuario puede cambiar una clave externa) puedes utilizar el widget sfWidgetFormDoctrineChoice:
$w = new sfWidgetFormDoctrineChoice(array( 'model' =>'Articulo', 'add_empty' =>false, ));

El widget obtiene automticamente las opciones (choices) a partir de la clase del modelo (model). Este widget se puede configurar mediante las siguientes opciones:
Opcin
model

Descripcin La clase del modelo (obligatoria) Indica si se debe mostrar un primer elemento vaco en la lista (false por defecto) Si el valor no es de tipo booleano, se utiliza como texto de la opcin

add_empty

method

El mtodo utilizado para mostrar el valor de los objetos (__toString por defecto) El mtodo utilizado para mostrar la clave de los objetos (getPrimaryKey por defecto) Array compuesto por dos opciones: 1) La columna por la que se ordenan los resultados (debe indicarse con el formato PhpName) 2) Criterio de ordenacin (asc o desc) Consulta que se utiliza para obtener los objetos El nombre de la conexin Doctrine que se utiliza (null por defecto)
true si la etiqueta <select> permite selecciones mltiples

key_method

order_by

query connection multiple

table_method El mtodo utilizado para obtener los objetos

12.5. Widgets para fechas


Los widgets para fechas se pueden utilizar para facilitar la introduccin de fechas, ya que utilizan varios elementos para introducir slo la fecha, slo la hora o la fecha y hora. Todos los widgets de fechas se representan mediante varias etiquetas HTML. Adems estos widgets se pueden personalizar en funcin de la cultura del usuario.
Nota

Algunos usuarios prefieren una sola etiqueta <input> para introducir fechas porque es mucho ms rpido que utilizar varias listas desplegables de tipo <select>. Cuando se utiliza un solo <input>, el formato de la fecha se comprueba en el servidor durante el proceso de validacin. El validador de fechas del framework Symfony incluye validaciones muy avanzadas que permiten que el usuario introduzca las fechas con formatos muy diferentes.
12.5.1. sfWidgetFormDate
sfWidgetFormDate muestra un widget para seleccionar la fecha:

Figura 12.15. Widget para seleccionar la fecha

Los valores enviados por el usuario se guardan en un array con el mismo nombre que el widget:
$w = new sfWidgetFormDate(); $w->render('fecha'); # # # # # # # # # los valores enviados por el usuario se encuentran en un array llamado "fecha" array( 'fecha' => array( 'day' => 15, 'month' => 10, 'year' => 2005, ), );

El comportamiento del widget se puede personalizar mediante las siguientes opciones:


Opcin
format

Descripcin Cadena de texto con el formato de la fecha (por defecto es %month%/%day%/%year%) Array con los aos que se muestran en la lista para seleccionar un ao (opcional) Array con los meses que se muestran en la lista para seleccionar un mes (opcional) Array con los das que se muestran en la lista para seleccionar un da (opcional)

years

months

days

can_be_empty Indica si el widget admite fechas vacas (true por defecto)

Array con los textos que se utilizan para los valores vacos de las listas
empty_values desplegables

(por defecto se utiliza una cadena de texto vaca para todos los campos)

La opcin format permite modificar la distribucin por defecto de las etiquetas (al mostrar el widget, las variables %year%, %month% y %day% se sustituyen por sus correspondientes etiquetas <select>):
$w = new sfWidgetFormDate( array('format' =>'%year% - %month% - %day%') );

Figura 12.16. Widget personalizado para seleccionar la fecha La etiqueta <select> de los aos (year) muestra por defecto los diez aos alrededor del ao actual. La opcin years permite modificar este comportamiento:
$years = range(2009, 2020); $w = new sfWidgetFormDate( array('years' => array_combine($years, $years)) );

Las opciones years, months y days toman como argumento un array cuyas claves son los valores de las etiquetas <option> y cuyos valores son las cadenas de texto que se muestran al usuario.
12.5.2. sfWidgetFormTime
sfWidgetFormTime muestra un widget para seleccionar una hora:

Figura 12.17. Widget para seleccionar la hora Los valores enviados por el usuario se guardan en un array con el mismo nombre que el widget:
$w = new sfWidgetFormTime(); $w->render('hora'); # # # # # # # # # los valores enviados por el usuario se encuentran en un array llamado "hora" array( 'hora' => array( 'hour' => 12, 'minute' => 13, 'second' => 14, ), );

El comportamiento del widget se puede personalizar mediante las siguientes opciones:

Opcin
format

Descripcin Cadena de texto con el formato de la hora (por defecto es %hour%:%minute%:%second%) Cadena de texto con el formato de la hora sin segundos (por defecto es %hour%:%minute%) Indica si se muestra una lista para seleccionar los segundos (false por defecto ) Array con las horas que se muestran en la lista para seleccionar una hora (opcional) Array con los minutos que se muestran en la lista para seleccionar un minuto (opcional) Array con los segundos que se muestran en la lista para seleccionar los segundos (opcional) Indica si el widget admite horas vacas (true por defecto) Array con los textos que se utilizan para los valores vacos de las listas desplegables (por defecto se utiliza una cadena de texto vaca para todos los campos)

format_without_seconds

with_seconds

hours

minutes

seconds

can_be_empty

empty_values

Por defecto este widget no permite elegir los segundos de la hora, pero este comportamiento se puede modificar estableciendo la opcin with_seconds a true:
$w = new sfWidgetFormTime(array('with_seconds' =>true));

Las opciones format y format_without_seconds permiten modificar la distribucin por defecto de las etiquetas (al mostrar el widget, las variables %hour%, %minute% y %second% se sustituyen por sus correspondientes etiquetas <select>):
$w = new sfWidgetFormTime(array( 'with_seconds' =>true, 'format' =>'%hour% : %minute% : %second%', ));

Figura 12.18. Widget personalizado para seleccionar la hora

Si no quieres que el usuario pueda elegir cualquier valor de las listas desplegables, puedes indicar los valores seleccionables para cada lista:
$segundos = array(0, 15, 30, 45); $w = new sfWidgetFormTime(array( 'with_seconds' =>true, 'seconds' => array_combine($segundos, $segundos), ));

Figura 12.19. Widget para seleccionar la hora con los segundos personalizados Las opciones hours, minutes y seconds toman como argumento un array cuyas claves son los valores de las etiquetas <option> y cuyos valores son las cadenas de texto que se muestran al usuario.
12.5.3. sfWidgetFormDateTime
sfWidgetFormDateTime es un widget especial que muestra dos sub-widgets: un widget de tipo sfWidgetFormDate y otro widget de tipo sfWidgetFormTime: $w = new sfWidgetFormDateTime();

Figura 12.20. Widget para seleccionar la fecha y la hora


Opcin
date time

Descripcin Opciones para el widget de la fecha (ver opciones de sfWidgetFormDate) Opciones para el widget de la hora (ver opciones de sfWidgetFormTime)

with_time Indica si se muestra el widget de la hora (true por defecto) format

Cadena de texto con el formato de la fecha y hora (por defecto es %date% %time%)

Sugerencia

Este widget crea por defecto instancias de sfWidgetFormDate y sfWidgetFormTime para mostrar la fecha y la hora. Si quieres cambiar las clases utilizadas por el widget, puedes redefinir los mtodos getDateWidget() y getTimeWidget().
12.5.4. sfWidgetFormI18nDate
sfWidgetFormI18nDate extiende el widget sfWidgetFormDate. Mientras que el widget

estndar muestra los meses como nmeros, este widget muestra los meses como cadenas de texto y traducidas en funcin de la cultura del usuario:
$w = new sfWidgetFormI18nDate(array('culture' =>'fr'));

Figura 12.21. Widget internacionalizado para seleccionar la fecha El formato de los meses se puede configurar mediante la opcin month_format, que acepta los siguientes tres valores: name (valor por defecto, que muestra el nombre completo del mes), short_name (abreviatura del nombre del mes) o number (nmero del mes).
$w = new sfWidgetFormI18nDate(array( 'culture' =>'fr', 'month_format' =>'short_name', ));

Figura 12.22. Widget internacionalizado para seleccionar la fecha con los meses abreviados El widget tambin tiene en cuenta la cultura del usuario en el orden en el que se muestran las tres listas desplegables y en el separador utilizado entre ellas.
Cuidado

Este widget depende del sub-framework de internacionalizacin de Symfony.


12.5.5. sfWidgetFormI18nTime
sfWidgetFormI18nTime extiende el widget sfWidgetFormTime estndar. En funcin de la cultura que se le pasa como opcin culture, el widget determina el orden en el que se muestran las tres listas desplegables y el separador utilizado entre ellas. $w = new sfWidgetFormI18nTime(array('culture' =>'ar'));

Figura 12.23. Widget internacionalizado para seleccionar la hora


Cuidado

Este widget depende del sub-framework de internacionalizacin de Symfony.


12.5.6. sfWidgetFormI18nDateTime
sfWidgetFormI18nDateTime es un widget especial que muestra dos sub-widgets: un widget de tipo sfWidgetFormI18nDate y otro widget de tipo sfWidgetFormI18nTime.

Cuidado

Este widget depende del sub-framework de internacionalizacin de Symfony.


12.5.7. sfWidgetFormDataRange
sfWidgetFormDateRange es un widget que permite seleccionar un rango de fechas: $w = new sfWidgetFormDateRange(array( 'from_date' =>new sfWidgetFormDate(), 'to_date' =>new sfWidgetFormDate(), ));

Figura 12.24. Widget para seleccionar un rango de fechas

Opcin

Descripcin

from_date El widget para la fecha inicial (obligatorio) to_date

El widget para la fecha final (obligatorio) La plantilla que se utiliza para mostrar el widget (la plantilla puede hacer uso de las variables %from_date% y %to_date%

template

La opcin template se puede emplear para modificar la plantilla que utiliza el wdiget para mostrar sus contenidos:
$w = new sfWidgetFormDateRange(array( 'from_date' =>new sfWidgetFormDate(), 'to_date' =>new sfWidgetFormDate(), 'template' =>'Begin at: %from_date%<br />End at: %to_date%', ));

Figura 12.25. Widget personalizado para seleccionar un rango de fechas


Nota

Este widget es la clase base de un widget ms avanzado llamado sfWidgetFormFilterDate.


12.5.8. sfWidgetFormJQueryDate
sfWidgetFormJQueryDate utiliza la librera JQuery UI para mostrar un widget que permite

seleccionar una fecha:


$w = new sfWidgetFormJQueryDate(array( 'culture' =>'en', ));

Cuidado

Este widget es parte del plugin sfFormExtraPlugin. Como los archivos de las libreras JQuery y JQuery UI no se incluyen en sfFormExtraPlugin, debes instalarlos e incluirlos a mano.
Opcin Descripcin

image

La ruta de la imagen que representa el widget (false por defecto)

config Array de JavaScript con la configuracin del widget de JQuery culture La cultura del usuario

12.6. Widgets de internacionalizacin


Cuidado

Los widgets de esta seccin dependen del sub-framework de internacionalizacin de Symfony.


12.6.1. sfWidgetFormI18nSelectCountry
sfWidgetFormI18nSelectCountry muestra una lista para seleccionar un pas: $w = new sfWidgetFormI18nSelectCountry(array('culture' =>'fr'));

Figura 12.26. Widget internacionalizado para seleccionar un pas


Opcin
culture countries

Descripcin La cultura que se utiliza para mostrar el nombre de los pases (obligatorio) Array con los cdigos de los pases que se deben mostrar en la lista (los cdigos se

corresponden con el estndar ISO 3166)


add_empty

Indica si se debe mostrar un primer elemento vaco en la lista (false por defecto) Si el valor no es de tipo booleano, se utiliza como texto de la opcin

12.6.2. sfWidgetFormI18nSelectLanguage
sfWidgetFormI18nSelectLanguage muestra una lista para seleccionar un idioma: $w = new sfWidgetFormI18nSelectLanguage(array('culture' =>'fr'));

Figura 12.27. Widget internacionalizado para seleccionar un idioma


Opcin
culture

Descripcin La cultura que se utiliza para mostrar el nombre de los idiomas (obligatorio) Array con los cdigos de los idiomas que se deben mostrar en la lista (los cdigos se corresponden con el estndar ISO 639-1) Indica si se debe mostrar un primer elemento vaco en la lista (false por defecto) Si el valor no es de tipo booleano, se utiliza como texto de la opcin

languages

add_empty

12.6.3. sfWidgetFormI18nSelectCurrency
sfWidgetFormI18nSelectCurrency muestra una lista para seleccionar una divisa:

$w = new sfWidgetFormI18nSelectCurrency(array('culture' =>'fr'));

Figura 12.28. Widget internacionalizado para seleccionar una divisa


Opcin
culture

Descripcin La cultura que se utiliza para mostrar el nombre de las divisas (obligatorio)

currencies Array con los cdigos de las divisas que se deben mostrar en la lista

add_empty

Indica si se debe mostrar un primer elemento vaco en la lista (false por defecto) Si el valor no es de tipo booleano, se utiliza como texto de la opcin

12.7. Widget para CAPTCHA


El plugin sfFormExtraPlugin incluye un widget para mostrar CAPTCHA llamado sfWidgetFormReCaptcha y basado en el proyecto ReCaptcha:
$w = new sfWidgetFormReCaptcha(array( 'public_key' =>'CLAVE_PUBLICA_DE_RECAPTCHA' ));

Opcin
public_key use_ssl

Descripcin La clave pblica de ReCaptcha Indica si se utiliza SSL en la conexin (false por defecto)

server_url

La URL de la APIHTTP

server_url_ssl La URL de la API HTTPS (slo se utiliza si la opcin use_ssl es true)

La opcin public_key es la clave pblica de ReCaptcha, que puedes solicitar gratuitamente desde la pgina para solicitar claves de la API.
Sugerencia

Si necesitas ayuda sobre este servicio, puedes consultar la documentacin de la API de ReCaptcha. Como no se puede cambiar el nombre de los campos de ReCaptcha, tienes que aadirlos manualmente al asociar un formulario enviado mediante una peticin HTTP. Si por ejemplo el nombre de los campos de tu formulario sigue el formato contacto[%s], este es el cdigo que necesitas para asegurar que la informacin del CAPTCHA se aade al resto de valores enviados mediante el formulario:
$captcha = array( 'recaptcha_challenge_field' =>$request>getParameter('recaptcha_challenge_field'), 'recaptcha_response_field' =>$request>getParameter('recaptcha_response_field'), ); $valoresEnviados = array_merge( $request->getParameter('contacto'), array('captcha' =>$captcha) );

Este widget hace uso del validador sfValidatorReCatpcha.

12.8. Widgets para filtros


Los widgets para filtros son un tipo de especial de widget que se pueden utilizar para mostrar un formulario como si fuera un filtro.
12.8.1. sfWidgetFormFilterInput
sfWidgetFormFilterInput muestra un filtro para texto. Por defecto incluye un checkbox

que permite al usuario buscar textos vacos.


Opcin Descripcin

with_empty Indica si se aade el checkbox para buscar textos vacos (true por defecto)

empty_label El ttulo del checkbox que permite buscar textos vacos

template

La plantilla que se utiliza para mostrar el widget (las variables que puede utilizar la plantilla son %input%, %empty_checkbox% y %empty_label%)

12.8.2. sfWidgetFormFilterDate
sfWidgetFormFilterDate muestra un filtro para seleccionar un rango de fechas. Por

defecto incluye un checkbox que permite al usuario buscar fechas vacas.


Opcin Descripcin

with_empty Indica si se aade el checkbox para buscar fechas vacas (true por defecto) empty_label El ttulo del checkbox que permite buscar fechas vacas

template

La plantilla que se utiliza para mostrar el widget (las variables que puede utilizar la plantilla son %date_range%, %empty_checkbox% y %empty_label%)

12.9. sfWidgetFormSchema
sfWidgetFormSchema es un tipo de widget especial compuesto por varios campos de

formulario. Un campo es simplemente un widget asociado a un nombre:


$w = new sfWidgetFormSchema(array( 'nombre' =>new sfWidgetFormInput(), 'pais' =>new sfWidgetFormI18nSelectCountry(), ));

Nota

Los formularios se definen mediante un esquema de widgets de tipo sfWidgetFormSchema. El constructor de sfWidgetFormSchema acepta cinco argumentos opcionales:
y y y y y

Un array de campos de formulario Un array de opciones Un array de atributos HTML Un array de ttulos para los widgets del esquema Un array de mensajes de ayuda para los widgets del esquema

Las opciones disponibles son las siguientes:

Opcin
name_format

Descripcin El formato que siguen los nombres de los campos (se debe utilizar la notacin de sprintf y su valor por defecto es %s) El nombre del formato del formulario (Symfony incluye los formatos table y
list, siendo table el que se utiliza por defecto)

form_formatter

Si quieres modificar el formato por defecto de todos los formularios, puedes utilizar el mtodo setDefaultFormFormatterName():
sfWidgetFormSchema::setDefaultFormFormatterName('list');

Como sfWidgetFormSchema extiende la clase sfWidgetForm, hereda todos sus mtodos y caractersticas.
Cuidado

Los objetos sfWidgetFormSchema solamente muestran las filas que contienen los widgets y no la etiqueta que encierra a todos ellos (<table> en el formato de tablas y <ul> en el formato de listas):
<table> <?phpecho$ws->render('')?> </table>

Se puede emplear sfWidgetFormSchema como si fuera un array para acceder a todos los widgets que incluye:
$ws = new sfWidgetFormSchema(array('nombre' =>new sfWidgetFormInput())); $widgetNombre = $ws['nombre']; unset($ws['nombre']);

Cuidado

Cuando un formulario incluye un esquema de widgets, el formulario te da acceso al campo asociado de la plantilla, pero no al propio widget, tal y como se explica en captulos anteriores. Los esquemas de widgets tambin se pueden anidar como cualquier otro tipo de widget:
$ws = new sfWidgetFormSchema(array( 'titulo' =>new sfWidgetFormInput(), 'autor' =>new sfWidgetFormSchema(array( 'nombre' =>new sfWidgetFormInput(), 'apellidos' =>new sfWidgetFormInput(),

)), ));

Para acceder a los esquemas de widgets anidados, se puede utilizar la notacin de los arrays:
$ws['autor']['nombre']->setLabel('Nombre');

A continuacin se describen los principales mtodos de las clases de los esquemas de widgets. La documentacin de la API de Symfony dispone de la lista completa de mtodos y todas sus caractersticas.
12.9.1. Mtodos setLabel(), getLabel(), setLabels() y getLabels()

Los mtodos setLabel(), getLabel(), setLabels() y getLabels() controlan los ttulos de los widgets incluidos en el esquema. En realidad, estos mtodos son atajos de los mtodos getLabel() y setLabel() de los widgets.
$ws = new sfWidgetFormSchema(array('nombre' =>new sfWidgetFormInput())); $ws->setLabel('nombre', 'Fabien'); // que es equivalente a... $ws['nombre']->setLabel('Fabien'); // y tambin es equivalente a... $ws->setLabel(array('nombre' =>'Fabien'));

El mtodo setLabels() fusiona los valores indicados con los valores existentes.
12.9.2. Mtodos setDefault(), getDefault(), setDefaults() y getDefaults()

Los mtodos setDefault(), getDefault(), setDefaults() y getDefaults() controlan los valores por defecto de los widgets incluidos en el esquema. Estos mtodos son atajos de los mtodos getDefault() y setDefault() de los widgets.
$ws = new sfWidgetFormSchema(array('nombre' =>new sfWidgetFormInput())); $ws->setDefault('nombre', 'Fabien'); // que es equivalente a... $ws['nombre']->setDefault('Fabien'); // y tambin es equivalente a... $ws->setDefaults(array('nombre' =>'Fabien'));

El mtodo setDefaults() fusiona los valores indicados con los valores existentes.

12.9.3. Mtodos setHelp(), setHelps(), getHelps() y getHelp()

Los mtodos setHelp(), setHelps(), getHelps() y getHelp() controlan los mensajes de ayuda asociados con los widgets incluidos en el esquema:
$ws = new sfWidgetFormSchema(array('nombre' =>new sfWidgetFormInput())); $ws->setHelp('nombre', 'Fabien'); // que es equivalente a... $ws->setHelps(array('nombre' =>'Fabien'));

El mtodo setHelps() fusiona los valores indicados con los valores existentes.
12.9.4. Mtodos getPositions(), setPositions() y moveField()

Los campos que se incluyen en un esquema de widgets se encuentran ordenados. El orden se puede modificar con el mtodo moveField():
$ws = new sfWidgetFormSchema(array( 'nombre' =>new sfWidgetFormInput(), 'apellidos' =>new sfWidgetFormInput() )); $ws->moveField('nombre', sfWidgetFormSchema::AFTER, 'apellidos');

Las constantes definidas para mover los widgets son las siguientes:
y y y y sfWidgetFormSchema::FIRST sfWidgetFormSchema::LAST sfWidgetFormSchema::BEFORE sfWidgetFormSchema::AFTER

Tambin se puede utilizar el mtodo setPositions() para modificar todas las posiciones:
$ws->setPositions(array('apellidos', 'nombre'));

12.9.5. sfWidgetFormSchemaDecorator
sfWidgetFormSchemaDecorator es un tipo especial de esquema de widgets que permite

decorar un esquema de widgets con el cdigo HTML indicado.


$ws = new sfWidgetFormSchema(array('nombre' =>new sfWidgetFormInput())); $wd = new sfWidgetFormSchemaDecorator($ws, '<table>%content%</table>');

Nota

Este widget lo utiliza Symfony internamente cuando un formulario se incluye dentro de otro formulario.

Captulo 13. Validadores 13.1. Introduccin


El framework de formularios de Symfony incluye muchos validadores tiles que cubren las necesidades comunes de la mayora de proyectos. En este captulo se describen detalladamente todos los validadores que incluye por defecto Symfony. Tambin se explican los validadores incluidos en los plugins sfPropelPlugin y sfDoctrinePlugin, ya que estos plugins los desarrollan los creadores de Symfony e incluyen muchos validadores tiles.
Sugerencia

Aunque no utilices el framework Symfony, puedes hacer uso de los validadores incluidos en sfFormExtraPlugin, sfPropelPlugin y sfDoctrinePlugin simplemente copiando el directorio validator/ de cada plugin en algn lugar de tu proyecto. Antes de profundizar en los detalles de cada validador, veamos las caractersticas comunes de todos los validadores.
13.1.1. La clase base sfValidatorBase

Todos los validadores de Symfony heredan de la clase base sfValidator, que proporciona las caractersticas comunes de todos los validadores. La finalidad de los validadores consiste en limpiar y validar los valores originales. Cuando se crea un validador, se le pueden pasar como argumentos opcionales diferentes opciones y mensajes de error:
$v = new sfValidatorString( array('required' =>true), array('required' =>'Este valor es obligatorio.') );

Las opciones y los mensajes de error tambin se pueden establecer con los mtodos setOptions() y setMessages():
$v = new sfValidatorString(); $v->setOptions(array('required' =>true)); $v->setMessages(array('required' =>'Este valor es obligatorio.'));

Si se quiere establecer una opcin o mensaje de error individual, se pueden utilizar los mtodos setOption() y setMessage():
$v = new sfValidatorString(); $v->setOption('required', true);

$v->setMessage('required', 'Este valor es obligatorio.');

Los valores originales se pueden validar con el mtodo clean():


$valorLimpio = $v->clean('nombre', 'valor', array('class' =>'foo'));

El mtodo clean() toma como argumento el valor original y devuelve el valor limpio. Si se produce un error de valiacin, se lanza una excepcin de tipo sfValidatorError.
Nota

Los validadores no almacenan informacin sobre su estado, lo que significa que una sola instancia de un validador puede validar tantos valores como necesites.
sfValidatorBase define las siguientes opciones por defecto:

Opcin
required

Error
required

Descripcin Vale true si el valor es obligatorio y false en cualquier otro caso (su valor por defecto es true) Vale true si se deben eliminar los espacios en blanco del principio y del final del valor y false en cualquier otro caso (su valor por defecto es false) Valor vaco que se devuelve cuando el valor no es obligatorio

trim

empty_value -

sfValidatorBase tambin define los siguientes mensajes de error por defecto:

Error
required

Descripcin Mensaje de error que se muestra cuando el valor original est vaco pero es obligatorio (por defecto el mensaje que se muestra es Required) Mensaje de error genrico que se muestra cuando se produce un error (por defecto el mensaje que se muestra es Invalid)

invalid

Para modificar los mensajes por defecto de los errores required y invalid, puedes utilizar los mtodos setRequiredMessage() y setInvalidMessage():
sfValidatorBase::setRequiredMessage('Este valor es obligatorio.'); sfValidatorBase::setInvalidMessage('Este valor no es vlido.');

Los mensajes de error pueden contener variables en forma de cadenas de texto encerradas por %. Las variables se sustituyen por sus valores durante la ejecucin de la aplicacin. Todos los mensajes de error tienen acceso directo al valor original mediante una variable llamada %value%. Adems, los mensajes de error pueden definir sus propias variables.
Nota

En la siguiente seccin no se menciona la variable %value% porque siempre est disponible. Algunos validadores necesitan conocer la codificacin que utiliza el valor original. Por defecto la codificacin utilizada es UTF-8, pero se puede configurar cualquier otra codificacin mediante el mtodo setCharset():
sfValidatorBase::setCharset('ISO-8859-1');

Nota

Si utilizas los validadores de Symfony dentro de un proyecto Symfony, la codificacin se obtiene directamente del archivo de configuracin settings.yml.
13.1.2. Esquema de validadores

Un esquema de validadores es un validador especial que agrupa uno o varios validadores. Si se produce un error, el esquema de validadores lanza una excepcin de tipo sfValidatorErrorSchema.

13.2. Validadores
A continuacin se muestra el listado completo de todos los validadores de Symfony:
y y y y y y y y y y y y y y y y y sfValidatorString sfValidatorRegex sfValidatorEmail sfValidatorUrl sfValidatorInteger sfValidatorNumber sfValidatorBoolean sfValidatorChoice sfValidatorPass sfValidatorCallback sfValidatorDate sfValidatorTime sfValidatorDateTime sfValidatorDateRange sfValidatorFile sfValidatorAnd sfValidatorOr

y y y y y y y y y y y

sfValidatorSchema sfValidatorSchemaCompare sfValidatorSchemaFilter sfValidatorI18nChoiceCountry sfValidatorI18nChoiceLanguage sfValidatorPropelChoice sfValidatorPropelChoiceMany sfValidatorPropelUnique sfValidatorDoctrineChoice sfValidatorDoctrineChoiceMany sfValidatorDoctrineUnique

13.3. Validadores simples


13.3.1. sfValidatorString
sfValidatorString valida una cadena de texto y convierte el valor original en una cadena

de texto.
Opcin Error Descripcin

max_length max_length La longitud mxima de la cadena de texto min_length min_length La longitud mnima de la cadena de texto

Error

Variables

Valor por defecto (se muestra en ingls)

max_length max_length "%value%" is too long (%max_length% characters max). min_length min_length "%value%" is too short (%min_length% characters min).

Cuidado

Este validador requiere la extensin mb_string de PHP para funcionar correctamente. Si se encuentra instalada, la longitud de la cadena de texto se calcula con la funcin mb_strlen(). Si no se encuentra instalada, se emplea la funcin strlen(), que no calcula correctamente la longitud de las cadenas de texto que utilizan caracteres que no sean ASCII.
13.3.2. sfValidatorRegex
sfValidatorRegex valida una cadena de texto en funcin de la expresin regular indicada.

Opcin

Error

Descripcin

pattern invalid Una expresin regular que siga el formato de PCRE

13.3.3. sfValidatorEmail
sfValidatorEmail valida que el valor indicado tenga el formato correcto de una direccin de email. Este validador hereda de sfValidatorRegex.

13.3.4. sfValidatorUrl
sfValidatorEmail valida que el valor indicado tenga el formato correcto de una URL de HTTP o FTP. Este validador hereda de sfValidatorRegex.

13.3.5. sfValidatorInteger
sfValidatorInteger valida un nmero entero y convierte el valor original en un nmero

entero.
Opcin Error
max min

Descripcin

max El nmero entero ms grande que se acepta como valor min El nmero entero ms pequeo que se acepta como valor

Error Variables Valor por defecto (se muestra en ingls)


max max min min

"%value%" must be less than %max%. "%value%" must be greater than %min%.

El mensaje por defecto para el error de tipo invalid es "%value%" is not an integer.
13.3.6. sfValidatorNumber
sfValidatorNumber valida un nmero y convierte el valor original en un nmero.

Cualquier cadena de texto que PHP sea capaz de convertir mediante la funcin floatval se considera un nmero.
Opcin Error
max min

Descripcin

max El nmero ms grande que se acepta como valor min El nmero ms pequeo que se acepta como valor

Error Variables Valor por defecto (se muestra en ingls)


max max min min

"%value%" must be less than %max%. "%value%" must be greater than %min%.

El mensaje por defecto para el error de tipo invalid es "%value%" is not a number..
13.3.7. sfValidatorBoolean
sfValidatorBoolean valida un valor booleano y devuelve true o false.

Opcin

Error

Descripcin La lista de valores booleanos verdaderos (por defecto son true, t, yes, y, on, 1) La lista de valores booleanos falsos (por defecto son false, f, no, n, off, 0)

true_values -

false_values -

13.3.8. sfValidatorChoice
sfValidatorChoice valida que el valor original pertenezca a una la lista de valores

esperados.
Opcin Error
choices multiple -

Descripcin Array con los valores esperados (esta opcin es obligatoria)


true si la etiqueta <select> debe permitir selecciones mltiples

Nota

La comparacin se realiza despus de convertir el valor original en una cadena de texto.


13.3.9. sfValidatorPass
sfValidatorPass es un validador especial que no realiza ninguna validacin y que

simplemente devuelve el valor original intacto.

13.3.10. sfValidatorCallback
sfValidatorCallback permite delegar la validacin del valor original al cdigo PHP

ejecutable indicado, tambin llamado "callback". Al cdigo PHP ejecutable se le pasan como argumentos la instancia del validador actual, el valor original y un array de opciones (obtenido mediante la opcin arguments):
function callback_validador_constante($validador, $valorOriginal, $argumentos) { if($valorOriginal != $argumentos['constante']) { throw new sfValidatorError($validador, 'invalid'); } return$valorOriginal; } $v = new sfValidatorCallback(array( 'callback' =>'callback_validador_constante', 'arguments' =>array('constante' =>'valor'), ));

Opcin

Error

Descripcin Un callback vlido de PHP (obligatorio) Array con las opciones que se le pasan al callback

callback arguments -

13.4. Validadores de fechas


13.4.1. sfValidatorDate
sfValidatorDate valida fechas y fechas + horas (para utilizar fechas + horas, se debe activar la opcin with_time). Adems de validar el formato de una fecha, puede forzar a

que la fecha sea anterior o posterior a una fecha indicada. Este validador acepta como valor original diferentes tipos de variables:
y y y y

Array con las siguientes claves: year, month, day, hour, minute y second Cadena de texto que cumpla opcionalmente con la expresin regular indicada en la opcin
date_format

Cadena de texto que se pueda procesar por la funcin strtotime() de PHP Nmero entero que representa un timestamp

El valor original se convierte en una fecha aplicando el formato date_output o


datetime_output

Opcin
date_format

Error

Descripcin

bad_format Expresin regular que deben cumplir las fechas true si el validador debe devolver tambin la hora, false en cualquier otro caso

with_time

date_output

El formato que se utiliza al devolver slo una fecha (por defecto es Y-m-d) El formato que se utiliza al devolver una fecha y una hora (por defecto es Y-m-d H:i:s)

datetime_output

max

date_format_error

max

La fecha mxima permitida (se indica como timestamp) La fecha mnima permitida (se indica como timestamp) El formato de fecha que se utiliza al mostrar un error de tipo max o min (por defecto es d/m/Y H:i:s)

min

min

date_format_range_error -

Nota

Las opciones date_output y datetime_output pueden emplear cualquier formato que entienda la funcin date() de PHP.
Error Variables Valor por defecto (se muestra en ingls)

bad_format date_format "%value%" does not match the date format (%date_format%). min max min max

The date must be after %min%. The date must be before %max%.

13.4.2. sfValidatorTime
sfValidatorTime valida que el valor original sea una hora.

Este validador acepta como valor original diferentes tipos de variables:


y

Array con las siguientes claves: hour, minute y second

y y y

Cadena de texto que cumpla opcionalmente con la expresin regular indicada en la opcin
time_format

Cadena de texto que se pueda procesar por la funcin strtotime() de PHP Nmero entero que representa un timestamp

El valor original se convierte en una hora aplicando el formato date_output o


datetime_output

Opcin
time_format

Error

Descripcin

bad_format Expresin regular que deben cumplir las horas

time_output

El formato que se utiliza al devolver una fecha con la hora (por defecto es H:i:s) El formato de fecha que se utiliza al mostrar un error de tipo bad_format. Si no se indica, se utiliza el formato
date_format

time_format_error -

Nota

La opcin time_output puede emplear cualquier formato que entienda la funcin date() de PHP.
Error Variables Valor por defecto (se muestra en ingls)

bad_format date_format "%value%" does not match the time format (%time_format%).

13.4.3. sfValidatorDateTime
sfValidatorDateTime valida las fechas que incluyen la hora. En realidad, este validador

es un atajo del siguiente cdigo:


$v = new sfValidatorDate(array('with_time' =>true));

13.4.4. sfValidatorDateRange
sfValidatorDateTime valida un rango de fechas.

Opcin

Error

Descripcin

from_date invalid El validador de la fecha inicial (obligatorio) to_date invalid El validador de la fecha final (obligatorio)

Los validadores de from_date y to_date deben ser instancias de la clase sfValidatorDate. El mensaje por defecto del error de tipo invalid es "%value%" does not match the
time format (%time_format%).

13.5. Validador de archivos


13.5.1. sfValidatorFile
sfValidatorFile valida los archivos subidos por los usuarios. Adems, el validador convierte el archivo subido en una instancia de la clase sfValidatedFile o de la clase que se indique en la opcin validated_file_class.

Opcin
max_size

Error
max_size

Descripcin El tamao mximo permitido para los archivos subidos

mime_types

Array con los tipos MIME permitidos o el nombre de la mime_types categora de tipos MIME (la nica categora disponible es web_images) Array con los ejecutables de PHP encargados de adivinar el tipo MIME de los archivos (deben devolver o el tipo MIME o null) Array de categoras de tipos MIME (la categora web_images est definida por defecto) La ruta en la que se guarda el archivo y que utiliza la clase sfValidatedFile (opcional) Nombre de la clase que gestiona el archivo subido y validado (opcional)

mime_type_guessers

mime_categories

path

validated_file_class -

La categora web_images incluye los siguientes tipos MIME:


y y y y y image/jpeg image/pjpeg image/png image/x-png image/gif

Si la opcin mime_types est activada, el validador necesita comprobar el tipo MIME del archivo subido. Por este motivo el validador ya incluye tres comprobadores de tipos MIME:
y y y guessFromFileinfo: utiliza la funcin finfo_open() (de la extensin Fileinfo de

PECL)
guessFromMimeContentType: utiliza la funcin mime_content_type() (obsoleto) guessFromFileBinary: utiliza el contenido del propio archivo (slo funciona en los

sistemas *nix) Error


max_size

Variables
%size%, %max_size%

Valor por defecto (se muestra en ingls) File is too large (maximum is %max_size% bytes).

mime_types %mime_types%, %mime_type% Invalid mime type (%mime_type%). partial no_tmp_dir cant_write extension

The uploaded file was only partially uploaded. Missing a temporary folder. Failed to write file to disk. File upload stopped by extension.

El validador asocia los errores de PHP de la siguiente manera:


y y y y y y UPLOAD_ERR_INI_SIZE: max_size UPLOAD_ERR_FORM_SIZE: max_size UPLOAD_ERR_PARTIAL: partial UPLOAD_ERR_NO_TMP_DIR: no_tmp_dir UPLOAD_ERR_CANT_WRITE: cant_write UPLOAD_ERR_EXTENSION: extension

13.6. Validadores lgicos


13.6.1. sfValidatorAnd
sfValidatorAnd considera vlido el valor original solamente si pasa correctamente una

lista de validadores. El constructor de sfValidatorAnd toma como primer argumento una lista de validadores:
$v = new sfValidatorAnd( array( new sfValidatorString(array('max' =>255)),

new sfValidatorEmail(), ), array('halt_on_error' =>true), array('invalid' =>'El valor indicado debe ser un email de menos de 255 caracteres de longitud.') );

Por defecto el validador guarda en un array todos los mensajes de error lanzados por los validadores incluidos. Tambin puede mostrar un nico mensaje de error si se indica una cadena de texto para el error de tipo invalid, tal y como se muestra en el ejemplo anterior.
Opcin Error Descripcin Indica si la validacin se debe detener despus del primer error o si debe continuar (por defecto vale false)

halt_on_error -

El orden de los validadores es muy importante cuando se establece a true la opcin halt_on_error. La lista de validadores incluidos tambin se puede gestionar con los mtodos getValidators() y addValidator().
13.6.2. sfValidatorOr
sfValidatorOr considera vlido el valor original si pasa correctamente al menos uno de

los validadores indicados. El constructor de sfValidatorOr toma como primer argumento una lista de validadores:
$v = new sfValidatorOr( array( new sfValidatorRegex(array('pattern' =>'/\.com$/')), new sfValidatorEmail(), ), array(), array('invalid' =>'El valor indicado debe ser o un dominio .com o una direccin de email.') );

Por defecto el validador guarda en un array todos los mensajes de error lanzados por los validadores incluidos. Tambin puede mostrar un nico mensaje de error si se indica una cadena de texto para el error de tipo invalid, tal y como se muestra en el ejemplo anterior. La lista de validadores incluidos tambin se puede gestionar con los mtodos getValidators() y addValidator().

13.6.3. sfValidatorSchema

*Schema validator*: Yes


sfValidatorSchema consiste en un validador compuesto por varios campos. Cada campo

se forma mediante un nombre y un validador:


$v = new sfValidatorSchema(array( 'nombre' =>new sfValidatorString(), 'pais' =>new sfValidatorI18nChoiceCountry(), ));

Nota

Los formularios se definen mediante un esquema de validadores de la clase sfValidatorSchema. Este validador slo acepta como argumento un array, por lo que lanza una excepcin de tipo InvalidArgumentException en cualquier otro caso. Adems, a este tipo de validador tambin se le puede asignar un pre-validador (que se ejecuta antes que cualquier otro validador) y un post-validador (que se ejecuta sobre los valores limpios despus de todos los dems validadores). Tanto el pre-validador como el post-validador, son esquemas de validadores a los que se les pasan todos los valores. Para establecer este tipo de validadores, se emplean los mtodos setPreValidator() y setPostValidator():
$v->setPostValidator( new sfValidatorCompare('password1', '==', 'password2') );

Opcin

Error

Descripcin

Si vale false, el validador muestra un error cuando el allow_extra_fields extra_fields usuario enva ms campos que los que tena el formulario original (por defecto vale false) Si vale true, el validador elimina los campos adicionales del array de valores limpios (por defecto vale true) Valor por defecto (se muestra en ingls)

filter_extra_fields -

Error

Variables

extra_fields %field% Unexpected extra form field named "%field%". post_max_size

The form submission cannot be processed. It probably means that you

have uploaded a file that is too big.

sfValidatorSchema permite acceder a sus validadores utilizando la notacin de los arrays: $vs = new sfValidatorSchema(array('nombre' =>new sfValidatorString())); $validadorNombre = $vs['nombre']; unset($vs['nombre']);

La notacin de los arrays tambin se puede emplear para acceder a los esquemas de validadores anidados:
$vs['autor']['nombre']->setMessage('invalid', 'El nombre no es vlido.');

Si el tamao de los datos enviados en el formulario excede del valor de la opcin post_max_size del archivo de configuracin php.ini, se lanza un error de tipo post_max_size.
13.6.4. sfValidatorSchemaCompare
sfValidatorSchemaCompare permite comparar dos de los valores incluidos en el array de

valores originales:
$v = new sfValidatorCompare('password1', '==', 'password2');

Opcin
left_field operator right_field

Error -

Descripcin El nombre del primer campo que se compara El operador utilizado en la comparacin El nombre del segundo campo que se compara Indica si se lanza un error global (por defecto vale false) o si se lanza un error asociado con el primer campo que se compara

throw_global_error -

Los operadores disponibles para realizar comparaciones son los siguientes:


y y y y y y sfValidatorSchemaCompare::EQUAL o tambin == sfValidatorSchemaCompare::NOT_EQUAL o tambin != sfValidatorSchemaCompare::LESS_THAN o tambin < sfValidatorSchemaCompare::LESS_THAN_EQUAL o tambin <= sfValidatorSchemaCompare::GREATER_THAN o tambin > sfValidatorSchemaCompare::GREATER_THAN_EQUAL o tambin >=

Por defecto, el validador lanza un error de tipo global. Sin embargo, si la opcin throw_global_error vale true, se lanza un error relacionado con el primer campo comparado. El mensaje del error de tipo invalid puede utilizar las siguientes variables: %left_field%, %right_field% y %operator%.
13.6.5. sfValidatorSchemaFilter
sfValidatorSchemaFilter convierte un validador normal en un esquema de validadores.

En ocasiones es til en el post-validador:


$v = new sfValidatorSchema(); $v->setPostValidator( new sfValidatorSchemaFilter('email', new sfValidatorEmail()) );

13.7. Validadores de internacionalizacin


13.7.1. sfValidatorI18nChoiceCountry
sfValidatorI18nChoiceCountry valida que el valor original sea un cdigo de pas

incluido en el estndar ISO 3166.


Opcin Error Descripcin

countries invalid Array con los cdigos de pas a utilizar (incluidos en el estndar ISO 3166)

13.7.2. sfValidatorI18nChoiceLanguage
sfValidatorI18nChoiceLanguage valida que el valor original sea un cdigo de idioma

incluido en el estndar ISO 639-1.


Opcin Error Descripcin Array con los cdigos de idioma a utilizar (incluidos en el estndar ISO 6391)

languages invalid

13.8. Validadores de Propel

13.8.1. sfValidatorPropelChoice
sfValidatorPropelChoice valida que el valor original se encuentre en la lista de registros

asociada a un modelo de Propel. Esta lista de registros se puede restringir utilizando la opcin criteria. Por defecto se comprueba que el valor original sea la clave primaria del registro, pero se puede comprobar cualquier otra columna indicada mediante la opcin column.
Opcin
model criteria

Error -

Descripcin La clase del modelo (esta opcin es obligatoria) Objeto criteria utilizado para realizar la consulta El nombre de la columna que se comprueba. Por defecto vale null, lo que significa que se emplea la clave primaria. El nombre de la columna se indica con el formato del nombre de los campos. La conexin Propel que se utiliza (por defecto vale null) Vale true si la lista desplegable permite selecciones mltiples

column

connection multiple

Nota

Este validador no funciona con los modelos que utilizan claves primarias compuestas.
13.8.2. sfValidatorPropelChoiceMany
sfValidatorPropelChoiceMany valida que los valores originales se encuentren en la lista

de registros asociada a un modelo de Propel.


Nota

Este validador utiliza como argumento un array, por lo que si se le pasa una cadena de texto, esta se convierte automticamente en un array. En realidad, este validador es un atajo de:
$v = new sfValidatorPropelChoice(array('multiple' =>true));

13.8.3. sfValidatorPropelUnique
sfValidatorPropelUnique valida que el valor de una columna o grupo de columnas (mediante la opcin column) sea nico dentro de un modelo de Propel.

Si se comprueban varias columnas, se puede lanzar un error global utilizando la opcin throw_global_error.
Opcin
model

Error -

Descripcin La clase del modelo (esta opcin es obligatoria) El nombre de la columna cuyo valor debe ser nico. El nombre se indica siguiendo el formato de Propel para el nombre de los campos (esta opcin es obligatoria). Si se comprueba el valor de varias columnas, se pasa un array con el nombre de los campos Nombre del campo que utiliza el formulario, distinto del nombre de la columna El nombre de la columna de la clave primaria (es opcional, por lo que si no se indica, Symfony intenta adivinarlo). Tambin se puede pasar un array si la tabla tiene varias claves primarias La conexin Propel que se utiliza (por defecto vale null) Indica si se lanza un error global (por defecto vale false) o un error asociado con el primer campo indicado en la opcin column

column

field

primary_key

connection

throw_global_error -

13.9. Validadores de Doctrine


13.9.1. sfValidatorDoctrineChoice
sfValidatorDoctrineChoice valida que el valor original se encuentre en la lista de

registros asociada a un modelo de Doctrine. Esta lista de registros se puede restringir utilizando la opcin query. Por defecto se comprueba que el valor original sea la clave primaria del registro, pero se puede comprobar cualquier otra columna indicada mediante la opcin column.
Opcin
model alias query

Error -

Descripcin La clase del modelo (esta opcin es obligatoria) El alias del componente principal utilizado en la consulta Consulta que se utiliza para obtener los objetos

column

El nombre de la columna que se comprueba. Por defecto vale null, lo que significa que se emplea la clave primaria. El nombre de la columna se indica con el formato del nombre de los campos. La conexin Propel que se utiliza (por defecto vale null)

connection -

Nota

Este validador no funciona con los modelos que utilizan claves primarias compuestas.
13.9.2. sfValidatorDoctrineChoiceMany
sfValidatorDoctrineChoiceMany valida que los valores originales se encuentren en la

lista de registros asociada a un modelo de Doctrine.


Nota

Este validador utiliza como argumento un array, por lo que si se le pasa una cadena de texto, esta se convierte automticamente en un array. Este validador hereda todas las opciones del validador sfValidatorDoctrineChoice.
13.9.3. sfValidatorDoctrineUnique
sfValidatorDoctrineUnique valida que el valor de una columna o grupo de columnas (mediante la opcin column) sea nico dentro de un modelo de Doctrine.

Si se comprueban varias columnas, se puede lanzar un error global utilizando la opcin throw_global_error.
Opcin
model

Error -

Descripcin La clase del modelo (esta opcin es obligatoria) El nombre de la columna cuyo valor debe ser nico. El nombre se indica siguiendo el formato de Doctrine para el nombre de los campos (esta opcin es obligatoria). Si se comprueba el valor de varias columnas, se pasa un array con el nombre de los campos El nombre de la columna de la clave primaria (es opcional, por lo que si no se indica, Symfony intenta adivinarlo). Tambin se puede pasar un array si la tabla tiene varias claves primarias La conexin Doctrine que se utiliza (por defecto vale null)

column

primary_key

connection

throw_global_error -

Indica si se lanza un error global (por defecto vale false) o un error asociado con el primer campo indicado en la opcin column

S-ar putea să vă placă și