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!