如何使用表单动态修改表单事件吗
编辑该页面警告:你浏览的文档欧宝官网下载appob娱乐下载Symfony 2.1,不再维护。
读这个页面的更新版本Symfob娱乐下载ony 6.3(当前的稳定版本)。
如何使用表单动态修改表单事件吗
通常,一种不能创建静态。在这个条目,您将学习如何定制表单基于三种常见的用例:
例如:你有一个“产品”的形式和需要修改/添加/删除字段根据底层产品正在编辑的数据。
例子:创建一个“朋友消息”的形式,需要建立一个下拉,只包含用户的朋友当前的经过身份验证的用户。
例子:在注册表单,你有一个“国家”字段和一个“状态”字段应该根据值动态填充的“国家”。
基于底层数据的定制表单
跳跃到动态表单生成之前,让我们快速回顾一下裸露的形式类是什么样子:
1 2 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(){返回“产品”;}}
请注意
如果这个特定部分的代码不熟悉你,你可能需要退一步,首先检查章形式在继续之前。
假定现在这种形式利用一个虚构的“产品”类,只有两个属性(“name”和“价格”)。形式从这个类会生成相同的无论如果正在创建一个新产品或现有产品正在编辑(如从数据库中取出的产品)。
现在,假设你不希望用户能够改变的名字
一旦创建了对象的值。要做到这一点,你可以依靠Symfony的ob娱乐下载事件调度器系统分析对象和修改表单上的数据基于产品对象的数据。在这个条目,您将学习如何向表单添加这种程度的灵活性。
向表单添加事件订阅者类
而不是直接添加,“名字”通过ProductType形式类部件,我们将创建特定领域的责任委托给一个事件订阅者:
1 2 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美元构建器数组,美元选项){美元订阅者=新AddNameFieldSubscriber (美元构建器- >getFormFactory ());美元构建器- >addEventSubscriber (美元订阅者);美元构建器- >add (“价格”);}/ /……}
事件订阅者通过FormFactory对象在它的构造函数,这样你的新用户能够创建表单小部件一旦派遣形式创建期间事件的通知。
在事件订阅者类
我们的目标是创建一个“名称”字段只有如果底层的产品对象是新的(如尚未持久化到数据库)。基于此,订阅者可能看起来像下面的:
1 2 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 33 34 35 36 37
/ / src / Acme / DemoBundle /形式/ EventListener / AddNameFieldSubscriber.php名称空间Acme\DemoBundle\形式\EventListener;使用ob娱乐下载\组件\形式\FormEvent;使用ob娱乐下载\组件\形式\FormEvents;使用ob娱乐下载\组件\形式\FormFactoryInterface;使用ob娱乐下载\组件\EventDispatcher\EventSubscriberInterface;类AddNameFieldSubscriber实现了EventSubscriberInterface{私人美元工厂;公共函数__construct(FormFactoryInterface美元工厂){美元这- >工厂=美元工厂;}公共静态函数getSubscribedEvents(){/ /告诉调度员form.pre_set_data你想听/ /事件,preSetData方法应该调用。返回数组(FormEvents::PRE_SET_DATA = >“preSetData”);}公共函数preSetData(FormEvent美元事件){美元数据=美元事件- >getData ();美元形式=美元事件- >getForm ();/ /检查是否“新”产品对象/ /如果没有通过任何数据表单,数据是“零”。/ /这应该考虑一个新的“产品”如果(!美元数据| | !美元数据- >getId ()) {美元形式- >add (美元这- >工厂- >createNamed (“名字”,“文本”));}}}
提示
的FormEvents: PRE_SET_DATA
行解析字符串form.pre_set_data
。FormEvents是一个组织的目的。它是一个集中的位置,你可以找到所有可用的各种形式活动。
请注意
您可以查看事件通过形式的完整列表FormEvents类。
如何动态生成形式根据用户数据
有时你想要一种动态生成不仅基于表单的数据还有别的东西,比如一些数据从当前用户。假设您有一个社交网站,用户只能在网站上消息的人是他的朋友。在这种情况下,“选择名单”的消息应该只包含用户的当前用户的朋友。
创建表单类型
使用一个事件侦听器,表单可能看起来像这样:
1 2 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;}
请注意
您可能想知道,现在你可以访问用户(通过安全上下文),为什么不直接使用它buildForm
省略的事件监听器呢?这是因为这样做的buildForm
方法将导致整个表单类型被修改,而不仅仅是这一个表单实例。这可能不会成为一个问题,但技术上单个表单类型可以用于单个请求创建多种形式或字段。
自定义表单类型
现在,你拥有了所有的基本知识的利用securityContext
并填写侦听器逻辑:
1 2 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
/ / 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没有经过身份验证的用户不能使用了!”);}美元工厂=美元构建器- >getFormFactory ();美元构建器- >addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent美元事件)使用(美元用户,美元工厂){美元形式=美元事件- >getForm ();美元formOptions=数组(“类”= >“Acme \ DemoBundle \实体\用户”,“多”= >假,“扩展”= >假,“属性”= >“fullName”,“query_builder”= >函数(EntityRepository美元呃)使用(美元用户){/ /构建一个定制的查询,或在您的存储库调用一个方法(甚至更好!)});/ /创建字段,这是类似$建设者- >添加()/ /字段名、字段类型、数据选项美元形式- >add (美元工厂- >createNamed (“朋友”,“实体”,零,美元formOptions));});}/ /……}
使用表单
我们现在的形式是可以使用,有两种可能的方法来使用它内部的控制器:
一)手动创建它,记得要将安全上下文传递给它;
或
b)作为服务定义它。
手动创建表单
这是非常简单的,可能是一种更好的方法,除非你使用你的新表单类型在许多地方或将它嵌入到其他形式:
1 2 3 4 5 6 7 8 9 10 11 12
类FriendMessageController扩展控制器{公共函数newAction(请求美元请求){美元securityContext=美元这- >容器- >get (“security.context”);美元形式=美元这- >createForm (新FriendMessageFormType (美元securityContext));/ /……}}
b)定义作为一种服务形式
将表单定义为一种服务,就创建一个正常的服务,然后标记依赖注入的标签。
1 2 3 4 5 6 7 8 9
# 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 -->< /span><服务><服务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
类FriendMessageController扩展控制器{公共函数newAction(请求美元请求){美元形式=美元这- >createForm (“acme_friend_message”);/ /……}}
您还可以方便地嵌入表单类型为另一种形式:
1 2 3 4 5
/ /内部其他“表单类型”类公共函数buildForm(FormBuilderInterface美元构建器数组,美元选项){美元构建器- >add (“消息”,“acme_friend_message”);}
动态生成提交的表单
会出现另一种情况是你想定制表单的用户提交的数据。例如,假设你有一个注册表单为运动集会。一些事件将允许您指定您的首选位置。这将是一个选择
领域为例。然而,可能的选择将取决于每个运动。足球会攻击,防御,门将等等……棒球投手,但是不会有守门员。你需要正确的选项被设置为了验证通过。
meetup是作为一个实体传递为表单隐藏字段。所以我们可以访问每个运动是这样的:
1 2 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 /形式/类型/ SportMeetupType.php类SportMeetupType扩展AbstractType{公共函数buildForm(FormBuilderInterface美元构建器数组,美元选项){美元构建器- >add (“number_of_people”,“文本”)- >add (“discount_coupon”,“文本”);美元工厂=美元构建器- >getFormFactory ();美元构建器- >addEventListener (FormEvents::PRE_SET_DATA,函数(FormEvent美元事件)使用(美元用户,美元工厂){美元形式=美元事件- >getForm ();/ /这是你的实体,即SportMeetup美元数据=美元事件- >getData ();美元职位=美元数据- >getSport ()- >getAvailablePositions ();/ /……进行定制表单根据可用的位置});}}
当你建立这种形式显示给用户的第一次,那么这个作品完美的例子。
然而,事情变得更加困难,当你处理表单提交。这是导致PRE_SET_DATA
事件告诉我们你开始的数据(例如,一个空SportMeetup
对象),不提交的数据。
在一个表单,我们通常可以听以下事件:
PRE_SET_DATA
POST_SET_DATA
PRE_BIND
绑定
POST_BIND
当听绑定
和POST_BIND
,它已经“太迟了”修改表单。幸运的是,PRE_BIND
是完美的。不过,还有一个很大的区别$事件- > getData ()
回报每一个事件。具体地说,在PRE_BIND
,$事件- > getData ()
返回用户提交的原始数据。
这可以用来获取SportMeetup
从数据库id和检索它,因为你有一个参考对象管理器(如果使用原则)。最后,你有一个事件订阅者听两个不同的事件,需要一些外部服务和定制表单。在这种情况下,它可能是更好地定义这个作为服务而不是使用一个匿名函数作为回调事件侦听器。
订阅者将现在的样子:
1 2 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 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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
/ / src / Acme / DemoBundle /形式/ EventListener / RegistrationSportListener.php名称空间Acme\DemoBundle\形式\EventListener;使用ob娱乐下载\组件\形式\FormFactoryInterface;使用学说\ORM\EntityManager;使用ob娱乐下载\组件\形式\FormEvent;使用ob娱乐下载\组件\形式\FormEvents;使用ob娱乐下载\组件\EventDispatcher\EventSubscriberInterface;类RegistrationSportListener实现了EventSubscriberInterface{/ * * *@varFormFactoryInterface * /私人美元工厂;/ * * *@varEntityManager * /私人美元om;/ * * *@param工厂FormFactoryInterface * /公共函数__construct(FormFactoryInterface美元工厂,EntityManager美元om){美元这- >工厂=美元工厂;美元这- >om =美元om;}公共静态函数getSubscribedEvents(){返回数组(FormEvents::PRE_BIND = >加固装订的,FormEvents::PRE_SET_DATA = >“preSetData”,);}/ * * *@param事件FormEvent * /公共函数preSetData(FormEvent美元事件){美元meetup=美元事件- >getData ()- >getMeetup ();/ /绑定表单之前,“聚会”将为空如果(零= = =美元meetup){返回;}美元形式=美元事件- >getForm ();美元职位=美元meetup- >getSport ()- >getPositions ();美元这- >customizeForm (美元形式,美元职位);}公共函数加固装订(FormEvent美元事件){美元数据=美元事件- >getData ();美元id=美元数据(“事件”];美元meetup=美元这- >om- >getRepository (“AcmeDemoBundle: SportMeetup”)- >找到(美元id);如果(美元meetup= = =零){美元味精=事件%年代无法找到为你注册的;扔新\异常(sprintf (美元味精,美元id));}美元形式=美元事件- >getForm ();美元职位=美元meetup- >getSport ()- >getPositions ();美元这- >customizeForm (美元形式,美元职位);}受保护的函数customizeForm(美元形式,美元职位){/ /……自定义表单根据职位}}
你可以看到,你需要这两个事件和监听有不同的回调函数仅仅是因为在两个不同的场景,给出的数据,您可以使用不同的格式。除此之外,这类总是对一个给定的形式执行完全相同的事情。
现在您已经设置,注册表单和侦听器服务:
1 2 3 4 5 6 7 8 9
# app / config / config.ymlacme.form.sport_meetup:类:Acme \ SportBundle \ \ \ SportMeetupType型形式参数:(@acme.form.meetup_registration_listener)标签:- - - - - -{名称:form.type,别名:acme_meetup_registration}acme.form.meetup_registration_listener类:Acme \ SportBundle \ \ EventListener \ RegistrationSportListener形式参数:(@form.factory,@doctrine]
1 2 3 4 5 6 7 8 9 10 11
< !- - - - - -- - - - - -app/config/config.xml -->< /span><服务><服务id=“acme.form.sport_meetup”类=“Acme \ SportBundle \ FormType \ SportMeetupType”><论点类型=“服务”id=“acme.form.meetup_registration_listener”/ ><标签的名字=“form.type”别名=“acme_meetup_registration”/ >< /服务><服务id=“acme.form.meetup_registration_listener”类=“Acme \ SportBundle \ \ EventListener \ RegistrationSportListener”><论点类型=“服务”id=“form.factory”/ ><论点类型=“服务”id=“主义”/ >< /服务>< /服务>
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/ / app / config / config . php美元定义=新定义(“Acme \ SportBundle \ \ \ SportMeetupType型的形式);美元定义- >addTag (“form.type”,数组(“别名”= >“acme_meetup_registration”));美元容器- >setDefinition (“acme.form.meetup_registration_listener”,美元定义,数组(“security.context”));美元定义=新定义(“Acme \ SportBundle \ \ EventListener \ RegistrationSportListener形式”);美元容器- >setDefinition (“acme.form.meetup_registration_listener”,美元定义,数组(“form.factory”,“原则”));
在此设置中,RegistrationSportListener
将是一个构造函数参数SportMeetupType
。你可以注册它作为事件订阅者表单:
1 2 3 4 5 6 7 8 9 10 11 12
私人美元registrationSportListener;公共函数__construct(RegistrationSportListener美元registrationSportListener){美元这- >registrationSportListener =美元registrationSportListener;}公共函数buildForm(FormBuilderInterface美元构建器数组,美元选项){/ /……美元构建器- >addEventSubscriber (美元这- >registrationSportListener);}
这应该绑在一起的一切。现在您可以检索表单从控制器,显示给用户,并验证它与正确的选择选项设置为每一个可能的运动,我们的用户注册。
可能仍然是失踪的一块是运动后的客户端更新表单被选中。这应该是通过一个AJAX调用处理您的应用程序。控制器,你可以绑定表单,但相反的处理它,简单地使用绑定表单呈现更新字段。来自AJAX调用的响应可以被用来更新视图。