Los formularios de Symfony 1.4

11.4. Personalizando los formularios generados

Las tareas doctrine:build-forms y doctrine:generate-crud permiten crear módulos de Symfony totalmente funcionales para listar, crear, editar y borrar objetos del modelo de datos. Estos módulos tienen en cuenta las reglas de validación y las relaciones entre tablas. Además, todo esto se consigue sin escribir ni una sola línea de código.

El siguiente paso consiste en personalizar el código generado automáticamente. Como los formularios incluyen muchos elementos, algunos aspectos de cada formulario deben ser modificados.

11.4.1. Configurando los widgets y validadores

En primer lugar se configuran los widgets y validadores generados automáticamente. El formulario ArticleForm incluye un campo llamado slug. Un slug es una cadena de caracteres que representan de forma única a cada artículo dentro de la URL.

Si se considera por ejemplo un artículo titulado "Optimiza las aplicaciones creadas con Symfony", su slug sería 12-optimiza-las-aplicaciones-creadas-con-symfony, siendo 12 el valor del campo id de ese artículo. Normalmente el slug se genera automáticamente a partir del título del objeto cada vez que se guarda el objeto en la base de datos.

En el siguiente ejemplo se considera que el usuario también puede indicar de forma explícita el valor del slug de un artículo. En este caso, aunque es obligatorio que el slug tenga un valor válido, no puede ser un campo obligatorio del formulario. Por este motivo se modificar su validador para que sea opcional, tal y como muestra el listado 11-8. Además, se modifica el campo content para aumentar su tamaño en pantalla y para forzar al usuario a escribir al menos cinco caracteres.

Listado 11-8 - Personalizando widgets y validadores

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    // ...

    $this->validatorSchema['slug']->setOption('required', false);
    $this->validatorSchema['content']->setOption('min_length', 5);

    $this->widgetSchema['content']->setAttributes(array('rows' => 10, 'cols' => 40));
  }
}

El código anterior utiliza los objetos validatorSchema y widgetSchema como si fueran arrays de PHP. Estos arrays asociativos admiten como clave el nombre de un campo del formulario y devuelven respectivamente el objeto validador y el objeto del widget. Mediante estos objetos se personalizan los campos y widgets individualmente.

Nota Para poder utilizar los objetos como si fueran arrays de PHP, las clases sfValidatorSchema y sfWidgetFormSchema implementan la interfaz ArrayAccess, disponible en PHP desde la versión 5.

Para asegurar que dos artículos no tengan el mismo valor en su campo slug, la definición del esquema incluye una restricción que exige que el valor del campo sea único. Esta restricción de la base de datos se refleja en el formulario ArticleForm mediante el validador sfValidatorDoctrineUnique. Este validador permite asegurar que el valor de un campo del formulario sea único en la base de datos. Se puede emplear este validador, entre otras cosas, para asegurar que una dirección de email o un login sean únicos. El listado 11-9 muestra como lo utiliza el formulario ArticleForm.

Listado 11-9 - Utilizando el validador sfValidatorDoctrineUnique para asegurar que un valor sea único

class BaseArticleForm extends BaseFormDoctrine
{
  public function setup()
  {
    // ...

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

El validador sfValidatorDoctrineUnique es de tipo postValidator, ya que se ejecuta sobre los datos completos del formulario después de que cada campo individual haya sido validado. De hecho, para validar que el valor del campo slug sea único, el validador no sólo debe acceder al propio valor del campo slug, sino que también debe conocer el valor de la clave primaria. Además, las reglas de validación son diferentes en la fase de creación de datos y en la de modificación, ya que el slug puede permanecer invariante mientras se modifica un artículo.

A continuación se modifica el campo active de la tabla author, que indica si un autor se encuentra activo. El listado 11-10 muestra cómo excluir a los autores inactivos en el formulario ArticleForm, modificando la opción query del widget sfWidgetDoctrineSelect asociado con el campo author_id. La opción query acepta un objeto de tipo consulta de Doctrine, lo que permite restringir los valores mostrados en la lista desplegable asociada.

Listado 11-10 - Personalizando el widget sfWidgetDoctrineSelect

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    // ...

    $query = Doctrine_Query::create()
      ->from('Author a')
      ->where('a.active = ?', true);
    $this->widgetSchema['author_id']->setOption('query', $query);
  }
}

Personalizar el widget permite restringir las opciones que se muestran en la lista desplegable, pero no olvides que también es necesario realizar esta modificación en el validador, tal y como muestra el listado 11-11. Al igual que el widget sfWidgetProperSelect, el validador sfValidatorDoctrineChoice acepta una opción llamada query que permite restringir los valores válidos para un campo del formulario.

Listado 11-11 - Personalizando el validador sfValidatorDoctrineChoice

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    // ...

    $query = Doctrine_Query::create()
      ->from('Author a')
      ->where('a.active = ?', true);

    $this->widgetSchema['author_id']->setOption('query', $query);
    $this->validatorSchema['author_id']->setOption('query', $query);
  }
}

En el ejemplo anterior, se define el objeto Query directamente en el método configure(). En el proyecto que se está realizando, esta consulta puede ser útil en muchas otras circunstancias, por lo que es mejor crear un método llamado getActiveAuthorsQuery() en la clase AuthorPeer e invocar este método desde ArticleForm, tal y como muestra el listado 11-12.

Listado 11-12 - Refactorizando la consulta con Query en el modelo

class AuthorTable extends Doctrine_Table
{
  public function getActiveAuthorsQuery()
  {
    $query = Doctrine_Query::create()
      ->from('Author a')
      ->where('a.active = ?', true);

    return $query;
  }
}

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $authorQuery = Doctrine::getTable('Author')->getActiveAuthorsQuery();
    $this->widgetSchema['author_id']->setOption('query', $authorQuery);
    $this->validatorSchema['author_id']->setOption('query', $authorQuery);
  }
}

Nota De la misma forma que el widget sfWidgetDoctrineSelect y el validador sfValidatorDoctrineChoice representan una relación 1-n entre dos tablas, el widget sfWidgetDoctrineSelectMany y el validador sfValidatorDoctrineChoiceMany representan una relación n-n con las mismas opciones que los anteriores. El formulario ArticleForm utiliza estas clases para representar la relación entre las tablas article y tag.

11.4.2. Modificando el validador

El esquema de datos define el campo email como un dato string(255), por lo que Symfony crea un validador de tipo sfValidatorString() que restringe su longitud máxima a 255 caracteres. Como este campo también debe cumplir la condición de que sea una dirección de correo electrónico válida, el listado 11-14 reemplaza el validador generado automáticamente por un validador de tipo sfValidatorEmail.

Listado 11-13 - Modificando el validador del campo email en la clase AuthorForm

class AuthorForm extends BaseAuthorForm
{
  public function configure()
  {
    $this->validatorSchema['email'] = new sfValidatorEmail();
  }
}

11.4.3. Añadiendo un validador

En la sección anterior se modificó un validador generado automáticamente. Sin embargo, en el caso del campo email, lo ideal sería mantener el validador que controla su longitud máxima. En el listado 11-14 se emplea el validador sfValidatorAnd para asegurar que el email proporcionado sea válido y que su longitud no sea mayor que la longitud máxima permitida en ese campo.

Listado 11-14 - Utilizando un validador múltiple

class AuthorForm extends BaseAuthorForm
{
  public function configure()
  {
    $this->validatorSchema['email'] = new sfValidatorAnd(array(
      new sfValidatorString(array('max_length' => 255)),
      new sfValidatorEmail(),
    ));
  }
}

El código del ejemplo anterior no es ideal porque si más adelante se modifica el tamaño del campo email en el esquema de la base de datos, es necesario modificarlo también en el formulario. Por lo tanto, en vez de reemplazar el validador generado automáticamente, es mejor añadir uno nuevo, tal y como muestra el listado 11-15.

Listado 11-15 - Añadiendo un validador

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

11.4.4. Modificando un widget

En el esquema de la base de datos, la tabla article almacena el estado de cada artículo en forma de cadena de caracteres en el campo status. Los posibles valores del estado se definen en la clase ArticePeer, tal y como muestra el listado 11-16.

Listado 11-16 - Definiendo los posibles estados en la clase ArticlePeer

class ArticlePeer extends BaseArticlePeer
{
  static protected $statuses = array('draft', 'online', 'offline');

  static public function getStatuses()
  {
    return self::$statuses;
  }

  // ...
}

Cuando se editan los datos de un artículo, el campo status se debería representar en forma de lista desplegable en vez de como un cuadro de texto. Para ello, se modifica el widget utilizado hasta el momento mediante el código mostrado en el listado 11-17.

Listado 11-17 - Modificando el widget del campo status

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses()));
  }
}

Para completar la modificación, también se debe cambiar el validador para asegurar que el estado seleccionado pertenece a alguna de las posibles opciones de la lista (ver listado 11-18).

Listado 11-18 - Modificando el validador del campo status

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $statuses = ArticlePeer::getStatuses();

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

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

11.4.5. Eliminando un campo

La tabla article dispone de dos columnas especiales llamadas created_at y updated_at, para las cuales Doctrine actualiza automáticamente sus valores. Para evitar que el usuario las modifique, el listado 11-19 muestra cómo se pueden eliminar del formulario.

Listado 11-19 - Eliminando un campo

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    unset($this->validatorSchema['created_at']);
    unset($this->widgetSchema['created_at']);

    unset($this->validatorSchema['updated_at']);
    unset($this->widgetSchema['updated_at']);
  }
}

Para eliminar un campo es preciso eliminar su validador y su widget. El listado 11-20 muestra cómo borrar los dos con una única instrucción accediendo al formulario como si fuera un array de PHP.

Listado 11-20 - Eliminando un campo accediendo al formulario como si fuera un array de PHP

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    unset($this['created_at'], $this['updated_at']);
  }
}

11.4.6. Resumiendo

Los listados 11-21 y 11-22 muestran el código definitivo de los formularios ArticleForm y AuthorForm después de personalizarlos.

Listado 11-21 - Formulario ArticleForm

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $authorQuery = Doctrine::getTable('Author')->getActiveAuthorsQuery();

    // widgets
    $this->widgetSchema['content']->setAttributes(array('rows' => 10, 'cols' => 40));
    $this->widgetSchema['status'] = new sfWidgetFormSelect(array('choices' => ArticlePeer::getStatuses()));
    $this->widgetSchema['author_id']->setOption('query', $authorQuery);

    // validators
    $this->validatorSchema['slug']->setOption('required', false);
    $this->validatorSchema['content']->setOption('min_length', 5);
    $this->validatorSchema['status'] = new sfValidatorChoice(array('choices' => array_keys(ArticlePeer::getStatuses())));
    $this->validatorSchema['author_id']->setOption('query', $authorQuery);

    unset($this['created_at']);
    unset($this['updated_at']);
  }
}

Listado 11-22 - Formulario AuthorForm

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

La tarea doctrine:build-forms permite generar automáticamente la mayoría de elementos de los formularios mediante la introspección del modelo de objetos. Las principales ventajas de esta automatización son:

  • Mejora la productividad del programador, evitándole todo el trabajo repetitivo y redundante. De esta forma el programador sólo se encarga de personalizar los validadores y los widgets en función de la lógica de negocio de la aplicación.
  • Si se actualiza el esquema de datos, los formularios generados también se actualizan automáticamente. Una vez más, el programador sólo se encarga de ajustar la personalización realizada anteriormente.

La siguiente sección trata de la modificación de las acciones y plantillas generadas por la tarea doctrine:generate-crud.