Los formularios de Symfony 1.4

4.5. Configurando los validadores y los widgets

En esta sección se muestra cómo configurar los validadores y los widgets generados por defecto.

El formulario ArticuloForm dispone de un campo llamado slug. Un slug es una cadena de caracteres que representa de forma única al artículo dentro de la URL. Si se dispone por ejemplo de un artículo cuyo título es "Optimizando la programación con Symfony" y cuyo id es 12, su slug es 12-optimizando-la-programacion-con-symfony. Este campo se determina automáticamente a partir del campo titulo cada vez que se guarda el objeto, pero también se permite al usuario establecerlo de forma explícita. Aunque este campo es obligatorio según 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. Además, se personaliza el campo contenido aumentando su tamaño y obligando al usuario a escribir al menos cinco caracteres.

Listado 4-8 - Personalizando los validadores y los widgets

class ArticuloForm extends BaseArticuloForm
{
  public function 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 código 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 versión PHP 5.

Para asegurar que dos artículos no tengan el mismo slug, la definición del esquema incluye la restricción para hacer el slug único. Esta restricción a nivel de base de datos, se refleja en el formulario ArticuloForm a través 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 cómo 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
{
  public function setup()
  {
    // ...

    $this->validatorSchema->setPostValidator(
      new sfValidatorPropelUnique(array('model' => 'Articulo', 'column' => array('slug')))
    );
  }
}

El validador sfValidatorPropelUnique se establece como post-validador para que se ejecute después de comprobar que toda la información enviada por el usuario es válida. Para validar que el campo slug sea único, el validador no sólo tiene que accceder a su valor, sino que también debe acceder al valor de la clave o claves primarias. Además, las reglas de validación son diferentes en la creación y en la modificación del objeto, ya que el slug puede permanecer invariante durante la modificación de un artículo.

A continuación 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 cómo se pueden excluir los autores inactivos en el formulario ArticuloForm modificando la opción criteria del widget sfWidgetPropelSelect asociado al campo autor_id. La opción criteria toma como valor un objeto de tipo Criteria de Propel, lo que permite restringir el número de elementos de la lista desplegable.

Listado 4-10 - Personalizando el widget sfWidgetPropelSelect

class ArticuloForm extends BaseArticuloForm
{
  public function configure()
  {
    // ...

    $autorCriteria = new Criteria();
    $autorCriteria->add(AutorPeer::ACTIVO, true);

    $this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria);
  }
}

El código anterior sólo modifica la lista de opciones disponibles en el widget, pero también 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 opción llamada criteria para restringir las opciones válidas para este campo.

Listado 4-11 - Personalizando el validador sfValidatorPropelChoice

class ArticuloForm extends BaseArticuloForm
{
  public function 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 método configure(). En un proyecto real, este objeto Criteria puede ser muy útil en otras partes del código, por lo que es mejor crear un método llamado getCriteriaAutoresActivos() dentro de la clase AutorPeer y llamar a este método desde ArticuloForm como muestra el listado 4-12.

Listado 4-12 - Refactorizando el objeto Criteria en la clase del modelo

class AutorPeer extends BaseAutorPeer
{
  static public function getCriteriaAutoresActivos()
  {
    $criteria = new Criteria();
    $criteria->add(AutorPeer::ACTIVO, true);

    return $criteria;
  }
}

class ArticuloForm extends BaseArticuloForm
{
  public function configure()
  {
    $autorCriteria = AutorPeer::getCriteriaAutoresActivos();
    $this->widgetSchema['autor_id']->setOption('criteria', $autorCriteria);
    $this->validatorSchema['autor_id']->setOption('criteria', $autorCriteria);
  }
}

Nota Mientras que el widget sfWidgetPropelSelect y el validador sfValidatorPropelChoice representan una relación 1-n entre dos tablas, el widget sfWidgetFormPropelSelectMany y el validador sfValidatorPropelChoiceMany representan una relación de tipo n-n y aceptan las mismas opciones. En el formulario ArticuloForm, estas clases se emplean para representar la relación entre las tablas articulo y etiqueta.

4.5.1. Modificando el validador

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 máximo de 255 caracteres. Como este campo requiere que el valor introducido sea una dirección válida de correo eletrónico, 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
{
  public function configure()
  {
    $this->validatorSchema['email'] = new sfValidatorEmail();
  }
}

4.5.2. Añadiendo un validador

En la sección anterior se explica cómo modificar un validador generado automáticamente. Pero en el caso del campo emaill, también es necesario mantener el validador que limita el máximo número de caracteres. El listado 4-14 utiliza el validador sfValidatorAnd para garantizar que la dirección de email sea válida y que su longitud sea igual o inferior al máximo establecido.

Listado 4-14 - Utilizando un validador múltiple

class AutorForm extends BaseAutorForm
{
  public function 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 tamaño del campo email en el esquema de la base de datos, es preciso acordarse de que también hay que modificarlo en el formulario. Por lo tanto, en vez de reemplazar el validador generado automáticamente, es mejor añadir otro validador como se muestra en el listado 4-15.

Listado 4-15 - Añadiendo un validador

class AutorForm extends BaseAutorForm
{
  public function configure()
  {
    $this->validatorSchema['email'] = new sfValidatorAnd(array(
      $this->validatorSchema['email'],
      new sfValidatorEmail(),
    ));
  }
}

4.5.3. Modificando el widget

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');

  static public function getEstados()
  {
    return self::$estados;
  }

  // ...
}

Cuando se modifica un artículo, 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
{
  public function configure()
  {
    $this->widgetSchema['estado'] = new sfWidgetFormSelect(array('choices' => ArticuloPeer::getEstados()));
  }
}

Por último, también 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
{
  public function configure()
  {
    $estados = ArticuloPeer::getEstados();

    $this->widgetSchema['estado'] = new sfWidgetFormSelect(array('choices' => $estados));

    $this->validatorSchema['estado'] = new sfValidatorChoice(array('choices' => array_keys($estados)));
  }
}

4.5.4. Eliminando un campo

La tabla articulo tiene dos columnas especiales llamadas created_at y updated_at, cuyos valores gestiona Propel de forma automática. 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
{
  public function 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 cómo borrar los dos de forma simultánea mediante una sola instrucción, 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
{
  public function configure()
  {
    unset($this['created_at'], $this['updated_at']);
  }
}

4.5.5. Resultado final

Los listados 4-21 y 4-22 muestran respectivamente las versiones finales de los formularios ArticuloForm y AutorForm después de su personalización.

Listado 4-21 - Formulario ArticuloForm

class ArticuloForm extends BaseArticuloForm
{
  public function 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']);
  }
}

Listado 4-22 - Formulario AutorForm

class AutorForm extends BaseAutorForm
{
  public function configure()
  {
    $this->validatorSchema['email'] = new sfValidatorAnd(array(
      $this->validatorSchema['email'],
      new sfValidatorEmail(),
    ));
  }
}

La tarea propel:build-forms genera de forma automática la mayoría de elementos de los formularios a partir de la información del modelo de datos. Este proceso automático es muy útil porque:

  • Simplifica el trabajo del programador, evitándole las tareas más tediosas y repetitivas. De esta forma, el programador se centra en la configuración de los widgets y validadores para que sigan la lógica de la aplicación.
  • Además, cada vez que se actualiza el esquema de la base de datos, también se actualizan los formularios generados de forma automática. El único trabajo del programador consiste en ajustar la configuración realizada.

La siguiente sección describe la personalización y configuración de las acciones y plantillas generadas con la tarea propel:generate-crud.