title: How to dynamically populate drop-down lists using Symfony forms?
body: <p>I want to dynamically fill in my form (see picture)</p>
  
  <p><a href="https://i.stack.imgur.com/jrs5C.png" rel="nofollow noreferrer">countries states and cities</a></p>
  
  <p>The states <code>select</code> (html tag) is populated with a json file by the javascript function :</p>
  
  <pre><code>function onChangeCountries(countries, states) {
      $(countries).on("change", function(ev) {
          var countryId = $(this).val();
  
          if (countryId != '') {
  
              states.empty();
              states.append('&lt;option selected="true" disabled&gt;Choose state&lt;/option&gt;');
              states.prop('selectedIndex', 0);
  
              $.getJSON(statesUrl, function (data) {
                  $.each(data['states'], function (key, entry) {
                      if (entry.country_id == countryId) {
                          states.append($('&lt;option&gt;&lt;/option&gt;').attr('value', entry.id).text(entry.name));
                      }
                  })
              });
          }
      });
  }
  </code></pre>
  
  <p>countries and states parameters correspond to the two firsts <code>select</code>.
  This function populate the states <code>select</code> corresponding to the country selected. The same mechanism operates with the cities <code>select</code>.</p>
  
  <p>Now I want to use Symfony 4, here is my form :</p>
  
  <pre><code>&lt;?php
  
  // src/Form/LocationType.php
  namespace App\Form;
  
  use App\Entity\Location;
  use Symfony\Component\Form\Extension\Core\Type\TextType;
  use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  use Symfony\Component\Form\AbstractType;
  use Symfony\Component\Form\FormBuilderInterface;
  use Symfony\Component\OptionsResolver\OptionsResolver;
  
  class LocationType extends AbstractType
  {
      public function buildForm(FormBuilderInterface $builder, array $options)
      {
          $countries = json_decode(file_get_contents("js/countries.json"), TRUE)['countries'];
          $countriesChoices = array();
  
          foreach($countries as $country) {
              $countriesChoices[$country['name']] = $country['id'];
          }
  
          $states = json_decode(file_get_contents("js/states.json"), TRUE)['states'];
          $statesChoices = array();
  
          foreach($states as $state) {
              $statesChoices[$state['name']] = $state['id'];
          }
  
          $cities = json_decode(file_get_contents("js/cities.json"), TRUE)['cities'];
          $citiesChoices = array();
  
          foreach($cities as $city) {
              $citiesChoices[$city['name']] = $city['id'];
          }
  
          $builder
              -&gt;add('country', ChoiceType::class, array(
                  'choices' =&gt; $countriesChoices
              ))
              -&gt;add('area', ChoiceType::class, array(
                  'choices'  =&gt; $statesChoices
              ))
              -&gt;add('city', ChoiceType::class, array(
                  'choices'  =&gt; array(
                      'Choose city' =&gt; $citiesChoices
              ))
              -&gt;add('zip_code', TextType::class)
              -&gt;add('street', TextType::class)
          ;
      }
  
      public function configureOptions(OptionsResolver $resolver)
      {
          $resolver-&gt;setDefaults(array(
              'data_class' =&gt; Location::class,
          ));
      }
  }
  </code></pre>
  
  <p>The problem here is that I can not fill in the choices depending on user inputs.
  Also I'm receiving an OutOfMemoryException (there is more than 40 000 cities in cities.json).</p>
  
  <p><strong>Edit</strong></p>
  
  <p>After dlondero answer, I haven't resolve my problem, here is my new <code>LocationType</code> class :</p>
  
  <pre><code>&lt;?php
  
  // src/Form/LocationType.php
  namespace App\Form;
  
  use App\Entity\Location;
  use Symfony\Component\Form\Extension\Core\Type\TextType;
  use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  use Symfony\Component\Form\AbstractType;
  use Symfony\Component\Form\FormBuilderInterface;
  use Symfony\Component\OptionsResolver\OptionsResolver;
  use Symfony\Component\Form\FormInterface;
  use Symfony\Component\Form\FormEvent;
  use Symfony\Component\Form\FormEvents;
  
  class LocationType extends AbstractType
  {
      public function buildForm(FormBuilderInterface $builder, array $options)
      {
          $countries = json_decode(file_get_contents("js/countries.json"), TRUE)['countries'];
          $countriesChoices = array();
  
          foreach($countries as $country) {
              $countriesChoices[$country['name']] = $country['id'];
          }
  
          $builder
              -&gt;add('zip_code', TextType::class)
              -&gt;add('street', TextType::class)
              -&gt;add('country', ChoiceType::class, array(
                  'choices'  =&gt; $countriesChoices
              ))
              -&gt;add('area', ChoiceType::class, array(
                  'choices'  =&gt; array()
              ))
              -&gt;add('city', ChoiceType::class, array(
                  'choices'  =&gt; array()
              ))
          ;
  
          $formModifierStates = function (FormInterface $form, $countryId = null) {
              $statesChoices = array();
  
              if (null !== $countryId) {
                  $states = json_decode(file_get_contents("js/states.json"), TRUE)['states'];
  
                  foreach($states as $state) {
                      if ($countryId == $state['country_id']) {
                          $statesChoices[$state['name']] = $state['id'];
                      }
                  }
              }
  
              $form-&gt;add('area', ChoiceType::class, array(
                  'choices'  =&gt; $statesChoices
              ));
          };
  
          $formModifierCities = function (FormInterface $form, $stateId = null) {
              $citiesChoices = array();
  
              if (null !== $stateId) {
                  $cities = json_decode(file_get_contents("js/cities.json"), TRUE)['cities'];
  
                  foreach($cities as $city) {
                      if ($stateId == $city['state_id']) {
                          $citiesChoices[$city['name']] = $city['id'];
                      }
                  }
              }
  
              $form-&gt;add('city', ChoiceType::class, array(
                  'choices'  =&gt; $citiesChoices
              ));
          };
  
          $builder-&gt;addEventListener(
              FormEvents::PRE_SET_DATA,
              function (FormEvent $event) use ($formModifierStates) {
                  $data = $event-&gt;getData(); // country id
  
                  $formModifierStates($event-&gt;getForm(), $data);
              }
          );
  
          $builder-&gt;addEventListener(
              FormEvents::PRE_SET_DATA,
              function (FormEvent $event) use ($formModifierCities) {
                  $data = $event-&gt;getData(); // state id
  
                  $formModifierCities($event-&gt;getForm(), $data);
              }
          );
  
          $builder-&gt;get('country')-&gt;addEventListener(
              FormEvents::POST_SUBMIT,
              function (FormEvent $event) use ($formModifierStates) {
                  // It's important here to fetch $event-&gt;getForm()-&gt;getData(), as
                  // $event-&gt;getData() will get you the client data (that is, the ID)
                  $country = $event-&gt;getForm()-&gt;getData();
  
                  // since we've added the listener to the child, we'll have to pass on
                  // the parent to the callback functions!
                  $formModifierStates($event-&gt;getForm()-&gt;getParent(), $country);
              }
          );
  
          $builder-&gt;get('area')-&gt;addEventListener(
              FormEvents::POST_SUBMIT,
              function (FormEvent $event) use ($formModifierCities) {
                  // It's important here to fetch $event-&gt;getForm()-&gt;getData(), as
                  // $event-&gt;getData() will get you the client data (that is, the ID)
                  $state = $event-&gt;getForm()-&gt;getData();
  
                  // since we've added the listener to the child, we'll have to pass on
                  // the parent to the callback functions!
                  $formModifierCities($event-&gt;getForm()-&gt;getParent(), $state);
              }
          );
      }
  
      public function configureOptions(OptionsResolver $resolver)
      {
          $resolver-&gt;setDefaults(array(
              'data_class' =&gt; Location::class,
          ));
      }
  }
  </code></pre>
  
  <p>After filling in the form, I've got the message : "The choice "..." does not exist or is not unique". The choice is refering to the id (value of <code>option</code>) of the city selected in the <code>select</code>.</p>
  
  <p>The problem is that I can't figure out how to populate the city <code>ChoiceType</code>?</p>