Hay veces en que las reglas de validación de un formulario pueden cambiar según el valor de otros campos del mismo formulario. ¿Un ejemplo? El caso del formulario de reserva para un billete de tren, si es un viaje de ida y vuelta, la fecha de regreso será obligatoria y además deberá ser mayor que la fecha de ida, si es un viaje de ida ésta no será obligatoria.

La clase del formulario

Como siempre, trabajaremos con un ejemplo concreto, el del formulario de reserva de billetes (con una versión simplificada, claro). Para ello construimos el formulario teniendo en cuenta las reglas de validación menos restrictivas. Quedaría así:

<?php
 
class PurchaseForm extends BaseForm{
 
 
    public function configure()
    {
 
      $types = array(1 => 'ida', 2 => 'vuelta' );
      $this->setWidgets(array(
          'type'         => new sfWidgetFormChoice(array('choices' => $types),
          'go_date'      => new sfWidgetFormDate();
          'return_date'  => new sfWidgetFormDate();
      ));
 
      $this->setValidators(array(
        'type_id'       => new sfValidatorChoice(array('choices' => array_keys($types))),
        'go_date'     => new sfValidatorDate(array('min' => date('Y-m-d'))),
        'return_date'       => new sfValidatorDate(array('min' => date('Y-m-d', strtotime("tomorrow")), 'required' => false)),
      ));
 
    }
 
}
?>

Para el que no tenga ganas de pararse a leer el código, se han considerado tres campos: type (tipo de billete), go_date (fecha de ida), return_date (fecha de vuelta), con las siguientes reglas de validación:

  • type: puede valer sólo 1 ó 2 (es decir, es de ida o vuelta)
  • go_date: es una fecha obligatoria y además mayor que hoy, ya que no se puede comprar un billete de ayer
  • return_date: debe ser una fecha mayor que mañana (se ha supuesto que no se puede ir y volver en el día, aunque eso siempre es debatible)
  • Si quieres echarle un vistazo en mayor detalle a todas las posibilidades que ofrecen los widget y validators de symfony relacionados con las fechas puedes ver mi otro post, “Fechas, fechas y más fechas: Symfony, sfWidgetFormDate y sfValidatorDate“.

    Las reglas de validación con palabras

    Antes de programar entendamos un poco más qué queremos hacer. Lo que buscamos es que, en el momento en el que recibamos los valores de los campos, dependiendo del valor que tome el campo type, deberemos exigirle al campo return_date que sea obligatorio y además que sea mayor que el campo go_date

    La solución

    Quizás tan importante como la solución (que no es otra cosa sino añadir 10 líneas de código) sea entender qué me ha llevado a hacerlo así. En primer lugar, en el epígrafe anterior hemos visto que necesitamos conocer el valor del campo, esto hace que sea imposible imponer esta validación en el método configure ya que a estas alturas no se tiene conocimiento del valor de los campos. Si pensamos un poco, no es hasta que ejecutamos el método bind() del formulario en el que éste es consciente del valor de sus campos. De todas formas, no es buena idea sobreescribir este método ya que no está pensado para ello.

    En general, los desarrolladores de symfony implementan métodos del tipo doXXXX() que son los que les gustaría que sobreescribiésemos, para nuestra suerte sfForm implementa el método doBind().

    Ahora sí, la solución

    <?php
    class PurchaseForm extends BaseForm{
     
        // resto de código
     
        public function  doBind(array $values)
        {
            if($values['type']==2)
            {
                $this->validatorSchema['date_to']->setOption('required', true);
                $this->validatorSchema->setPostValidator(
                    new sfValidatorSchemaCompare('date_from', '<', 'date_to', array('throw_global_error' => true), array('invalid' => 'La fecha de comienzo debe ser menor que la de fin'))
                );
            }
     
            return parent::doBind($values);
        }