Symfony 2.3, 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')
            ->add('save', 'submit')
            ->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. Por último, se ha añadido un botón para enviar el formulario al servidor.

La opción de añadir botones a los formularios está disponible desde la versión 2.3 de Symfony. En las versiones anteriores tienes que añadir los botones a mano mediante el código HTML correspondiente.

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(form) }}
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->

<?php echo $view['form']->form($form) ?>
Resultado de mostrar un formulario sencillo

Figura 12.1 Resultado de mostrar un formulario sencillo

Nota El código del ejemplo anterior supone que el formulario se envía mediante una petición POST a la misma URL que se accedió para mostrar el formulario. Más adelante se explicará cómo modificar el método utilizado y la URL a la que se envían los datos.

¡Y ya está! Al renderizar el helper form(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). La función form también encierra todos los contenidos en la correspondiente etiqueta <form> de HTML. 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()) o hasser (por ejemplo, hasReminder()) en lugar de un getter (por ejemplo, getPublished() o getReminder()).

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 guardan en el 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')
        ->add('save', 'submit')
        ->getForm();

    $form->handleRequest($request);

    if ($form->isValid()) {
        // guardar la tarea en la base de datos

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

    // ...
}

El método handleRequest() está disponible desde la versión 2.3 de Symfony. En las versiones anteriores se pasaba el objeto $request al método submit(). Este último comportamiento se ha declarado obsoleto y se eliminará en Symfony 3.0.

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, se crea y renderiza el formulario. El método handleRequest() detecta que el formulario no se ha enviado y por tanto, no hace nada. El método isValid() devuelve false si el formulario no se ha enviado.
  2. Cuando el usuario envía el formulario, el método handleRequest() lo detecta y guarda inmediatamente los datos enviados en las propiedades task y dueDate del objeto $task. Después se valida este objeto. Si no es válido, el método isValid() devuelve false otra vez, por lo que se vuelve a mostrar el formulario, esta vez con los mensajes de error correspondientes. Si solamente quieres comprobar si el formulario se ha enviado, independientemente de si es válido o no, utiliza el método isSubmitted().
  3. Cuando el usuario envía el formulario con datos válidos, los datos enviados se guardan de nuevo en el formulario, pero esta vez el método isValid() devuelve true. En este momento ya puedes trabajar con el objeto $task (por ejemplo guardándolo en una base de datos) antes de redirigir 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.

12.1.4. Enviando formularios con varios botones

Esta característica solamente está disponible desde la versión 2.3 de Symfony.

Cuando el formulario contiene más de un botón de envío, es necesario comprobar en el controlador qué botón concreto se ha pulsado. En primer lugar, modifica ligeramente el formulario de los ejemplos anteriores para añadir un segundo botón llamado "Save and add":

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

Dentro del controlador, utiliza el método isClicked() para comprobar si se ha pulsado el botón "Save and add":

if ($form->isValid()) {
    // ... haz algo con los datos, como por ejemplo guardarlos
    // en la base de datos ...

    $nextAction = $form->get('saveAndAdd')->isClicked()
        ? 'task_new'
        : 'task_success';

    return $this->redirect($this->generateUrl($nextAction));
}