Cómo usar la opción virtual en los campos de formulario

La opción virtual para los campos de formulario puede ser muy útil cuando tienes algunos campos duplicados en diferentes entidades.

Por ejemplo, imagina que tienes dos entidades, una Compañía y un Cliente:

// src/Acme/HelloBundle/Entity/Company.php
namespace Acme\HelloBundle\Entity;

class Company
{
    private $name;
    private $website;

    private $street;
    private $zipcode;
    private $city;
    private $country;
}
// src/Acme/HelloBundle/Entity/Customer.php
namespace Acme\HelloBundle\Entity;

class Customer
{
    private $firstName;
    private $lastName;

    private $street;
    private $zipcode;
    private $city;
    private $country;
}

Como puedes ver, cada entidad comparte algunos de los mismos campos: calle, código postal, ciudad, país.

Ahora, quieres construir dos formularios: uno para la Compañía y el segundo para un Cliente.

Comienzas creando un muy simple CompanyType y un CustomerType:

// src/Acme/HelloBundle/Form/Type/CompanyType.php
namespace Acme\HelloBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

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

class CompanyType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', 'text')
            ->add('website', 'text');
    }
}
// src/Acme/HelloBundle/Form/Type/CustomerType.php
namespace Acme\HelloBundle\Form\Type;

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

class CustomerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstName', 'text')
            ->add('lastName', 'text');
    }
}

Ahora, para hacer frente a los cuatro campos duplicados. Aquí está un (sencillo) tipo de formulario domicilio:

// src/Acme/HelloBundle/Form/Type/AddressType.php
namespace Acme\HelloBundle\Form\Type;

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

class AddressType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('address', 'textarea')
            ->add('zipcode', 'text')
            ->add('city', 'text')
            ->add('country', 'text');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'virtual' => true
        ));
    }

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

En realidad no tienes un campo calle en cada una de tus entidades, por lo tanto no puedes vincular directamente tu LocationType a CompanyType o a CustomerType. Pero definitivamente quieres tener un tipo de formulario específico que se ocupe del domicilio (recuerda, ¡No te repitas!).

La opción virtual en los campos de formulario es la solución.

Podemos ajustar la opción 'virtual' => true en el método getDefaultOptions() de LocationType y comenzar a utilizarlo directamente en los dos tipos de formulario originales.

Mira el resultado:

// CompanyType
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('foo', new LocationType(), array(
        'data_class' => 'Acme\HelloBundle\Entity\Company'
    ));
}
// CustomerType
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('bar', new LocationType(), array(
        'data_class' => 'Acme\HelloBundle\Entity\Customer'
    ));
}

Con la opción virtual establecida a false (el comportamiento predeterminado), el componente Formulario espera que cada objeto subyacente tenga una propiedad foo (o bar) que sea o bien un objeto o un arreglo que contiene los cuatro campos de la ubicación. Por supuesto, ¡no tienes este objeto/arreglo en tus entidades y no lo quieres!

Con la opción virtual, ajustada al valor true, el componente formulario omite la propiedad foo (o bar), y en su lugar ¡«capta» y «define» los 4 campos del domicilio directamente en el objeto subyacente!

Nota

En lugar de establecer la opción virtual, en el AddressType, también puedes (al igual que con cualquier otra opción) pasar como argumento un arreglo al tercer parámetro de $builder->add().

Bifúrcame en GitHub