Symfony表单DataTransformer实现省市区联动报错求助
解决Symfony国家>省>城市联动表单的TransformationFailedException问题
在构建国家→省(Dpt)→城市的动态联动表单时,选择Dpt后触发TransformationFailedException,Symfony Profiler提示**"The selected choice is invalid."**,核心原因是代码中存在参数传递错误、多余转换器、变量名混淆以及JS请求数据不完整等问题,以下是针对性修复方案:
问题根源梳理
PRE_SET_DATA事件传递错误参数,将实体的name而非关联对象传给表单修改器- 为
dpt字段额外添加了不必要的DataTransformer,与EntityType自带的转换逻辑冲突 - Dpt事件监听中变量名混淆,将
dpt错误命名为city,导致无法正确获取城市列表 - JS仅传递单个字段值,未携带完整表单上下文,Symfony无法正确重建表单状态
具体修复步骤
1. 修正PRE_SET_DATA事件的参数传递
修改表单中两个PRE_SET_DATA事件的回调,传递正确的关联实体:
// 国家关联的PRE_SET_DATA事件 $builder->addEventListener( FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formModifier): void { $data = $event->getData(); // 传递Country实体而非name属性 $formModifier($event->getForm(), $data->getCountry()); } ); // 省关联的PRE_SET_DATA事件 $builder->addEventListener( FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formModifiers): void { $data = $event->getData(); // 传递Dpt实体而非name属性 $formModifiers($event->getForm(), $data->getDpt()); } );
2. 移除多余的DataTransformer
EntityType本身已具备将ID转换为实体对象的能力,额外添加转换器会引发重复转换异常:
// 删除以下代码行 // $builder->get('dpt')->addModelTransformer($this->transformer);
若CityToDpt转换器无其他用途,可直接删除该类文件。
3. 修正Dpt事件监听中的变量名错误
调整PRE_SUBMIT和POST_SUBMIT事件的变量名与逻辑,区分提交阶段的数据类型:
$builder->get('dpt')->addEventListener( FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($formModifiers): void { // PRE_SUBMIT阶段获取的是原始提交的ID字符串 $dptId = $event->getData(); $dpt = $this->dptRepository->find($dptId); $formModifiers($event->getForm()->getParent(), $dpt); } ); $builder->get('dpt')->addEventListener( FormEvents::POST_SUBMIT, function (FormEvent $event) use ($formModifiers): void { // POST_SUBMIT阶段已完成转换,获取的是Dpt实体 $dpt = $event->getForm()->getData(); $formModifiers($event->getForm()->getParent(), $dpt); } );
4. 优化JS请求,传递完整表单数据
修改JS逻辑,使用FormData传递完整表单上下文,确保Symfony能正确重建表单:
const updateForm = async (data, url, method) => { const req = await fetch(url, { method: method, body: data // 无需手动设置Content-Type,FormData会自动处理 }); return await req.text(); }; const changeOptions = async (e) => { const formData = new FormData(form); formData.set(e.target.name, e.target.value); const html = parseTextToHtml(await updateForm(formData, form.action, form.method)); form_select_dpt.innerHTML = html.getElementById('ticket_form_dpt').innerHTML; // 切换国家时重置城市选择框 form_select_city.innerHTML = '<option value="">Choisir une ville</option>'; }; const changeOption = async (e) => { const formData = new FormData(form); formData.set(e.target.name, e.target.value); const html = parseTextToHtml(await updateForm(formData, form.action, form.method)); form_select_city.innerHTML = html.getElementById('ticket_form_city').innerHTML; };
5. 避免重复添加表单字段
在表单修改器中先移除旧字段再添加新字段,防止字段重复:
$formModifier = function (FormInterface $form, Country $country = null) { $dpts = $country === null ? [] : $this->dptRepository->findDptByCountry($country); if ($form->has('dpt')) { $form->remove('dpt'); } $form->add('dpt', EntityType::class, [ 'class' => Dpt::class, 'placeholder' => 'Choisir un département', 'choices' => $dpts, 'choice_label' => 'name', 'label' => "Département", ]); }; $formModifiers = function (FormInterface $form, Dpt $dpt = null) { $cities = $dpt === null ? [] : $this->cityRepository->findCityByDpt($dpt); if ($form->has('city')) { $form->remove('city'); } $form->add('city', EntityType::class, [ 'class' => City::class, 'placeholder' => 'Choisir une ville', 'choices' => $cities, 'choice_label' => 'name', ]); };
内容的提问来源于stack exchange,提问作者Gigi_IT
相关产品推荐
相关产品推荐

