Symfony 1.0, la guía definitiva

10.4. Validaciones complejas

El archivo de validación es útil en la mayoría de los casos, aunque puede no ser suficiente cuando la validación es muy compleja. En este caso, se puede utilizar el método validateXXX() en la acción o se puede utilizar alguna de las soluciones que se presentan a continuación.

10.4.1. Creando un validador propio

Los validadores son clases que heredan de la clase sfValidator. Si las clases de validación que incluye Symfony no son suficientes, se puede crear otra clase fácilmente y si se guarda en cualquier directorio lib/ del proyecto, se cargará automáticamente. La sintaxis es muy sencilla: cuando el validador se ejecuta, se llama al método execute(). El método initialize() se puede emplear para definir opciones por defecto.

El método execute() recibe como primer argumento el valor que se debe comprobar y como segundo argumento, el mensaje de error que se debe mostrar cuando falla la validación. Los dos parámetros se pasan por referencia, por lo que se pueden modificar los mensajes de error directamente en el propio método de validación.

El método initialize() recibe el singleton del contexto y el array de parámetros del archivo YAML. En primer lugar debe invocar el método initialize() de su clase padre sfValidator y después, debe establecer los valores por defecto.

Todos los validadores disponen de un contenedor de parámetros accesible mediante $this->getParameterHolder().

Si por ejemplo se quiere definir un validador llamado sfSpamValidator para comprobar si una cadena de texto no es spam, se puede utilizar el código del listado 10-31 en un archivo llamado sfSpamValidator.class.php. El validador comprueba si $valor contiene más de max_url veces la cadena de texto http.

Listado 10-31 - Creando un validador propio, en lib/sfSpamValidator.class.php

class sfSpamValidator extends sfValidator
{
  public function execute (&$valor, &$error)
  {
    // Para max_url=2, la expresión regular es /http.*http/is
    $re = '/'.implode('.*', array_fill(0, $this->getParameter('max_url') + 1, 'http')).'/is';

    if (preg_match($re, $valor))
    {
      $error = $this->getParameter('spam_error');

      return false;
    }

    return true;
  }

  public function initialize ($contexto, $parametros = null)
  {
    // Inicializar la clase padre
    parent::initialize($contexto);

    // Valores por defecto de los parámetros
    $this->setParameter('max_url', 2);
    $this->setParameter('spam_error', 'Esto es spam');

    // Establecer los parámetros
    $this->getParameterHolder()->add($parametros);

    return true;
  }
}

Después de incluir el validador en cualquier directorio con carga automática de clases (y después de borrar la cache de Symfony) se puede utilizar en los archivos de validación de la forma que muestra el listado 10-32.

Listado 10-32 - Utilizando un validador propio, en validate/enviar.yml

fields:
  mensaje:
    required:
      msg:          El mensaje no se puede dejar vacío
    sfSpamValidator:
      max_url:      3
      spam_error:   En este sitio web no nos gusta el spam

10.4.2. Utilizando la sintaxis de los arrays para los campos de formulario

PHP permite utilizar la sintaxis de los arrays para los campos de formulario. Cuando se diseñan manualmente los formularios o cuando se utilizan los que genera automáticamente Propel (ver Capítulo 14) el código HTML resultante puede ser similar al del listado 10-33.

Listado 10-33 - Formulario con sintaxis de array

<label for="articulo_titulo">Titulo:</label>
<input type="text" name="articulo[titulo]" id="articulo_titulo" value="Valor inicial"
       size="45" />

Si en un archivo de validación se utiliza el nombre del campo de formulario tal y como aparece en el formulario (con los corchetes) se producirá un error al procesar el archivo YAML. La solución consiste en reemplazar los corchetes [] por llaves {} en la sección fields, como muestra el listado 10-34. Symfony se encarga de la conversión de los nombres que se envían después a los validadores.

Listado 10-34 - Archivo de validación para un formulario que utiliza la sintaxis de los arrays

fields:
  articulo{titulo}:
    required:     Yes

10.4.3. Ejecutando un validador en un campo vacío

En ocasiones es necesario ejecutar un validador a un campo que no es obligatorio, es decir, en un campo que puede estar vacío. El caso más habitual es el de un formulario en el que el usuario puede (pero no es obligatorio) cambiar su contraseña. Si decide cambiarla, debe escribir la nueva contraseña dos veces. El ejemplo se muestra en el listado 10-35.

Listado 10-35 - Archivo de validación para un formulario con 2 campos de contraseña

fields:
  password1:
  password2:
    sfCompareValidator:
      check:         password1
      compare_error: Las 2 contraseñas no coinciden

La validación que se ejecuta es la siguiente:

  • Si password1 == null y password2 == null:
    • La comprobación required se cumple.
    • Los validadores no se ejecutan.
    • El formulario es válido.
  • Si password2 == null y password1 no es null:
    • La comprobación required se cumple.
    • Los validadores no se ejecutan.
    • El formulario es válido.

El validador para password2 debería ejecutarse si password1 es not null. Afortunadamente, los validadores de Symfony permiten controlar este caso gracias al parámetro group. Cuando un campo de formulario pertenece a un grupo, su validador se ejecuta si el campo no está vacío y si alguno de los campos que pertenecen al grupo no está vacío.

Así que si se modifica la configuración del proceso de validación por lo que se muestra en el listado 10-36, la validación se ejecuta correctamente.

Listado 10-36 - Archivo de validación para un formulario con 2 campos de contraseña y un grupo

fields:
  password1:
    group:           grupo_password
  password2:
    group:           grupo_password
    sfCompareValidator:
      check:         password1
      compare_error: Las 2 contraseñas no coinciden

El proceso de validación ahora se ejecuta de la siguiente manera:

  • Si password1 == null y password2 == null:
    • La comprobación required se cumple.
    • Los validadores no se ejecutan.
    • El formulario es válido.
  • Si password1 == null and password2 == lo_que_sea:
    • La comprobación required se cumple.
    • password2 es not null, por lo que se ejecuta su validador y falla.
    • Se muestra un mensaje de error para password2.
  • Si password1 == lo_que_sea y password2 == null:
    • La comprobación required se cumple.
    • password1 es not null, por lo que se ejecuta también el validador para password2 por pertenecer al mismo grupo y la validación falla.
    • Se muestra un mensaje de error para password2.
  • Si password1 == lo_que_sea y password2 == lo_que_sea:
    • La comprobación required se cumple.
    • password2 es not null, por lo que se ejecuta su validador y no se producen errores.
    • El formulario es válido.