Symfony 2.4, el libro oficial

12.10. Embebiendo formularios

En ocasiones los formularios son tan complejos que incluyen cambios de varios objetos diferentes. Un formulario de registro de usuarios por ejemplo puede contener la información relacionada con un objeto User y uno o más objetos de tipo Address. Por suerte, embeber formularios dentro de otros formularios es algo sencillo y completamente natural para el componente de formularios de Symfony.

12.10.1. Embebiendo un solo objeto

Imagina que cada objeto Task pertenece a un objeto simple de tipo Category. En primer lugar, define el nuevo objeto Category:

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

use Symfony\Component\Validator\Constraints as Assert;

class Category
{
    /**
     * @Assert\NotBlank()
     */
    public $name;
}

A continuación, añade una nueva propiedad category a la clase Task:

// ...

class Task
{
    // ...

    /**
     * @Assert\Type(type="Acme\TaskBundle\Entity\Category")
     */
    protected $category;

    // ...

    public function getCategory()
    {
        return $this->category;
    }

    public function setCategory(Category $category = null)
    {
        $this->category = $category;
    }
}

Una vez actualizada esta parte de la aplicación, crea una nueva clase de formulario para que el usuario pueda modificar los objetos de tipo Category:

// src/Acme/TaskBundle/Form/Type/CategoryType.php
namespace Acme\TaskBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class CategoryType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\TaskBundle\Entity\Category',
        ));
    }

    public function getName()
    {
        return 'category';
    }
}

Nuestro objetivo es que los datos del objeto Category relacionado con el objeto Task se puedan modificar en el propio formulario con el que se gestiona el objeto Task. Para ello, añade un campo category al objeto TaskType y haz que su tipo sea una instancia de la nueva clase CategoryType:

use Symfony\Component\Form\FormBuilderInterface;

public function buildForm(FormBuilderInterface $builder, array $options)
{
    // ...

    $builder->add('category', new CategoryType());
}

Los campos de CategoryType ahora se pueden mostrar junto a los de la clase TaskType. Para activar la validación de CategoryType, añade la opción cascade_validation a TaskType:

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Acme\TaskBundle\Entity\Task',
        'cascade_validation' => true,
    ));
}

Ahora muestra los campos de Category de la misma manera que los campos de Task:

{# ... #}

<h3>Category</h3>
<div class="category">
    {{ form_row(form.category.name) }}
</div>

{# ... #}
<!-- ... -->

<h3>Category</h3>
<div class="category">
    <?php echo $view['form']->row($form['category']['name']) ?>
</div>

<!-- ... -->

Cuando el usuario envía el formulario, los datos enviados para los campos de Category se utilizan para crear una instancia de Category, que después se asigna como valor del campo category de la instancia de Task.

La instancia de Category se puede acceder, como cualquier otro campo, a través del método $task->getCategory() y la puedes persistir en la base de datos o utilizarla como quieras.

12.10.2. Embebiendo una colección de formularios

También es posible embeber toda una colección de formularios dentro de otro formulario (por ejemplo para embeber muchos formularios de tipo Product dentro de otro formulario de tipo Category). Para ello tienes que utilizar el tipo de campo collection.

Para más información consulta la referencia del tipo de campo collection.