Documente Academic
Documente Profesional
Documente Cultură
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.
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
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', ));
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:
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.
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_email
contacto[mensaje] contacto_mensaje
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.
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]'); } }
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');
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
lista desplegable
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 "Mundo!"" class="<script>alert("hola")</script>" type="text" name="contacto[email]" id="contacto_email" />
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.
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.
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)), )); } }
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
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() { } }
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:
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'))
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
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:
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
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.
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(), )), )); } }
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.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'), )));
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);
// ... }
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.
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.
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"' } ?>>
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
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.
Aunque este mtodo permite personalizar el estilo del formulario, no ofrece la flexibilidad necesaria para modificar la estructura o layout del formulario.
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.
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" />
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')?>
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>
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
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.
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
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.
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,
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.
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
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.
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"> <a href="<?php echo url_for('autor/index') ?>">Cancel</a> <?phpif(!$autor->isNew()): ?> <?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.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.
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
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.
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(); } }
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() {
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();
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']); } }
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']); } }
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.
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
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'))
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:
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();
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.
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'); } } }
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.
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.
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>
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.
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>
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')); } }
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.
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
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 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'); } } }
Los widgets disponibles para traducir y adaptar las fechas son los siguientes:
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):
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',
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',
// 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
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.
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
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.
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"> <a href="<?php echo url_for('author/index') ?>">Cancel</a> <?php if (!$author->isNew()): ?> <?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.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()
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().
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(); } }
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(), )); } }
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))); } }
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']); } }
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.
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
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:
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();
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
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'); } } }
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; } }
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'); } }
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');
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()
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
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)
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
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.
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
Cuando el usuario debe elegir un valor entre una lista de posibilidades, HTML ofrece diferentes formas de representar esa eleccin:
y
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
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'), ));
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, ));
Cuando el widget se muestra con una etiqueta <select> simple, se utiliza la etiqueta <optgroup> estndar.
12.4.3. Opciones soportadas
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')
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', ));
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>
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
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)
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
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
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:
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, ), );
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
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, ), );
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%', ));
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();
Descripcin Opciones para el widget de la fecha (ver opciones de sfWidgetFormDate) Opciones para el widget de la hora (ver opciones de sfWidgetFormTime)
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
Cuidado
Opcin
Descripcin
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%', ));
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
config Array de JavaScript con la configuracin del widget de JQuery culture La cultura del usuario
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
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'));
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:
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
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
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) );
with_empty Indica si se aade el checkbox para buscar textos vacos (true por defecto)
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
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
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
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.
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
Nota
Este widget lo utiliza Symfony internamente cuando un formulario se incluye dentro de otro formulario.
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);
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 -
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
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
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
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
"%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
"%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 -
Nota
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 -
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
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.
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
Opcin
time_format
Error
Descripcin
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
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%).
Opcin
max_size
Error
max_size
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 -
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
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.
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
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
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 -
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.
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
languages invalid
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
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 -
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
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