如何使用表单事件动态修改表单
编辑本页警告:您正在浏览的文档欧宝官网下载appob娱乐下载Symfony 2.4,现已不再维护。
读本页的更新版本用于Syob娱乐下载mfony 6.2(当前稳定版本)。
如何使用表单事件动态修改表单
通常情况下,表单是不能静态创建的。在这篇文章中,你将学习如何基于三个常见的用例定制你的表单:
-
- 例如:您有一个“Product”表单,需要修改/添加/删除一个字段
- 基于正在编辑的底层Product上的数据。
示例:您创建了一个“好友消息”表单,需要构建一个下拉列表,其中仅包含与当前的经过身份验证的用户。
例如:在注册表单上,您有一个“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 24 25 26 27
/ / src / Acme / DemoBundle /形式/类型/ ProductType.php名称空间Acme\DemoBundle\形式\类型;使用ob娱乐下载\组件\形式\AbstractType;使用ob娱乐下载\组件\形式\FormBuilderInterface;使用ob娱乐下载\组件\OptionsResolver\OptionsResolverInterface;类ProductType扩展AbstractType{公共函数buildForm(FormBuilderInterface$构建器数组,$选项){$构建器->add (“名字”);$构建器->add (“价格”);}公共函数setDefaultOptions(OptionsResolverInterface$解析器){$解析器->setDefaults (数组(“data_class”= >“Acme \ DemoBundle \实体\产品”));}公共函数getName(){返回“产品”;}}
请注意
如果您还不熟悉这段代码,那么您可能需要退一步,首先查看章形式在继续之前。
假设这个表单使用了一个假想的“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 / Acme / DemoBundle /形式/类型/ ProductType.php名称空间Acme\DemoBundle\形式\类型;/ /……使用ob娱乐下载\组件\形式\FormEvent;使用ob娱乐下载\组件\形式\FormEvents;类ProductType扩展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 (“名字”,“文本”);}});}
2.2
将字符串传递到的能力FormInterface:添加在Symfony 2.2中引入。ob娱乐下载
请注意
的FormEvents: PRE_SET_DATA
Line实际上解析为字符串form.pre_set_data
.FormEvents服务于组织目的。它是一个集中的位置,您可以在其中找到所有可用的各种表单事件。控件可以查看表单事件的完整列表FormEvents类。
向表单类添加事件订阅器
对象的可重用性更好,或者如果事件侦听器中有一些沉重的逻辑,您还可以移动用于创建对象的逻辑的名字
字段到事件订阅者:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/ / src / Acme / DemoBundle /形式/类型/ ProductType.php名称空间Acme\DemoBundle\形式\类型;/ /……使用Acme\DemoBundle\形式\EventListener\AddNameFieldSubscriber;类ProductType扩展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 24 25 26
/ / src / Acme / DemoBundle /形式/ EventListener / AddNameFieldSubscriber.php名称空间Acme\DemoBundle\形式\EventListener;使用ob娱乐下载\组件\形式\FormEvent;使用ob娱乐下载\组件\形式\FormEvents;使用ob娱乐下载\组件\EventDispatcher\EventSubscriberInterface;类AddNameFieldSubscriber实现了EventSubscriberInterface{公共静态函数getSubscribedEvents(){//告诉dispatcher你想要监听form.pre_set_data//事件和preSetData方法应该被调用。返回数组(FormEvents::PRE_SET_DATA = >“preSetData”);}公共函数preSetData(FormEvent$事件){$产品=$事件->getData ();$形式=$事件->getForm ();如果(!$产品||零= = =$产品->getId ()) {$形式->add (“名字”,“文本”);}}}
如何基于用户数据动态生成表单
有时,您希望动态生成表单,不仅基于表单中的数据,还基于其他内容——比如来自当前用户的一些数据。假设你有一个社交网站,用户只能给网站上标记为朋友的人发消息。在这种情况下,要发送消息的“选择列表”应该只包含当前用户的好友。
创建表单类型
使用事件监听器,你的表单看起来像这样:
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
/ / src / Acme / DemoBundle /形式/类型/ FriendMessageFormType.php名称空间Acme\DemoBundle\形式\类型;使用ob娱乐下载\组件\形式\AbstractType;使用ob娱乐下载\组件\形式\FormBuilderInterface;使用ob娱乐下载\组件\形式\FormEvents;使用ob娱乐下载\组件\形式\FormEvent;使用ob娱乐下载\组件\安全\核心\SecurityContext;使用ob娱乐下载\组件\OptionsResolver\OptionsResolverInterface;类FriendMessageFormType扩展AbstractType{公共函数buildForm(FormBuilderInterface$构建器数组,$选项){$构建器->add (“主题”,“文本”)->add (“身体”,“文本区域”);$构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent$事件){/ /……添加当前应用程序用户的好友选择列表});}公共函数getName(){返回“acme_friend_message”;}公共函数setDefaultOptions(OptionsResolverInterface$解析器){}}
现在的问题是获取当前用户并创建一个只包含该用户的朋友的选择字段。
幸运的是,在表单中注入服务非常容易。这可以在构造函数中完成:
1 2 3 4 5 6
私人$securityContext;公共函数__construct(SecurityContext$securityContext){$这->securityContext =$securityContext;}
请注意
您可能想知道,既然您已经访问了User(通过安全上下文),为什么不直接在中使用它呢buildForm
并忽略事件侦听器?这是因为这样做buildForm
方法将导致修改整个表单类型,而不仅仅是修改这个表单实例。这通常不是问题,但从技术上讲,单个表单类型可以用于单个请求来创建多个表单或字段。
定制表单类型
现在您已经有了所有的基础,您可以利用SecurityContext
并填写监听器逻辑:
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
/ / src / Acme / DemoBundle / FormType / FriendMessageFormType.php使用ob娱乐下载\组件\安全\核心\SecurityContext;使用学说\ORM\EntityRepository;/ /……类FriendMessageFormType扩展AbstractType{私人$securityContext;公共函数__construct(SecurityContext$securityContext){$这->securityContext =$securityContext;}公共函数buildForm(FormBuilderInterface$构建器数组,$选项){$构建器->add (“主题”,“文本”)->add (“身体”,“文本区域”);//获取用户,快速检查是否存在$用户=$这->securityContext->getToken ()->getUser ();如果(!$用户){扔新\ LogicException (没有经过身份验证的用户,FriendMessageFormType不能使用!);}$构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent$事件)使用($用户){$形式=$事件->getForm ();$formOptions=数组(“类”= >“Acme \ DemoBundle \实体\用户”,“属性”= >“fullName”,“query_builder”= >函数(EntityRepository$呃)使用($用户){//创建一个自定义查询//返回$er->createQueryBuilder('u')->addOrderBy('fullName', 'DESC');//或调用存储库上返回查询生成器的方法$er是你的UserRepository的一个实例//返回$er->createOrderByFullNameQueryBuilder();});//创建字段,这类似于$builder->add()//字段名称,字段类型,数据,选项$形式->add (“朋友”,“实体”,$formOptions);});}/ /……}
请注意
的多个
而且扩大
表单选项将默认为false,因为好友字段的类型为实体
.
使用表单
我们的表单现在已经可以使用了,有两种可能的方法可以在控制器中使用它:
A)手动创建它,并记得将安全上下文传递给它;
或
B)将其定义为服务。
a)手动创建表单
这很简单,而且可能是更好的方法,除非你在很多地方使用你的新表单类型,或者将它嵌入到其他表单中:
12 3 4 5 6 7 8 9 10 11 12
类FriendMessageController扩展控制器{公共函数newAction(请求$请求){$securityContext=$这->容器->get (“security.context”);$形式=$这->createForm (新FriendMessageFormType ($securityContext));/ /……}}
b)将表单定义为服务
要将表单定义为服务,只需创建一个普通服务,然后用依赖注入标签.
- YAML
- XML
- PHP
1 2 3 4 5 6 7
# app / config / config.yml服务:acme.form.friend_message:类:Acme \ DemoBundle \ \ \ FriendMessageFormType型形式参数:(“@security.context”)标签:-{名称:form.type,别名:acme_friend_message}
1 2 3 4 5 6 7
< !--app/config/config.xml --><服务><服务id=“acme.form.friend_message”类=“Acme \ DemoBundle \ \ \ FriendMessageFormType型”><论点类型=“服务”id=“security.context”/><标签的名字=“form.type”别名=“acme_friend_message”/>服务>服务>
1 2 3 4 5 6 7 8
/ / app / config / config . php$定义=新定义(“Acme \ DemoBundle \ \ \ FriendMessageFormType型的形式);$定义->addTag (“form.type”,数组(“别名”= >“acme_friend_message”));$容器->setDefinition (“acme.form.friend_message”,$定义,数组(“security.context”));
如果你希望从控制器或任何其他可以访问表单工厂的服务中创建它,那么你可以使用:
1 2 3 4 5 6 7 8 9 10 11
使用ob娱乐下载\组件\DependencyInjection\ContainerAware;类FriendMessageController扩展ContainerAware{公共函数newAction(请求$请求){$形式=$这->get (“form.factory”)->创建(“acme_friend_message”);/ /……}}
如果你扩展ob娱乐下载
类,您可以简单地调用:
1
$形式=$这->createForm (“acme_friend_message”);
您还可以轻松地将表单类型嵌入到另一个表单中:
1 2 3 4 5
//在一些其他的“表单类型”类公共函数buildForm(FormBuilderInterface$构建器数组,$选项){$构建器->add (“消息”,“acme_friend_message”);}
提交表单的动态生成
另一种可能出现的情况是,您希望定制特定于用户提交的数据的表单。例如,假设您有一个体育聚会的注册表单。有些事件将允许您指定您在字段中的首选位置。这是一个选择
字段为例。然而,可能的选择将取决于每项运动。足球会有进攻、防守、守门员等等……棒球会有投手,但不会有守门员。您需要正确的选项才能通过验证。
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
/ / src / Acme / DemoBundle /形式/类型/ SportMeetupType.php名称空间Acme\DemoBundle\形式\类型;使用ob娱乐下载\组件\形式\AbstractType;使用ob娱乐下载\组件\形式\FormBuilderInterface;使用ob娱乐下载\组件\形式\FormEvent;使用ob娱乐下载\组件\形式\FormEvents;/ /……类SportMeetupType扩展AbstractType{公共函数buildForm(FormBuilderInterface$构建器数组,$选项){$构建器->add (“运动”,“实体”,数组(“类”= >“AcmeDemoBundle:运动”,“empty_value”= >”,));$构建器->addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent$事件){$形式=$事件->getForm ();//这将是你的实体,即SportMeetup$数据=$事件->getData ();$体育运动=$数据->getSport ();$职位=零= = =$体育运动吗?数组():$体育运动->getAvailablePositions ();$形式->add (“位置”,“实体”,数组(“类”= >“AcmeDemoBundle:位置”,“empty_value”= >”,“选择”= >$职位));});}/ /……}
当您构建这个表单第一次显示给用户时,这个示例可以完美地工作。
但是,在处理表单提交时,事情变得更加困难。这是因为PRE_SET_DATA
事件告诉我们你开始使用的数据(例如一个空的SportMeetup
对象),不提交的数据。
在表单上,我们通常可以听到以下事件:
PRE_SET_DATA
POST_SET_DATA
PRE_SUBMIT
提交
POST_SUBMIT
2.3
的事件PRE_SUBMIT
,提交
而且POST_SUBMIT
是在Symfony 2.3中引入的。ob娱乐下载以前,它们被命名为PRE_BIND
,绑定
而且POST_BIND
.
2.2.6款
的行为POST_SUBMIT
事件在2.2.6中略有变化,下面的示例使用了它。
关键是加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
/ / src / Acme / DemoBundle /形式/类型/ SportMeetupType.php名称空间Acme\DemoBundle\形式\类型;/ /……使用ob娱乐下载\组件\形式\FormInterface;使用Acme\DemoBundle\实体\体育运动;类SportMeetupType扩展AbstractType{公共函数buildForm(FormBuilderInterface$构建器数组,$选项){$构建器->add (“运动”,“实体”,数组(“类”= >“AcmeDemoBundle:运动”,“empty_value”= >”));;$formModifier=函数(FormInterface$形式、运动$体育运动= null){$职位=零= = =$体育运动吗?数组():$体育运动->getAvailablePositions ();$形式->add (“位置”,“实体”,数组(“类”= >“AcmeDemoBundle:位置”,“empty_value”= >”,“选择”= >$职位));};$构建器->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 (),$体育运动);});}/ /……}
您可以看到,您需要侦听这两个事件并使用不同的回调,这仅仅是因为在两个不同的场景中,您可以使用的数据在不同的事件中可用。除此之外,侦听器总是在给定的表单上执行完全相同的操作。
仍然缺少的一部分是在选择运动后对表单进行客户端更新。这应该通过对应用程序进行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
/ / src / Acme / DemoBundle /控制器/ MeetupController.php名称空间Acme\DemoBundle\控制器;使用ob娱乐下载\包\FrameworkBundle\控制器\控制器;使用ob娱乐下载\组件\HttpFoundation\请求;使用Acme\DemoBundle\实体\SportMeetup;使用Acme\DemoBundle\形式\类型\SportMeetupType;/ /……类MeetupController扩展控制器{公共函数createAction(请求$请求){$meetup=新SportMeetup ();$形式=$这->createForm (新SportMeetupType (),$meetup);$形式->handleRequest ($请求);如果($形式->isValid ()) {/ /……保存聚会,重定向等等。}返回$这->呈现(“AcmeDemoBundle: Meetup: create.html.twig”,数组(“形式”= >$形式->createView ()));}/ /……}
相关联的模板使用一些JavaScript来更新位置
属性中的当前所选内容体育运动
字段:
- 嫩枝
- PHP
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
{# src / Acme / DemoBundle /资源/视图/聚会/ create.html。树枝#}{{form_start(form)}}{{form_row(form.sport)}}{#
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
< !--src/Acme/DemoBundle/Resources/views/Meetup/create.html.php --><?php回声$视图[“形式”]->开始($形式)? ><?php回声$视图[“形式”]->行($形式[“运动”])? >< !-- <?php回声$视图[“形式”]->行($形式[“位置”])? >< !-- < !--...--><?php回声$视图[“形式”]->结束($形式)? ><脚本>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字段现在显示适当的位置。}});});脚本>
提交整个表单的主要好处是只提取更新后的内容位置
字段是不需要额外的服务器端代码;上面生成提交表单的所有代码都可以重用。
抑制表单验证
要抑制表单验证,可以使用POST_SUBMIT
事件,并防止ValidationListener因为被召唤。
需要这样做的原因是即使你设置了group_validation
来假
仍然执行一些完整性检查。例如,一个上传的文件仍然会检查它是否太大,表单仍然会检查是否提交了不存在的字段。要禁用所有这些,请使用监听器:
1 2 3 4 5 6 7 8 9 10 11
使用ob娱乐下载\组件\形式\FormBuilderInterface;使用ob娱乐下载\组件\形式\FormEvents;公共函数buildForm(FormBuilderInterface$构建器数组,$选项){$构建器->addEventListener (FormEvents::POST_SUBMIT,函数($事件){$事件->stopPropagation ();},900);//总是设置优先级高于ValidationListener/ /……}
谨慎
通过这样做,您可能会不小心禁用表单验证以外的东西,因为POST_SUBMIT
事件可能有其他侦听器。