Carlos Vergara
Carlos Vergara

Reputation: 3622

Symfony2 entity with default value and embedded forms

I have a class which is meant to be a form type (and, in turn, embedded into another form) for selecting commune, region and country, each with a dropdown list (region depends on country). I want a certain country to be selected by default when constructing the country list. This is what I have:

$factory = $builder->getFormFactory();
$builder->add('pais', 'entity', array(
'class' => 'Codesin\ColegiosBundle\Localidad\Pais',
'property' => 'nombre',
'query_builder' => function (EntityRepository $repositorio) {
    $qb = $repositorio->createQueryBuilder('pais')
        ->orderBy('pais.nombre');
        return $qb;
    }
));

I've tried using preferred_value (it says I need an object, which I can't fetch from anywhere given I've got no database access inside the class) and data (which doesn't work). I tried using the id of the entity for both cases. How do I set a selected default value, in this case?

Upvotes: 3

Views: 1839

Answers (4)

Carlos Vergara
Carlos Vergara

Reputation: 3622

Alright, I think I finally understood how to make it as a service. "Why would you make it a service?", you may ask? Well, because I need to inject the entity manager to this. I hope the following tutorial is of any use.

Preferred_choices in custom form types

Let's say you have several countries saved in your database, and they are a Country entity, properly defined in Doctrine as such. But there's a problem - there are nearly 250 countries around the world, and the user is most likely to be from one single country! So, let's say, you need a country to be selected by default, or to be at the top of the list.

Step 1: Populate the field

Let's say we're building a custom form type, CountryType. Let's define it.

class CountryType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('country', 'entity', array(
             'class' => 'Acme\AcmeBundle\Locality\Country',
             'property' => 'name',
             'query_builder' => function (EntityRepository $repository) {
                 $qb = $repository->createQueryBuilder('country')->orderBy('country.name');
                 return $qb;
             }
        ));
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'Acme\AcmeBundle\Locality\Country');
    }

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

}

And when you call it from somewhere, as in,

$builder->add('country', new CountryType());

It all works good, except you don't get a selected country by default. Worse, if you try to add

'preferred_choices' => array(15) //Say, 15 is the id of the country

to your options, you will get an exception. And for some weird reason, doing this:

$defaultCountry = new Country();
$defaultCountry->setId(15);

and then

'preferred_choices' => array($defaultCountry)

does not work either.

Here's where fun begins.

Step 2: Add an EntityManager

In order to search for the entity we need, we will have to query the database. And right now we cannot access it as we don't have a Doctrine EntityManager. What we will do is to add one to our class, passing it to the constructor as an argument and storing it as a private property of the class.

class CountryType extends AbstractType
{
    //Add this
    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('country', 'entity', array(
             'class' => 'Acme\AcmeBundle\Locality\Country',
             'property' => 'name',
             'query_builder' => function (EntityRepository $repository) {
                 $qb = $repository->createQueryBuilder('country')->orderBy('country.name');
                 return $qb;
             }
        ));
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'Acme\AcmeBundle\Locality\Country');
    }

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

    //Add this
    private $em;
}

We query the database afterwards.

class CountryType extends AbstractType
{
    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        //Add the following lines
        $qb = $this->em->createQueryBuilder();
        $resultset = $qb->select('country')
                        ->from('Acme\AcmeBundle\Locality\Country', 'country')
                        ->where('country.id = ?1')
                        ->setParameter(1, 15)
                        ->getQuery()
                        ->execute();

        $default = $resultset[0];

        $builder->add('country', 'entity', array(
             'class' => 'Acme\AcmeBundle\Locality\Country',
             'property' => 'name',
             'preferred_choices' => array($default), //Add this too
             'query_builder' => function (EntityRepository $repository) {
                 $qb = $repository->createQueryBuilder('country')->orderBy('country.name');
                 return $qb;
             }
        ));
    }

    public function getDefaultOptions(array $options)
    {
        return array('data_class' => 'Acme\AcmeBundle\Locality\Country');
    }

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

    private $em;
}

But now we run into a problem: we cannot call it anymore!! How do we pass it an EntityManager, especially if we're not calling this from a controller but rather we're embedding it? Here's where magic is to be done.

Step 3: Make it a service

Why? Because this way we can inject an EntityManager to it. Go to Acme/AcmeBundle/Resources/cofig/services.yml and add the following lines:

acme.acmebundle.countrytype:
    class: Acme\AcmeBundle\Types\CountryType
    arguments: ["@doctrine.orm.entity_manager"]
    tags:
        - { name: form.type, alias: countrytype }

Some notes here:

  • The argument passed to the service is another service, namely, a Doctrine EntityManager. This argument is passed to the function's constructor and injected from somewhere else. This way we will make sure we always have an EntityManager.
  • In the tags, make sure to include both tags named. The alias tag must match whatever is returned from getName() in your custom type, in this case, countrytype.

If you want to know more about dependency injection and services, go here: Service Container.

Now, the big question: how do we call it?

Step 4: Calling the service

It's extremely easy to do. Remember this line?

$builder->add('country', new CountryType());

Now change it to the following:

$builder->add('country', 'countrytype');

That's it! Now your custom type has an entity manager attached to it, and no arguments had to be passed. Good luck in your next project!

Upvotes: 3

Sofien Benrhouma
Sofien Benrhouma

Reputation: 406

you need to insert this ready bundle https://github.com/shtumi/ShtumiUsefulBundle

and configure it here https://github.com/shtumi/ShtumiUsefulBundle/blob/master/Resources/doc/dependent_filtered_entity.rst

i use the framework to make dependent forms

Upvotes: 0

user1236048
user1236048

Reputation: 5612

Try at SET_DATA or POST_SET_DATA form events: You could do something like this inside the lambda function:

$data = $event->getData();
$form = $event->getForm();

// search in the form for the right entity to set as country.. let's consider $country

$data->setCountry($country);
$event->setData($data);

It may work. Try and let us know on the progress

Upvotes: 0

Elnur Abdurrakhimov
Elnur Abdurrakhimov

Reputation: 44851

If you're using the form type with a model object, set the default on the object itself.

Upvotes: 0

Related Questions