3

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?

4 Answers 4

3

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!

Sign up to request clarification or add additional context in comments.

Comments

0

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

2 Comments

Try harder. Should work. Also, if you're using FF, do a full refresh of the page because it saves old form values.
I declare a new Pais() and then setId() with the value it has. I set it in preferred_choices, and it just doesn't work. Unless something else is to be done?
0

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

1 Comment

Only question: how do I search in the form? Do I use get, and if so, what do I send as argument?
0

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

2 Comments

Are you sure this works on 2.3? I'm trying to use it and apparently it uses the deprecated field type... I was modifying the source a little bit and I ended up with a mess.
I had to modify a lot of the source and even then it didn't work

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.