Symfony 5.1自定义form_widget后表单提交丢失users数据求助
你遇到的提交数据缺失,本质是自定义form_widget时完全覆盖了原有表单控件的渲染逻辑,没有输出携带正确name属性的表单输入元素。
Symfony表单的提交机制完全依赖页面渲染出的input、select、textarea等元素的name属性匹配服务端的表单结构。你当前的自定义块只输出了纯文本、表格标签和删除链接,没有任何可以承载提交数据的原生表单控件,浏览器提交表单时自然不会携带这部分的任何数据,才会出现提交数据缺失的问题。
你之前尝试单独给name、email字段做自定义widget仍然出问题,也是一样的逻辑:只要自定义块里没输出带正确name的控件,数据就提交不上来。你之前手动在PRE_SUBMIT事件补数据的方案之所以不能同步删除操作,也是因为页面上根本没有和删除条目关联的表单控件,前端的删除操作根本不会触发任何表单字段的状态变更,服务端完全感知不到用户删了哪条。
你的核心诉求是:在表格里展示用户纯文本信息、不显示冗余输入框、同时保留表单的增删同步能力,不需要硬套默认渲染的输入框样式,按下面的步骤调整即可:
1. 自定义块里保留必要的表单控件渲染
不要在自定义_invitation_users_widget块里完全丢弃表单渲染逻辑,要在每个用户行里输出可承载提交数据的元素,同时不要让这些默认控件显示成普通输入框。
调整后的自定义Twig块示例:
{% block _invitation_users_widget %} {% for user_entry in form.children %} <tr data-user-id="{{ user_entry.vars.data.id }}"> {# 展示纯文本,替代默认输入框的可见效果 #} <td>{{ user_entry.vars.data.name }}</td> <td>{{ user_entry.vars.data.email }}</td> <td class="text-center"> {# 重点:渲染隐藏的表单字段,保证提交时携带正确的name和值 #} {{ form_widget(user_entry.name) }} {{ form_widget(user_entry.email) }} {# 渲染CollectionType自带的删除标记字段,配合allow_delete逻辑 #} {{ form_widget(user_entry._token) }} {# 原有的删除入口 #} <a href="#" class="btn-delete-user"> <i class="fas fa-user-minus"></i> </a> </td> </tr> {% endfor %} {% endblock %}
注意:这里循环遍历的是
form.children(每个嵌入的UserType表单实例),不是之前的value(原始业务数据集合),只有遍历表单实例,才能拿到每个子字段的渲染入口,输出带正确name属性的控件,让浏览器能识别为可提交的表单数据。
2. 隐藏不需要显示的默认输入框
在表单主题文件里补充对应字段的渲染逻辑,让原来的name、email字段渲染成隐藏input,不要在页面上显示成可见的输入框:
{% block user_name_widget %} <input type="hidden" {{ block('widget_attributes') }} value="{{ value }}"> {% endblock %} {% block user_email_widget %} <input type="hidden" {{ block('widget_attributes') }} value="{{ value }}"> {% endblock %}
调整后页面上不会出现冗余的输入框,同时提交时,这些字段的name、值会正常被浏览器携带发送,不会出现数据缺失。
3. 配合简单JS实现删除逻辑的同步
你已经在表单配置里开启了allow_delete,Symfony的CollectionType会在每个嵌入条目里自动生成隐藏的_delete标记字段,只需要给删除按钮加几行简单JS:点击时移除当前行的DOM元素,同时把对应行的_delete隐藏input的值设为1。这样表单提交时,服务端就能正确识别用户要删除的条目,和默认渲染的删除逻辑完全一致,根本不需要手动在PRE_SUBMIT事件里补数据。
- 只要是
mapped=true、需要服务端接收的表单字段,无论怎么自定义渲染,都必须保证DOM里有和表单结构完全对应的带正确name属性的控件,否则Symfony根本识别不到提交数据。 - 自定义CollectionType渲染时,不要直接遍历
value(原始业务数据)做纯HTML输出:这样拿不到子表单的实例,自然也没法输出正确的带name的控件,遍历form.children才是表单渲染的正确入口。
内容的提问来源于stack exchange,提问作者Irek

