jueves, 5 de septiembre de 2013

Lista de tareas simple con Symfony 2: Parte 5

Continuación de la página Insertar Tarea

1. Validación del formulario

La validación del formulario se realiza en la entidad asociada, Tarea en nuestro caso, ya que será la que recibirá directamente los datos del formulario y será la que valide sus restricciones.

 /*src/Tarea/ListadoBundle/Entity/Tarea.php*/  
 ….  
  public static function loadValidatorMetadata(ClassMetadata $metadata) {  
     $metadata->addPropertyConstraint('descripcion', new Assert\NotBlank(array(  
       'message' => 'This value should not be blank.'  
     )));  
     $metadata->addPropertyConstraint('descripcion', new Assert\MinLength(12,array(  
       'message' => 'This value is too short. It should have {{ limit }} character or more.'  
     )));  
     $metadata->addPropertyConstraint('fechaRealizar', new Assert\Date(array(  
       'message' => 'This value is not a valid date.'  
     )));  
   }  

En está función estática se añadirán las restricciones sobre cada campo del formulario y sus mensajes de error. Está función será la encargada de comprobar si son válidos los datos enviados junto al formulario y será llamada desde el controlador. Dicho controlador la llamará internamente mediante $form->isValid(). Por lo tanto una vez se envíe el formulario se validará (validación en el servidor).

Nota: {{limit}} será el valor del primer parámetro de la función Assert que se utilice. Por ejemplo, en Assert\MinLength(12,...) {{limit}} valdrá 12.

Si estás utilizando un navegador compatible con HTML5 se te informará con los mensajes de HTML5 los cuales fuerzan determinadas restricciones. Esto añade validación del lado del cliente y Symfony2 establecerá las restricciones HTML5 apropiadas basándose en las anotaciones de los atributos de tu entidad. La validación del lado del cliente es muy útil, ya que no requiere enviar datos al servidor para validar el formulario. Sin embargo, no se debe  usar solo la validación del lado del cliente. Siempre debes validar los datos presentados en el servidor, debido a que un usuario podría eludir la validación del lado del cliente

2. Personalizar errores

Finalmente podemos personalizar los mensajes de error y el idioma en el que se presentan.
- Para ello lo primero que necesitamos es modificar el idioma (si no es que ya lo tenemos en español) en los ficheros de configuración de la aplicación (no del bundle). Por lo que en app/config.config.yml añadiremos:

 framework:  
   translator:   { fallback: %locale% }  

- Y en app/config/parameter.yml el parametro 'locale' lo pondremos a 'es':

 parameters:  
   locale: es   

Con esto le hemos indicado a Simfony  2 que use las traducciones para los mensajes de error en idioma español.

- Posteriormenete tendremos que crear un fichero de traducciones para nuestro Bundle o de lo contrario Symfony 2 usará los mensajes y traducciones que tiene por defecto. Dicho fichero lo crearemos en Tarea/ListadoBundle/Resources/translations/. Para trabajar más rápido, copiaremos un fichero por defecto de traducciones a nuestro directorio de traducciones local del bundle. Para ello copiaremos el fichero vendor/symfony/symfony/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf (ojo con la ruta :)) a src/Tarea/ListadoBundle/Resources/translations/validators.es.xlf. Ahora ya podemos modificar las traducciones o crear nuevos mensajes, siguiendo la estructura de este fichero.

 <?xml version="1.0"?>  
 <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">  
   <file source-language="en" datatype="plaintext" original="file.ext">  
     <body>  
       <trans-unit id="1">  
         <source>This value should be false.</source>  
         <target>Este valor debería ser falso.</target>  
       </trans-unit>  
       <trans-unit id="2">  
         <source>This value should be true.</source>  
         <target>Este valor debería ser verdadero.</target>  
       </trans-unit>  
       <trans-unit id="3">  
         <source>This value should be of type {{ type }}.</source>  
         <target>Este valor debería ser de tipo {{ type }}.</target>  
       </trans-unit>  
       <trans-unit id="4">  
         <source>This value should be blank.</source>  
         <target>Este valor debería estar vacío.</target>  
       </trans-unit>  
       <trans-unit id="5">  
         <source>The value you selected is not a valid choice.</source>  
         <target>El valor seleccionado no es una opción válida.</target>  
       </trans-unit>  
       <trans-unit id="6">  
         <source>You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.</source>  
         <target>Debe seleccionar al menos {{ limit }} opción.|Debe seleccionar al menos {{ limit }} opciones.</target>  
       </trans-unit>  
       <trans-unit id="7">  
         <source>You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.</source>  
         <target>Debe seleccionar como máximo {{ limit }} opción.|Debe seleccionar como máximo {{ limit }} opciones.</target>  
       </trans-unit>  
       <trans-unit id="8">  
         <source>One or more of the given values is invalid.</source>  
         <target>Uno o más de los valores indicados no son válidos.</target>  
       </trans-unit>  
       <trans-unit id="9">  
         <source>The fields {{ fields }} were not expected.</source>  
         <target>No se esperaban los campos {{ fields }}.</target>  
       </trans-unit>  
       <trans-unit id="10">  
         <source>The fields {{ fields }} are missing.</source>  
         <target>Faltan los campos {{ fields }}.</target>  
       </trans-unit>  
       <trans-unit id="11">  
         <source>This value is not a valid date.</source>  
         <target>Este valor no es una fecha válida.</target>  
       </trans-unit>  
       <trans-unit id="12">  
         <source>This value is not a valid datetime.</source>  
         <target>Este valor no es una fecha y hora válidas.</target>  
       </trans-unit>  
       <trans-unit id="13">  
         <source>This value is not a valid email address.</source>  
         <target>Este valor no es una dirección de email válida.</target>  
       </trans-unit>  
       <trans-unit id="14">  
         <source>The file could not be found.</source>  
         <target>No se pudo encontrar el archivo.</target>  
       </trans-unit>  
       <trans-unit id="15">  
         <source>The file is not readable.</source>  
         <target>No se puede leer el archivo.</target>  
       </trans-unit>  
       <trans-unit id="16">  
         <source>The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.</source>  
         <target>El archivo es demasiado grande ({{ size }} {{ suffix }}). El tamaño máximo permitido es {{ limit }} {{ suffix }}.</target>  
       </trans-unit>  
       <trans-unit id="17">  
         <source>The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.</source>  
         <target>El tipo mime del archivo no es válido ({{ type }}). Los tipos mime válidos son {{ types }}.</target>  
       </trans-unit>  
       <trans-unit id="18">  
         <source>This value should be {{ limit }} or less.</source>  
         <target>Este valor debería ser {{ limit }} o menos.</target>  
       </trans-unit>  
       <trans-unit id="19">  
         <source>This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.</source>  
         <target>Este valor es demasiado largo. Debería tener {{ limit }} carácteres o menos.|Este valor es demasiado largo. Debería tener {{ limit }} caracteres o menos.</target>  
       </trans-unit>  
       <trans-unit id="20">  
         <source>This value should be {{ limit }} or more.</source>  
         <target>Este valor debería ser {{ limit }} o más.</target>  
       </trans-unit>  
       ...
     </body>  
   </file>  
 </xliff>  

- Y para utilizar las traducciones utilizaremos los contenidos de los tags 'source' del fichero de traducciones, como mensajes de error en el método loadValidatorMetadata, visto anteriormente.  Y dependiendo de la configuración del idioma, también especificada anteriormente, se accederá al contenido del tag 'target' del fichero de traducción correspondiente. Si el idioma local especificado es 'es', Symfony 2 busca el fichero validators.es.xlf (en src/Tarea/ListadoBundle/Resources/translations).  Si no hay fichero de traducción se mostrará el tag 'source' introducido.

Es posible que necesitemos limpiar la cache (desde el directorio raíz de la aplicación) para ver los mensajes en otro idioma:

 rm -rf app/cache/*  
 rm -rf app/logs/*  

3. Completando el controlador

En el tutorial anterior vimos como llamar, desde el controlador, al método que crea al formulario a partir de la entidad y TareaType (que representa el formulario). Sin embargo no hicimos comprobaciones sobre la validez de los datos introducidos en el o del método de envío. Así que vamos a actualizar el controlador.

 /*Controller/TareaController.php*/  
 …  
  public function createAction() {  
     $tarea = new Tarea();  
     $form = $this->createForm(new TareaType(), $tarea);  
     $peticion = $this->getRequest();  
     if ($peticion->isMethod('POST')) {  
       $form->bind($peticion);  
       $tarea->setEstado(false); //añadimos manualmente el valor del atributo Estado 
       if ($form->isValid()) {  
         $em = $this->getDoctrine()  
             ->getEntityManager();  
         $em->persist($tarea);  
         $em->flush();  
         return $this->redirect($this->generateUrl('homepage'));  
       }  
       //else echo $form->getErrorsAsString();  
     }  
     return $this->render('TareaListadoBundle:Tarea:nueva.html.twig', array(  
           'form' => $form->createView()  
     ));  
   }   

Los formularios se suelen enviar a través del método POST, y nuestro formulario no será la excepción. Si el método de la petición es POST, una llamada a bind() asocia el objeto formulario (creado a partir de TareaType y la entidad Tarea) con los datos enviados por el usuario. En este punto, el objeto $form tiene una representación de lo que el usuario envió. A continuación hacemos una comprobación para ver si el formulario es válido. Como comentamos anteriormente, el método isValid() llamará internamente al método loadValidatorMetadata() de la entidad vinculada (evidentemente si el método esta definido). Y de esta forma se comprobará los datos introducidos en el formulario contra las reglas especificadas en dicho método (loadValidatorMetada).
Y si es válido solo falta hacer que persista la tarea. La persistencia de la entidad Tarea es tan simple como una llamada a persist() y otra a flush(). Con lo que Doctrine2 ya se encarga de insertar la tarea en la base de datos.
Si el los datos del formulario no son válidos se llamará a la función de renderizar la vista del formulario junto con el objeto formulario. Este objeto formulario contendrá los datos erróneos (tras comprobación) vinculados mediante bind(). Por lo que en la vista se mostrarán junto con los mensajes de error.
Si la petición no se realiza mediante el método HTTP POST, se procederá renderizar la plantilla del formulario. Y el objeto formulario que se pasa a dicha vista estará vació. Ya que no se llegó a vincular información. Por lo que el la vista formulario aparecerá sin datos de usuario.

Notas:
-Si tenemos activado la protección CSRF  para los formularios (viene activada por defecto) no nos olvidemos de usar form_rest(form) en la plantilla para que envíe  correctamente el contenido del campo oculto que crea dicha protección. O si no,  $form->isValid() puede dar el siguiente error: The CSRF token is invalid. Please try to resubmit the form.
- Es útil para tareas de depuración usar el método   $form->getErrorsAsString() para obtener una cadena con el estado de todos los campos del formulario y si tienen error o no.      
- En PHP hay que parsear los datos de entrada por parte del usuario. Los 'constraints' definidos en la entidad pueden evitar que se introduzcan datos de tipo incorrecto. El problema son las cadenas malintencionados (SQL Injection). Estos y otros temas de seguridad en Symfony 2 se tratarán más adelante.


Entradas relacionadas

Lista de tareas simple con Symfony 2: Parte 1
Lista de tareas simple con Symfony 2: Parte 2
Lista de tareas simple con Symfony 2: Parte 3
Lista de tareas simple con Symfony 2: Parte 4
Introducción a Symfony 2
Seguridad PHP - SQL injection
PDO - Parte 1

No hay comentarios:

Publicar un comentario