My custom form type is based on the EntityType.
My entity has a composite primary key so I set the choice_value to reflect both key fields values in the options "value" attribute, which works as expected.
I subscribed to the formType PRE_SUBMIT event so that I could parse that value and convert it back into an entity instance thank's to Doctrine\ORM\EntityManager::getReference method.
My problem arise upon submission. I expected the event setData($my_retrieved_entity) method (inside the event listener) to successfully replace the form submitted value with the corresponding entity (satisfying the form validation pipeline) but I get what seems to be a default symfony error message instead:
«This value is not valid.»
// …
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired(['entityManager','country']);
$resolver->setAllowedTypes('country', Country::class);
$resolver->setAllowedTypes('entityManager', EntityManager::class);
$resolver->setDefaults([
'class' => NetworkTypeModel::class,
'placeholder' => 'Choose a network type',
'choice_value' => function ($networkType) {
// Set specific format for for the value attribute
// so that it reflects both primary key fiels values
return $networkType
? "{$networkType->getId()}¤{$networkType->getCountry()->getId()}"
: ''
;
}
,'query_builder' => function (Options $options) {
return function (EntityRepository $er) use ($options) {
$qb = $er->createQueryBuilder('nt')
->leftJoin('nt.country', 'c')
->addSelect('c');
return $qb->where($qb->expr()->eq('nt.country', ':country'))
->setParameter('country', $options['country']->getId())
->orderBy('nt.label', 'ASC');
};
},
]);
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$entityManager = $options['entityManager'];
// Listen to post DATA in order to transform Option's value
// back to a networkType instance
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($entityManager) {
if ($data = $event->getData()) {
// Special value has been sent through POST field
// It needs to be parsed and transformed back to a networkType entity
// According to the format set from inside choice_value options.
$ids = explode('¤', $data);
$networkType = $entityManager->getReference(
NetworkTypeModel::class,
['id' => $ids[0], 'country' => $ids[1]]
);
// I expect $event->setData to populate the form submitted value
// to be the selected value
$event->setData($networkType);
// But I get the following common error message on that field
// «This value is not valid.»
}
}
);
}
public function getParent()
{
return EntityType::class;
}
Yet, executing dump($networkType) right before $event->setData($networkType); dumps a perfectly valid entity instance, and actually the right one, associated to the submitted item.
It feels like I almost got things right, but I have no idea what's wrong here. Any idea on how to correctly transform the submitted data into an entity, inject it into the form and satisfy the validator chain ?
EDIT
I got the job done but probably not really the symfony way. It might help you understanding my need.
I used a shared variable:
- Populated from inside
PRE_SUBMITevent listener, with the expected networkType instance, reconstructed using the$event->getData()parsed string. - Then consumed this instance reference inside the
POST_SUBMITevent as theFormEvent::setDataargument.
Changed/added code:
$entityManager = $options['entityManager'];
$selectedNetworkType = null;
// Transform Option's value back to a networkType instance
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($entityManager, &$selectedNetworkType) {
// Transform the custom dropdown "value" attribute coming from POST
// into a my networkType model instance
if ($data = $event->getData()) {
// PRE_SUBMIT event data holds the view data as a string
// which needs to be parsed accordingly to what have been
// done when encoding the entity's ids in choice_value callable
$ids = explode('¤', $data);
// We don't need to retrieve the entire record from DB anyway
// so we use getReference
$networkType = $entityManager->getReference(
NetworkTypeModel::class,
['id' => $ids[0], 'country' => $ids[1]]
);
// Now store the newly created networkType instance
// for later
$selectedNetworkType = $networkType;
}
}
)->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use (&$selectedNetworkType) {
// Use the fresh stored instance to feed the model data
$selectedNetworkType && $event->setData($selectedNetworkType);
}
);
I hope someone will come up with the right directions to solve this use case, for the sake of better understanding the Symfony Form component.
Thank you.
As far as I understood your task what you need is to modify entity that your form is reassembling from form data right before it will be sent back to client. You actually need to use FormEvents::SUBMIT to do that($event->setData($data) and $event->getData() here used to manage reassembled entity here), and what you are doing is changing raw form data array(which you will get by $event->getData() call inside FormEvents::PRE_SUBMIT, you can check this by calling var_dump() on it) onto proxy entity, that on submit form tries to recreate your entity from proxy(while valid data for submit must be provided in form of arrray). Also note, that you using other instance of entity manager than using form itself, so your entity that you will get from form will not be managed. So, better approach will be modify entity return by $event->getData() inside submit event, but I do not feel you get one because of composite key. Also note, that you can get form data in submit event by calling it in such way
That was not clear for me when I first faced such issue so may be helpful for you.