I have a Symfony2 Form with two layered dynamic fields. The first Layer is no problem implementing the documented way with form events: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms
But then comes a third field, which depends on the second field, which is already a dynamic field.
To demonstrate the problem, here is my stripped code:
<?php
class ServiceeventType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('park', 'entity', array(
'class' => 'AppBundle:Park',
'property' => 'identifyingName',
'label' => 'Park',
'required' => true,
'invalid_message' => 'Choose a Park',
'placeholder' => 'Please choose',
))
// just a placeholder for the $builder->get('facility')->addEventListener to have something to bind to
// I'm aware, that this is just a symptom of my problem
->add('facility', 'choice', array(
'choices' => array(),
'expanded' => true,
'multiple' => false,
'label' => 'Facility',
'required' => false,
'invalid_message' => 'Choose a Park first',
'placeholder' => 'Please choose a Park first',
))
// other fields
;
$formModifierPark = function (FormInterface $form, Park $park = null) {
// overwrite the facility field with the desired entity type
$form->add('facility', 'entity', array(
'class' => 'AppBundle:Facility',
'property' => 'identifyingName',
'choices' => null === $park ? array() : $park->getFacilities(),
'label' => 'Facility',
'required' => true,
'invalid_message' => 'Choose a Facility',
'placeholder' => null === $park ? 'Please choose a Park first' : 'Please choose',
));
};
$formModifierFacility = function (FormInterface $form, Facility $facility = null) {
$form->add('facilityStatuscode', 'entity', array(
'class' => 'AppBundle:FacilityStatuscode',
'property' => 'identifyingName',
'choices' => null === $facility ? array() : $facility->getFacilityStatuscodeType()->getFacilityStatuscodes(),
'label' => 'Statuscode',
'required' => null === $facility ? false : true,
'invalid_message' => 'Choose a Statuscode',
'placeholder' => null === $facility ? 'Please choose a Facility first' : 'Please choose',
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifierPark) {
$formModifierPark($event->getForm(), $event->getData()->getPark());
}
);
$builder->get('park')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifierPark) {
$formModifierPark($event->getForm()->getParent(), $event->getForm()->getData());
}
);
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifierFacility) {
$formModifierFacility($event->getForm(), $event->getData()->getFacility());
}
);
$builder->get('facility')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifierFacility) {
$formModifierFacility($event->getForm()->getParent(), $event->getForm()->getData());
}
);
}
// more code
}
The problem is now:
The event-listener set with $builder->get('facility')->addEventListener(FormEvents::POST_SUBMIT,…
gets lost at the moment, the facility-field is overwritten by the other event-listener.
I tried several workarounds, but it turns out, that form field options cannot be overridden and form later added fields don't accept new event listeners, once the builder is ready (i.e. when added inside an event listener).
I really have to solve this. Am I missing something? Is Symfony2's Form engine not able to handle two layered dynamic form field dependencies?
Any suggestions?
Thanks to dmnptr's link from his first comment (http://showmethecode.es/php/symfony/symfony2-4-dependent-forms/), I could solve the problem for my case. The trick is, to bind the events to the whole form and not to certain fields (and to PRE_SUBMIT
instead of POST_SUBMIT
). So my form class now looks like this:
<?php
class ServiceeventType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('park', 'entity', array(
'class' => 'AppBundle:Park',
'property' => 'identifyingName',
'label' => 'Park',
'required' => true,
'invalid_message' => 'Choose a Park',
'placeholder' => 'Please choose',
))
// other fields
;
$addFacilityForm = function (FormInterface $form, $park_id) {
// it would be easier to use a Park entity here,
// but it's not trivial to get it in the PRE_SUBMIT events
$form->add('facility', 'entity', array(
'class' => 'AppBundle:Facility',
'property' => 'identifyingName',
'label' => 'Facility',
'required' => true,
'invalid_message' => 'Choose a Facility',
'placeholder' => null === $park_id ? 'Please choose a Park first' : 'Please Choose',
'query_builder' => function (FacilityRepository $repository) use ($park_id) {
// this does the trick to get the right options
return $repository->createQueryBuilder('f')
->innerJoin('f.park', 'p')
->where('p.id = :park')
->setParameter('park', $park_id)
;
}
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($addFacilityForm) {
$park = $event->getData()->getPark();
$park_id = $park ? $park->getId() : null;
$addFacilityForm($event->getForm(), $park_id);
}
);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($addFacilityForm) {
$data = $event->getData();
$park_id = array_key_exists('park', $data) ? $data['park'] : null;
$addFacilityForm($event->getForm(), $park_id);
}
);
$addFacilityStatuscodeForm = function (FormInterface $form, $facility_id) {
$form->add('facilityStatuscode', 'entity', array(
'class' => 'AppBundle:FacilityStatuscode',
'property' => 'identifyingName',
'label' => 'Statuscode',
'required' => true,
'invalid_message' => 'Choose a Statuscode',
'placeholder' => null === $facility_id ? 'Please choose a Facility first' : 'Please Chosse',
'query_builder' => function (FacilityStatuscodeRepository $repository) use ($facility_id) {
// a bit more complicated, that's how this model works
return $repository->createQueryBuilder('fs')
->innerJoin('fs.facilityStatuscodeType', 'fst')
->innerJoin('AppBundle:Facility', 'f', 'WITH', 'f.facilityStatuscodeType = fst.id')
->where('f.id = :facility_id')
->setParameter('facility_id', $facility_id)
;
}
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($addFacilityStatuscodeForm) {
$facility = $event->getData()->getFacility();
$facility_id = $facility ? $facility->getId() : null;
$addFacilityStatuscodeForm($event->getForm(), $facility_id);
}
);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($addFacilityStatuscodeForm) {
$data = $event->getData();
$facility_id = array_key_exists('facility', $data) ? $data['facility'] : null;
$addFacilityStatuscodeForm($event->getForm(), $facility_id);
}
);
}
// more code
}
The AJAX-stuff then works like suggested in the article link above
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With