如何使用表单事件动态修改表单

编辑本页

警告:您正在浏览的文档欧宝官网下载appob娱乐下载Symfony 5.1,现已不再维护。

本页的更新版本用于Syob娱乐下载mfony 6.2(当前稳定版本)。

如何使用表单事件动态修改表单

通常情况下,表单是不能静态创建的。在本文中,您将学习如何基于三个常见的用例定制表单:

  1. 如何使用表单事件动态修改表单

    例如:您有一个“Product”表单,需要根据正在编辑的底层Product上的数据修改/添加/删除字段。

  2. 如何使用表单事件动态修改表单

    示例:您创建了一个“好友消息”表单,需要构建一个下拉列表,其中仅包含与当前的经过身份验证的用户。

  3. 如何使用表单事件动态修改表单

    例如:在注册表单上,您有一个“country”字段和一个“state”字段,它们应该根据“country”字段中的值动态填充。

如果您希望了解表单事件背后的更多基础知识,可以查看形成事件欧宝官网下载app文档。

根据基础数据定制表单

在开始动态表单生成之前,请记住一个裸表单类是什么样的:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/ / src /形式/类型/ ProductType.php名称空间应用程序形式类型使用应用程序实体产品使用ob娱乐下载组件形式AbstractType使用ob娱乐下载组件形式FormBuilderInterface使用ob娱乐下载组件OptionsResolverOptionsResolverProductType扩展AbstractType公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“名字”);构建器->add (“价格”);}公共函数configureOptions(OptionsResolver解析器无效解析器->setDefaults ([“data_class”= >产品::类,]);}}

请注意

如果您还不熟悉这段代码,那么您可能需要退一步,首先查看形式的文章在继续之前。

假设这个表单使用了一个假想的“Product”类,这个类只有两个属性(“name”和“price”)。从这个类生成的表单无论是否正在创建一个新的Product或是否正在编辑一个现有的产品(例如从数据库获取的产品)看起来都是完全相同的。

假设现在,您不希望用户能够更改的名字值。要做到这一点,您可以依赖Symfonyob娱乐下载EventDispatcher组件系统用于分析对象上的数据并根据Product对象的数据修改表单。在本文中,您将学习如何将这种级别的灵活性添加到表单中。

向表单类添加事件侦听器

所以,不直接加的名字小部件,创建特定字段的责任被委托给事件监听器:

12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20
/ / src /形式/类型/ ProductType.php名称空间应用程序形式类型/ /……使用ob娱乐下载组件形式FormEvent使用ob娱乐下载组件形式FormEventsProductType扩展AbstractType公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“价格”);构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent事件/ /……如果需要,添加名称字段});}/ /……

目标是创建一个的名字只有如果潜在的产品对象是新的(例如没有持久化到数据库中)。基于此,事件监听器可能看起来像下面这样:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/ /……公共函数buildForm(FormBuilderInterface构建器数组,选项无效/ /……构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent事件产品事件->getData ();形式事件->getForm ();//检查Product对象是否为new//如果没有数据传递到表单,则数据为"null"。//这应该被认为是一个新的"Product"如果(!产品||= = =产品->getId ()) {形式->add (“名字”, TextType::类);}});}

请注意

FormEvents: PRE_SET_DATALine实际上解析为字符串form.pre_set_dataFormEvents服务于组织目的。它是一个集中的位置,您可以在其中找到所有可用的各种表单事件。控件可以查看表单事件的完整列表FormEvents类。

向表单类添加事件订阅器

对象的可重用性更好,或者如果事件侦听器中有一些沉重的逻辑,您还可以移动用于创建对象的逻辑的名字字段到事件订阅者

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/ / src /形式/ EventListener / AddNameFieldSubscriber.php名称空间应用程序形式EventListener使用ob娱乐下载组件EventDispatcherEventSubscriberInterface使用ob娱乐下载组件形式扩展核心类型TextType使用ob娱乐下载组件形式FormEvent使用ob娱乐下载组件形式FormEventsAddNameFieldSubscriber实现了EventSubscriberInterface公共静态函数getSubscribedEvents()数组//告诉dispatcher你想要监听form.pre_set_data//事件和preSetData方法应该被调用。返回[FormEvents::PRE_SET_DATA = >“preSetData”];}公共函数preSetData(FormEvent事件无效产品事件->getData ();形式事件->getForm ();如果(!产品||= = =产品->getId ()) {形式->add (“名字”, TextType::类);}}}

太棒了!现在在你的表单类中使用它:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/ / src /形式/类型/ ProductType.php名称空间应用程序形式类型/ /……使用应用程序形式EventListenerAddNameFieldSubscriberProductType扩展AbstractType公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“价格”);构建器->addEventSubscriber (AddNameFieldSubscriber ());}/ /……

如何基于用户数据动态生成表单

有时,您希望动态生成表单,不仅基于表单中的数据,还基于其他内容——比如来自当前用户的一些数据。假设你有一个社交网站,用户只能给网站上标记为朋友的人发消息。在这种情况下,要发送消息的“选择列表”应该只包含当前用户的好友。

创建表单类型

使用事件监听器,你的表单看起来像这样:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/ / src /形式/类型/ FriendMessageFormType.php名称空间应用程序形式类型使用ob娱乐下载组件形式AbstractType使用ob娱乐下载组件形式扩展核心类型TextareaType使用ob娱乐下载组件形式扩展核心类型TextType使用ob娱乐下载组件形式FormBuilderInterface使用ob娱乐下载组件形式FormEvent使用ob娱乐下载组件形式FormEventsFriendMessageFormType扩展AbstractType公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“主题”, TextType::类)->add (“身体”, TextareaType::类);构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent事件/ /……添加当前应用程序用户的好友选择列表});}}

现在的问题是获取当前用户并创建一个只包含该用户的朋友的选择字段。这可以通过注射安全Service转换为form类型,这样你就可以得到当前用户对象:

12 3 4 5 6 7 8 9 10 11 12 13 14
使用ob娱乐下载组件安全核心安全/ /……FriendMessageFormType扩展AbstractType私人安全公共函数__construct(安全安全->安全=安全;}/ /…….

定制表单类型

现在你已经有了所有的基础,你可以使用安全助手的特性来填充监听器逻辑:

12 34 56 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
/ / src /形式/类型/ FriendMessageFormType.php名称空间应用程序形式类型使用应用程序实体用户使用学说ORMEntityRepository使用ob娱乐下载学说形式类型EntityType使用ob娱乐下载组件形式扩展核心类型TextareaType使用ob娱乐下载组件形式扩展核心类型TextType使用ob娱乐下载组件安全核心安全/ /……FriendMessageFormType扩展AbstractType私人安全公共函数__construct(安全安全->安全=安全;}公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“主题”, TextType::类)->add (“身体”, TextareaType::类);//获取用户,快速检查是否存在用户->安全->getUser ();如果(!用户) {\ LogicException (没有经过身份验证的用户,FriendMessageFormType不能使用!);}构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent事件使用用户如果= = !事件->getData ()->getFriend ()) {//我们不需要添加friend字段,因为//该消息将被发送给固定的朋友返回;}形式事件->getForm ();formOptions= (“类”= >用户::类,“choice_label”= >“fullName”“query_builder”= >函数(UserRepositoryuserRepository使用用户//在存储库上调用一个返回查询生成器的方法//返回$userRepository->createFriendsQueryBuilder($user);});//创建字段,这类似于$builder->add()//字段名称,字段类型,字段选项形式->add (“朋友”, EntityType::类,formOptions);});}/ /……

请注意

你可能会想,现在你可以访问用户对象,为什么不直接用在buildForm ()并忽略事件侦听器?这是因为这样做buildForm ()方法将导致修改整个表单类型,而不仅仅是修改这个表单实例。这通常不是问题,但从技术上讲,单个表单类型可以用于单个请求来创建多个表单或字段。

使用表单

如果你在用默认的服务。yaml的配置,您的表单可以使用了自动装配而且可以使用autoconfigure.否则,将表单类注册为服务而且标记它form.type标签。

在控制器中,像往常一样创建表单:

12 3 4 5 6 7 8 9 10 11 12 13
使用ob娱乐下载FrameworkBundle控制器AbstractController使用ob娱乐下载组件HttpFoundation请求使用ob娱乐下载组件HttpFoundation响应FriendMessageController扩展AbstractController公共函数(请求请求响应形式->createForm (FriendMessageFormType::类);/ /……}}

你也可以将表单类型嵌入到另一个表单中:

1 2 3 4 5
//在一些其他的“表单类型”类公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“消息”, FriendMessageFormType::类);}

提交表单的动态生成

另一种可能出现的情况是,您希望定制特定于用户提交的数据的表单。例如,假设您有一个体育聚会的注册表单。有些事件将允许您指定您在字段中的首选位置。这是一个选择字段为例。然而,可能的选择将取决于每一项运动。足球会有进攻、防守、守门员等等……棒球会有投手,但不会有守门员。您需要正确的选项才能通过验证。

meetup作为一个实体字段传递给表单。所以我们可以这样看待每一项运动:

12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
/ / src /形式/类型/ SportMeetupType.php名称空间应用程序形式类型使用ob娱乐下载学说形式类型EntityType使用ob娱乐下载组件形式AbstractType使用ob娱乐下载组件形式FormBuilderInterface使用ob娱乐下载组件形式FormEvent使用ob娱乐下载组件形式FormEvents/ /……SportMeetupType扩展AbstractType公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“运动”, EntityType::类,“类”= >“应用程序实体\ \运动”“占位符”= >,]);构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent事件形式事件->getForm ();//这将是你的实体,即SportMeetup数据事件->getData ();体育运动数据->getSport ();职位= = =体育运动?[]:体育运动->getAvailablePositions ();形式->add (“位置”, EntityType::类,“类”= >“App \实体\位置”“占位符”= >“选择”= >职位]);});}/ /……

当您构建这个表单第一次显示给用户时,这个示例可以完美地工作。

但是,在处理表单提交时,事情变得更加困难。这是因为PRE_SET_DATA事件告诉我们你开始使用的数据(例如一个空的SportMeetup对象),提交的数据。

在表单上,我们通常可以听到以下事件:

  • PRE_SET_DATA
  • POST_SET_DATA
  • PRE_SUBMIT
  • 提交
  • POST_SUBMIT

关键是加aPOST_SUBMIT监听新字段所依赖的字段。如果你加上POST_SUBMIT窗体子窗体的侦听器(例如。体育运动),并向父表单添加新的子表单,form组件将自动检测新字段并将其映射到提交的客户端数据。

该类型现在看起来像:

12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
/ / src /形式/类型/ SportMeetupType.php名称空间应用程序形式类型使用应用程序实体体育运动使用ob娱乐下载学说形式类型EntityType使用ob娱乐下载组件形式FormInterface/ /……SportMeetupType扩展AbstractType公共函数buildForm(FormBuilderInterface构建器数组,选项无效构建器->add (“运动”, EntityType::类,“类”= >“应用程序实体\ \运动”“占位符”= >,]);formModifier函数(FormInterface形式、运动体育运动= null)职位= = =体育运动?[]:体育运动->getAvailablePositions ();形式->add (“位置”, EntityType::类,“类”= >“App \实体\位置”“占位符”= >“选择”= >职位]);};构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent事件使用formModifier//这将是你的实体,即SportMeetup数据事件->getData ();formModifier事件->getForm (),数据->getSport ());});构建器->get (“运动”->addEventListener (FormEvents::POST_SUBMIT,函数(FormEvent事件使用formModifier//这里很重要的是获取$event->getForm()->getData(),作为// $event->getData()将为您提供客户端数据(即ID)体育运动事件->getForm ()->getData ();//由于我们已经将监听器添加到子对象中,所以我们必须传递//回调函数的父函数!formModifier事件->getForm ()->getParent (),体育运动);});}/ /……

您可以看到,您需要侦听这两个事件并使用不同的回调,这仅仅是因为在两个不同的场景中,您可以使用的数据在不同的事件中可用。除此之外,侦听器总是在给定的表单上执行完全相同的操作。

提示

FormEvents: POST_SUBMIT事件不允许修改侦听器绑定的窗体,但允许修改其父窗体。

仍然缺少的一部分是在选择运动后对表单进行客户端更新。这应该通过对应用程序进行AJAX回调来处理。假设你有一个运动聚会创建控制器:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/ / src /控制器/ MeetupController.php名称空间应用程序控制器使用应用程序实体SportMeetup使用应用程序形式类型SportMeetupType使用ob娱乐下载FrameworkBundle控制器AbstractController使用ob娱乐下载组件HttpFoundation请求使用ob娱乐下载组件HttpFoundation响应/ /……MeetupController扩展AbstractController公共函数创建(请求请求响应meetupSportMeetup ();形式->createForm (SportMeetupType::类,meetup);形式->handleRequest (请求);如果形式->isSubmitted () & &形式->isValid ()) {/ /……保存聚会,重定向等等。返回->呈现(“meetup / create.html.twig”, (“形式”= >形式->createView ()));}/ /……

相关联的模板使用一些JavaScript来更新位置属性中的当前所选内容体育运动字段:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
{/聚会/ create.html #模板。树枝#}{{form_start(form)}}{{form_row(form.sport)}}{# {#……#}{{form_end(form)}}<脚本>var$sport = $(“# meetup_sport”);//当sports被选中时…sport.change美元(函数/ /……检索相应的表单。var$form = $() .closest (“形式”);//模拟表单数据,但只包括所选的运动值var数据= {};数据(sport.attr美元(“名字”)] = $sport.val();//通过AJAX向表单的操作路径提交数据。. ajax({美元url: $ form.attr (“行动”),类型: $ form.attr (“方法”),数据:数据,成功函数超文本标记语言//替换当前位置字段…$ (“# meetup_position”) .replaceWith (/ /……从AJAX响应返回一个。美元(html) (“# meetup_position”));// Position字段现在显示适当的位置。}});});脚本>

提交整个表单的主要好处是只提取更新后的内容位置字段是不需要额外的服务器端代码;上面生成提交表单的所有代码都可以重用。

此工作,包括代码示例,是根据创作共用BY-SA 3.0许可证。