Symfony 1.4, la guía definitiva

10.8. Formularios basados en un modelo

Los formularios son el mecanismo más utilizado en las aplicaciones web para editar la información de la base de datos. De hecho, la mayoría de formularios de las aplicaciones Symfony se emplean para editar un objeto del modelo. Como toda la información necesaria para construir el formulario ya está disponible en el esquema de datos de la aplicación, Symfony es capaz de generar automáticamente todos los formularios de tu modelo.

Nota Doctrine también dispone de todas las características que se van a explicar a continuación para Propel.

10.8.1. Generando los formularios del modelo

Symfony deduce los widgets y validadores del formulario de un modelo a partir de la información del esquema. Si se considera el siguiente esquema de ejemplo creado para Propel:

// config/schema.yml
propel:
  articulo:
    id:             ~
    titulo:         { type: varchar(255), required: true }
    slug:           { type: varchar(255), required: true, index: unique }
    contenido:      longvarchar
    esta_publicado: { type: boolean, required: true }
    autor_id:       { type: integer, required: true, foreignTable: autor, foreignReference: id, OnDelete: cascade }
    created_at:     ~

  autor:
    id:         ~
    nombre:     varchar(20)
    apellidos:  varchar(20)
    email:      { type: varchar(255), required: true, index: unique }
    activo:     boolean

El formulario para editar un objeto de tipo Articulo debería contener un widget oculto para el id, un widget de texto para el título, un validador de cadena de texto para el título, etc. Symfony genera todo el formulario automáticamente al ejecutar la tarea propel:build-forms:

// propel
$ php symfony propel:build-forms

// doctrine
$ php symfony doctrine:build-forms

Para cada tabla del modelo se generan dos archivos en el directorio lib/form/: una clase BaseXXXForm, que se borra y se vuelve a generar cada vez que ejecutas la tarea propel:build-forms y una clase XXXForm vacía que hereda de la anterior. Se trata del mismo sistema que utiliza Propel al generar las clases del modelo.

El formulario lib/form/base/BaseArticleForm.class.php generado contiene todos los widgets y validadores necesarios para las columnas definidas en la tabla articulo del archivo schema.yml:

class BaseArticuloForm extends BaseFormPropel
{
  public function setup()
  {
    $this->setWidgets(array(
      'id'             => new sfWidgetFormInputHidden(),
      'titulo'         => new sfWidgetFormInputText(),
      'slug'           => new sfWidgetFormInputText(),
      'contenido'      => new sfWidgetFormTextarea(),
      'esta_publicado' => new sfWidgetFormInputCheckbox(),
      'autor_id'       => new sfWidgetFormPropelChoice(array('model' => 'Autor', 'add_empty' => false)),
      'created_at'     => new sfWidgetFormDatetime(),
    ));
    $this->setValidators(array(
      'id'             => new sfValidatorPropelChoice(array('model' => 'Articulo', 'column' => 'id', 'required' => false)),
      'titulo'         => new sfValidatorString(array('max_length' => 255)),
      'slug'           => new sfValidatorString(array('max_length' => 255)),
      'contenido'      => new sfValidatorString(array('max_length' => 255, 'required' => false)),
      'esta_publicado' => new sfValidatorBoolean(),
      'autor_id'       => new sfValidatorPropelChoice(array('model' => 'Autor', 'column' => 'id')),
      'created_at'     => new sfValidatorDatetime(array('required' => false)),
    ));
    $this->setPostValidator(
      new sfValidatorPropelUnique(array('model' => 'Articulo', 'column' => array('slug')))
    );
    $this->setNameFormat('articulo[%s]');
    parent::setup();
  }

  public function getModelName()
  {
    return 'Articulo';
  }
}

Observa como Symfony no solamente comprueba que el valor de la columna id sea un número entero, sino que también utiliza el validador sfValidatorPropelChoice para comprobar que el valor enviado por el formulario exista en la tabla. El generador automático de formularios siempre utiliza las reglas de validación más estrictas para asegurar la información de la base de datos.

10.8.2. Utilizando los formularios del modelo

Si quieres personalizar los formularios generados automáticamente, puedes hacerlo añadiendo tu código en el método configure() de la clase del formulario.

El siguiente ejemplo muestra cómo se utiliza el formulario dentro de una acción. En concreto, se modifica el validador del campo slug para hacerlo opcional y se restringe la lista de autores del widget autor_id, para que sólo muestre los activos.

// en lib/form/ArticuloForm.class.php
public function configure()
{
  $this->getWidget('autor_id')->setOption('criteria', $this->getOption('criteria'));
  $this->getValidator('slug')->setOption('required', false);
}

// en modules/mimodulo/actions/actions.class.php
public function executeEditarArticulo($request)
{
  $c = new Criteria();
  $c->add(AutorPeer::ACTIVE, true);

  $this->form = new ArticuloForm(
    ArticuloPeer::retrieveByPk($request->getParameter('id')),
    array('criteria' => $c)
  );

  if ($request->isMethod('post'))
  {
    $this->form->bind($request->getParameter('articulo'));
    if ($this->form->isValid())
    {
      $articulo = $this->form->save();

      $this->redirect('articulo/editar?id='.$autor->getId());
    }
  }
}

Los formularios del modelo no establecen sus valores por defecto mediante un array asociativo, sino que utilizan un objeto del modelo para inicializar el valor de sus widgets. Si quieres mostrar un formulario vacío, pasa como argumento un nuevo objeto del modelo.

El manejo del envío del formulario se simplifica mucho porque el objeto del formulario incluye el objeto del modelo. La llamada a $this->form->save() sobre un formulario válido actualiza los valores del objeto Articulo asociado y realiza una llamada al método save() del objeto Articulo y de todos sus objetos relacionados.

Nota El código de la acción que se encarga de procesar un formulario casi siempre es el mismo, pero eso no quiere decir que lo tengas que copiar y pegar en varios módulos. Symfony incluye un generador de módulos completos que crea todas las acciones y plantillas necesarias para manipular los datos del modelo a través de formularios.