Symfony 2.0, el libro oficial

12.1.  Creando un formulario sencillo

Imagina que estás construyendo una aplicación lista de tareas. Como tus usuarios tendrán que editar y crear tareas, vas a necesitar un formulario. Pero antes de empezar, vamos a centrarnos en la clase genérica Task que representa y almacena los datos para una sola tarea:

// src/Acme/TaskBundle/Entity/Task.php
namespace Acme\TaskBundle\Entity;

class Task
{
    // descripción de la tarea
    protected $task;

    // fecha en la que debe estar completada
    protected $dueDate;

    public function getTask()
    {
        return $this->task;
    }
    public function setTask($task)
    {
        $this->task = $task;
    }

    public function getDueDate()
    {
        return $this->dueDate;
    }
    public function setDueDate(\DateTime $dueDate = null)
    {
        $this->dueDate = $dueDate;
    }
}

Nota Si estás programando este ejemplo a medida que lees el capítulo, primero crea el bundle AcmeTaskBundle ejecutando el siguiente comando (y acepta los valores por defecto de todas las opciones):

$ php app/console generate:bundle --namespace=Acme/TaskBundle

Esta clase no es más que una clase PHP normal, ya que por el momento no tiene nada que ver ni con Symfony ni con ninguna otra librería. Se trata de una clase PHP adecuada para resolver el problema de tu aplicación. Al finalizar este capítulo, serás capaz de enviar información a una instancia de la clase Task mediante un formulario, validar sus datos y guardarlos en una base de datos.

12.1.1. Construyendo el formulario

Ahora que has creado una clase Task, el siguiente paso consiste en crear y mostrar en el navegador el formulario HTML real. En Symfony2, esto se hace construyendo un objeto de tipo Form y luego renderizándolo en una plantilla. Por el momento vamos a hacer todos estos pasos dentro de un controlador:

// src/Acme/TaskBundle/Controller/DefaultController.php
namespace Acme\TaskBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Acme\TaskBundle\Entity\Task;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{
    public function newAction(Request $request)
    {
        // crea una task y le asigna algunos datos ficticios para este ejemplo
        $task = new Task();
        $task->setTask('Write a blog post');
        $task->setDueDate(new \DateTime('tomorrow'));

        $form = $this->createFormBuilder($task)
            ->add('task', 'text')
            ->add('dueDate', 'date')
            ->getForm();

        return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}

Truco Este ejemplo muestra cómo crear el formulario directamente en el controlador. Más adelante en este mismo capítulo aprenderás a construir tu formulario en una clase independiente, lo que es muy recomendable para hacer que tu formulario sea reutilizable.

La creación de un formulario requiere de muy poco código, porque los objetos Form de Symfony2 se crean con un "generador de formularios" (o form builder en inglés). El generador de formularios te permite definir los formularios con unas instrucciones sencillas y después él se encarga de todo el trabajo duro de crear realmente el formulario.

En este ejemplo, se han añadido dos campos al formulario (task y dueDate) que se corresponden con las propiedades task y dueDate de la clase Task. También se asigna un tipo de dato a cada campo (text y date), para que Symfony sepa qué tipo de etiqueta HTML debe mostrar para cada campo.

Symfony2 incluye muchos tipos de datos, tal y como se explicará en las próximas secciones.

12.1.2. Mostrando el formulario

Una vez creado el formulario, el siguiente paso es renderizarlo. Para ello puedes pasar a la plantilla un objeto especial de formulario (que se crea con el método $form->createView()) y también puedes utilizar los diferentes helpers definidos por Symfony:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}

    <input type="submit" />
</form>
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?> >
    <?php echo $view['form']->widget($form) ?>

    <input type="submit" />
</form>
Resultado de mostrar un formulario sencillo

Figura 12.1 Resultado de mostrar un formulario sencillo

Nota Para que este ejemplo funcione, debes crear una ruta llamada task_new que apunte al controlador AcmeTaskBundle:Default:new creado anteriormente.

¡Y ya está! Al renderizar el helper form_widget(form), se renderizan todos los campos del formulario, cada uno con su etiqueta <label> y su mensaje de error (si es que se ha producido algún error). Aunque es una forma muy sencilla de mostrar un formulario completo, no es muy flexible. Normalmente es preferible renderizar cada campo individualmente para poder controlar con precisión cómo se muestra. Esto se explica más adelante en este mismo capítulo.

Antes de continuar, observa cómo el campo task renderizado tiene el valor de la propiedad task del objeto $task (en este ejemplo, verás el texto Write a blog post). Esta es la primera tarea importante del formulario: obtener los datos de un objeto y transformarlos para que se puedan mostrar correctamente en un formulario.

Truco El sistema de formularios es lo suficientemente inteligente como para acceder al valor de la propiedad protegida task a través de los métodos getTask() y setTask() de la clase Task. A menos que una propiedad sea pública, es obligatorio añadir los getters y setters para que el componente Form pueda obtener y guardar datos en la propiedad. Para las propiedades booleanas, puedes utilizar un método isser (por ejemplo, isPublished()) en lugar de un getter (por ejemplo, getPublished()).

12.1.3. Procesando el envío del formulario

La segunda responsabilidad del formulario consiste en traducir los datos enviados por el usuario a las propiedades del objeto. Para ello, primero se asocian al formulario los datos enviados por el usuario, tal y como muestra el siguiente ejemplo:

// ...

public function newAction(Request $request)
{
    // crear un objeto $task nuevo (borra los datos de prueba)
    $task = new Task();

    $form = $this->createFormBuilder($task)
        ->add('task', 'text')
        ->add('dueDate', 'date')
        ->getForm();

    if ($request->getMethod() == 'POST') {
        $form->bindRequest($request);

        if ($form->isValid()) {

            // guardar la tarea en la base de datos

            return $this->redirect($this->generateUrl('task_success'));
        }
    }

    // ...
}

Ahora, cuando el usuario envíe el formulario, el controlador asocia al formulario los datos enviados, que a su vez pasa la información a las propiedades task y dueDate del objeto $task. Todo esto sucede cuando se ejecuta el método bindRequest().

Nota Justo después de ejecutar el método bindRequest(), los datos del formulario se guardan en el objeto, sin importar si estos datos son realmente válidos o no.

El controlador del ejemplo anterior sigue el flujo de trabajo habitual para el manejo de formularios, que consiste en:

  1. Cuando se carga por primera vez la página asociada a este controlador, el método es GET y esto hace que se cree y renderice el formulario.
  2. Cuando el usuario envía el formulario (es decir, cuando el método es POST) con información no válida (en la siguiente sección se explica la validación), el formulario se asocia a la información y la plantilla vuelve a mostrar el formulario, pero con todos los errores de validación existentes.
  3. Cuando el usuario envía el formulario con datos válidos, el formulario se asocia de nuevo a la información y ya puedes realizar cualquier acción sobre el objeto $task (como por ejemplo guardarlo en la base de datos). Después, se redirige al usuario a otra página (por ejemplo a la página de agradecimiento o a la que muestra un mensaje determinado).

Nota Redirigir a un usuario después de enviar el formulario evita que el usuario pueda pulsar sobre el botón Actualizar del navegador provocando un nuevo envío del formulario.