You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony表单DataTransformer实现省市区联动报错求助

解决Symfony国家>省>城市联动表单的TransformationFailedException问题

在构建国家→省(Dpt)→城市的动态联动表单时,选择Dpt后触发TransformationFailedException,Symfony Profiler提示**"The selected choice is invalid."**,核心原因是代码中存在参数传递错误、多余转换器、变量名混淆以及JS请求数据不完整等问题,以下是针对性修复方案:

问题根源梳理

  1. PRE_SET_DATA事件传递错误参数,将实体的name而非关联对象传给表单修改器
  2. 为dpt字段额外添加了不必要的DataTransformer,与EntityType自带的转换逻辑冲突
  3. Dpt事件监听中变量名混淆,将dpt错误命名为city,导致无法正确获取城市列表
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 19:14:58