I advise you to use an event listener/subscriber in your form type. You can look at how to dynamically modify forms using form events.
How to proceed : When a user select a particular option in your select field called "city" then a listener/subscriber adds a text field to let the user write a city name.
So, in your form type, you can add an event subscriber like that :
$builder->addEventSubscriber(new CityFieldSubscriber($builder->getFormFactory(), 'city', $options['manager']);
Note that you need to pass an entity manager as an option of your form type.
Then create the subscriber (this is an example. I have not executed this code so there are probably errors) :
class CityFieldSubscriber implements EventSubscriberInterface
{
protected $factory;
protected $fieldName;
protected $listenedFieldName;
protected $repository;
protected $optionId;
public function __construct(FormFactoryInterface $factory, ObjectManager $manager)
{
$this->factory = $factory;
$this->fieldName = 'cityName'; // text field to add
$this->listenedFieldName = 'city'; // entity field to listen
$this->repository = $manager->getRepository('AppBundle:City');
$this->optionId = 0; // The value of the option in your select field
}
public static function getSubscribedEvents()
{
return [
FormEvents::POST_SET_DATA => 'postSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
];
}
private function addCityForm(FormInterface $form, City $city = null)
{
if (null !== $city) {
if ($city->getId() === $this->optionId) {
$form->add($this->fieldName, TextType::class, [
'label' => 'app.form.type.cityName.label',
'required' => true
]);
}
}
}
public function postSetData(FormEvent $event)
{
$city = $event->getForm()->get($this->listenedFieldName)->getData();
if ($city) {
$this->addCityForm($event->getForm(), $city);
} else {
$this->addCityForm($event->getForm());
}
}
public function preSubmit(FormEvent $event)
{
$data = $event->getData();
if (isset($data[$this->listenedFieldName]) && $data[$this->listenedFieldName] !== '') {
$this->addCityForm($event->getForm(), $this->repository->find($data[$this->listenedFieldName]));
}
if (!isset($data[$this->listenedFieldName])) {
$this->addCityForm($event->getForm());
}
}
}
After that, you need to write some javascript code to add the text field when necessary. See this example :
<script>
var form = $('#form');
var formUrl = '{{ url('app_front_form') }}';
function replaceIfExistsOrRemove(selector, beforeSelectors, dom) {
var $new = $(dom).find(selector).parent('.form-group');
if ($new.length == 0) {
$(selector).parent('.form-group').remove();
} else {
if ($(selector).length > 0) {
$(selector).parent('.form-group').replaceWith($new);
} else {
for (i in beforeSelectors) {
if ($(beforeSelectors[i]).length > 0) {
$(beforeSelectors[i]).parent('.form-group').after($new);
return;
}
}
}
}
}
form.on('change', '#form_city', function() {
$('#form_submit_button').prop('disabled', true);
$.get(formUrl, $form.serialize(), function(html) {
$('#form_submit_button').prop('disabled', false);
replaceIfExistsOrRemove('#form_cityName', ['#form_city'], html);
});
});
</script>
After the form is submitted, if the form is valid, you save this new city and set it in your entity.
I hope this will help you :)