Update:
The original blog post’s blog is dead. I found a related article here.
This article was published here. I am reposting here because it is an interesting solution.
The concept is simple. We have two HTML SELECT fields, one for the city and one for the state (US). When the state is changed, we would like to populate the city select with cities from the selected state. We’ll use my ContactInfoType as an example. The form type would look like this:
public function buildForm(FormBuilder $builder, array $options) { $builder->add('city', 'entity', array('query_builder' => function (EntityRepository $er) { ... })); $builder->add('state', 'entity', array('query_builder' => function (EntityRepository $er) { ... })); } |
Here is the relevant snippet from the Controller:
$em = $this->get('doctrine.orm.entity_manager'); // In beta2 the Entity Manager is retrieved by $this->get('doctrine')->getEntityManager(); $contact = $em->getRepository('Entity:ContactInfo') ->find(1); $form = $this->get('form.factory') ->create(new ContactInfoType(), $contact) ->getForm(); if($this->get('request')->getMethod() == 'POST') { $form->bindRequest($this->get('request'); $em->persist($contact); $em->flush(); } |
Everything looks straightforward enough, but the solution is flawed. Let’s say out $contact’s city is Columbia, MO. When we instantiate our form in the Controller, the “city” field is
populated with cities in Missouri. Now we want to set the city to Boulder, CO. We issue out our AJAX request and retrieve a list of cities in Colorado and populate the “city” SELECT accordingly, select Boulder and submit the form. Now, back in our Controller, we reload our $contact form the database and instantiate our form. Once again, our “city” field is populated with cities in Missouri. Since this is now a POST request, we want to bind the request to the form. When the Form Component tries to setCity, the city it is trying to set is not on the list, and that’s a problem. I posted my issue on the Symfony Users mailing list, and Berhard Schussek was kind enough to offer this solution. It’s not the shortest solution in the world, but it works. To work around this we need to use the Event System, utilizing the “preSetData” and “preBind” events, and create a Closure to use as a callback to create and populate the “city” field. Before the data is set, we populate our “city” field with cities in Missouri and then, before we bind data, we re-create our “city” field, populating it with cities from Colorado.
Here is our Closure:
$refreshCity = function ($form, $state) use ($factory) { $form->add($factory->createNamed('entity', 'city', null, array( 'class' => 'Entity:Cities', 'property' => 'city_name', 'label' => 'City', 'query_builder' => function (EntityRepository $repository) use ($state) { $qb = $repository->createQueryBuilder('cities') ->select(array('cities', 'zip_codes')) ->innerJoin('cities.states', 'states') ->innerJoin('cities.zip_codes', 'zip_codes'); if($state instanceof States) { $qb = $qb->where('cities.states = :state') ->setParameter('state', $state); } elseif(is_numeric($state)) { $qb = $qb->where('states.state_id = :state_id') ->setParameter('state_id', $state); } else { $qb = $qb->where('states.state_id = 1'); } return $qb; } ))); }; |
And now, let’s put $refreshCity to use:
$builder->addEventListener(Events::preSetData, function (DataEvent $event) use ($refreshCity) { $form = $event->getForm(); $data = $event->getData(); if($data == null) return; //As of beta2, when a form is created setData(null) is called first if($data instanceof ContactInfo) { $refreshCity($form, $data->getCity()->getState()); } }); $builder->addEventListener(Events::preBind, function (DataEvent $event) use ($refreshCity) { $form = $event->getForm(); $data = $event->getData(); if(array_key_exists('state', $data)) { $refreshCity($form, $data['state']); } }); |
Now we can use the ContactInfoType in our Controller and we get the result we expect. This may be a somewhat lengthy solution, but Symfony provides lots of opportunities for code re-use. I use this form type in a *lot* of places and copy and paste elsewhere.
Hi
thanks for nice example..
can you please send me source for this.. ?
this is all that i have for now, more posts are coming in the future, look out for them, thanks!
I am glad future is very broad. I have not done my homework but now i am in need for real concrete way of solving this.
ok got it, it is becoming easier and easier
What about validation? You must ensure that a submitted city is belonging to submitted state, and of course you can’t just rely on ajax.
i think the validation is implicit because if it is different option that persists then it would fire up an exception hmm of course different exeception.
This is actually a very good question, have you yourself found a solution? please share it back url?
I think validation in terms of regular validation should work normal, however if you want to validate further like for sets of ids that means your code needs to include a validator in the form type i guess.
A query. I followed suit, but I can’t handle the event changed the field ‘state’.
Yes, once the object is to create the form, but it does once the form has already been created. I mean?
That is, once the object already exists, and return to change the “state”, not refreshed the ‘cities’.
I hope I was clear. Thank you very much for the reply.
you are not clear at all your english is not clear sorry
Puedes publicar el codigo fuente ???? para sf2.1
you can put the source for sf2.1
Puedes publicar el codigo fuente ???? para sf2.1 por favor
you can put the source for sf2.1 please ????