Más con Symfony

5.2. Creando un widget simple y un validador

Esta sección explica cómo crear un widget simple. Llamaremos a este widget "Trilean", ya que se trata de un valor booleano con tres opciones, que se muestran en una lista desplegable: No, Yes y Null.

class sfWidgetFormTrilean extends sfWidgetForm
{
  public function configure($options = array(), $attributes = array())
  {

    $this->addOption('choices', array(
      0 => 'No',
      1 => 'Yes',
      'null' => 'Null'
    ));
  }

  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    $value = $value === null ? 'null' : $value;

    $options = array();
    foreach ($this->getOption('choices') as $key => $option)
    {
      $attributes = array('value' => self::escapeOnce($key));
      if ($key == $value)
      {
        $attributes['selected'] = 'selected';
      }

      $options[] = $this->renderContentTag(
        'option',
        self::escapeOnce($option),
        $attributes
      );
    }

    return $this->renderContentTag(
      'select',
      "\n".implode("\n", $options)."\n",
      array_merge(array('name' => $name), $attributes
    ));
  }
}

El método configure() define la lista de valores mediante la opción choices. Este array se puede redefinir por ejemplo para modificar el título asociado a cada valor. No existe un límite en el número de opciones que puede definir un widget. No obstante, la clase base de los widgets declara una serie de opciones estándar que funcionan como opciones reservadas de facto:

  • id_format: el formato del identificador, cuyo valor por defecto es '%s'
  • is_hidden: valor booleano que indica si el widget es un campo oculto (utilizado por sfForm::renderHiddenFields() para mostrar a la vez todos los campos ocultos)
  • needs_multipart: valor booleano que indica si la etiqueta del formulario debe incluir la opción multipart (necesaria cuando se suben archivos)
  • default: el valor por defecto que se utiliza al mostrar el widget a menos que no se proporcione otro valor
  • label: el título por defecto del widget

El método render() genera el código HTML de la lista desplegable. En realidad, el método invoca la función interna renderContentTag() para facilitar la creación de las etiquetas HTML.

El widget ya está terminado, por lo que a continuación se crea su validador:

class sfValidatorTrilean extends sfValidatorBase
{
  protected function configure($options = array(), $messages = array())
  {
    $this->addOption('true_values', array('true', 't', 'yes', 'y', 'on', '1'));
    $this->addOption('false_values', array('false', 'f', 'no', 'n', 'off', '0'));
    $this->addOption('null_values', array('null', null));
  }

  protected function doClean($value)
  {
    if (in_array($value, $this->getOption('true_values')))
    {
      return true;
    }

    if (in_array($value, $this->getOption('false_values')))
    {
      return false;
    }

    if (in_array($value, $this->getOption('null_values')))
    {
      return null;
    }

    throw new sfValidatorError($this, 'invalid', array('value' => $value));
  }

  public function isEmpty($value)
  {
    return false;
  }
}

El validador sfValidatorTrilean define tres opciones en su método configure(). Cada opción es un conjunto de valores válidos. Como se definen mediante opciones, el programador puede personalizar los valores de acuerdo a sus necesidades.

El método doClean() comprueba si el valor se encuentra dentro de los valores definidos como válidos y devuelve el valor limpio. Si el valor no coincide con ninguno de los permitidos, se lanza una excepción de tipo sfValidatorError, que es el error de validació estándar en el framework de formularios.

El último método llamado isEmpty() se redefine porque por defecto devuelve true si el valor es null. Como el widget creado permite el uso de null como valor válido, el método siempre debe devolver false.

Nota Si el método isEmpty() devuelve true, no se ejecuta el método doClean().

Aunque este widget era muy sencillo, ha servido para introducir algunas características básicas muy importantes que se necesitarán más adelante. La siguiente sección crea un widget más complejo formado por varios campos y que también hace uso de JavaScript.