如何解决使用表单事件Symfony 5动态修改自定义FormType中的表单选择
我想构建一个自定义AjaxEntityType,以通过ajax加载选项。我不能用所有选择来构建表单,因为选择太多了,并且性能受到很大影响。
问题是,如果我从自定义类型中读取了表单字段(如食谱中的内容),则根本不会提交数据。
我需要一种无需读取表单字段即可在“自定义类型类”中更改选择的方法。
这是我的课程:
<?php
namespace App\Form\Type;
class AjaxEntityType extends AbstractType
{
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'ajax_url' => null
]);
}
public function buildForm(FormBuilderInterface $builder,array $options)
{
$builder->addEventListener(FormEvents::PRE_SUBMIT,function (FormEvent $event) use ($options) {
$data = $event->getData();
if ($data === null || $data === []) {
return;
}
// here i get the ids from the submited data,get the entities from the database and I try to set them as choices
$entityIds = is_array($data) ? $data : [$data];
$entities = $this->em->getRepository($event->getForm()->getConfig()->getOptions()['class'])->findBy(["id" => $entityIds]);
$options['choices'] = $entities;
$event->getForm()->getParent()->add(
$event->getForm()->getName(),self::class,$options
);
// the result is that the from gets submitted,but the new data is not set on the form. It's like the form field never existed in the first place.
});
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view,FormInterface $form,array $options)
{
$view->vars['ajax_url'] = $options['ajax_url'];
}
public function getParent()
{
return EntityType::class;
}
}
我的控制器非常简单:
public function create(Request $request,ProductService $productService,Product $product = null)
{
if(empty($product)){
$product = new Product();
}
$form = $this->createForm(ProductType::class,$product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->persist($product);
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('products_list');
}
return $this->render('admin/products/form.html.twig',[
'page_title' => "Product form",'product' => $product,'form' => $form->createView()
]);
}
解决方法
咨询form components code并考虑了一段时间后,我有了一个理论。
首先,表单提交的工作方式如下(简化):
- 表单提交方法称为
- 预提交被解雇
- sub forms submit methods are called
- generation of view data from sub forms
- 数据映射到规范数据
- 提交事件被触发
- 数据映射到模型(+视图)数据
- 后提交事件被触发
因此它是递归的,子窗体在父窗体继续之前经历了整个周期。
这是怎么回事:
轮到该子窗体时,您的子窗体会做一些复杂的工作来创建一个新的子窗体(该子窗体将不包含在父窗体的循环中)并将覆盖原始窗体。但是,由于新的子表单没有自己的循环,因此基本上为空。 (此外,父表单会删除已处理的表单的提交数据,以检测其他数据)。因此,我们现在有了一个独立的原始表单来接收数据,而有一个新的链接表单不包含数据。
父窗体现在将为其自身(及其子窗体)调用数据映射器,以将数据映射回视图->规范化->模型数据。由于您的旧子表单不再是子表单,而新子表单为空,因此结果为空。
如何解决此问题?
您必须在子窗体循环浏览子窗体之前添加子窗体。因此,实现此目的的一种方法是将PRE_SUBMIT事件侦听器添加到 parent 表单,然后在其中添加 sub 表单,而不是在父级中覆盖自身的子表单。显然,这并不像您希望的那样可重用。 也许您可以在子表单的buildForm方法中将事件侦听器添加到父表单中,但这听起来有点脏(不过,它并不是编辑父表单开头的地方)。 / p>
旁注:我也希望您知道,如果表单产生错误,则您更改后的表单将以非常有限的选项集显示给用户...-如果您对表单响应的处理良好这应该没问题。
更新
好吧,显然它应该是可重用的...在这种情况下,我看到了两种选择,其中两种可能都不起作用:
-
在 new 表单上将
empty_data
设置为实体,由于该表单未接收任何数据,因此可能会使用它。也许您也可以设置data
。 -
不替换表单,而是实际实现实际设置数据的事件处理程序。 SUBMIT处理程序应have direct access to the normData(通过
$event->setData(...)
设置新值)然后传播。 -
您可以选择不扩展 EntityType,而是包装(例如,在其
->add(...,EntityType...)
中包含buildForm
)并应用我之前为父级修改描述的方法。这将使其可重用。如果将label
设置为false并使用方便的表单主题,则应该与直接使用实体类型几乎没有区别。
在PRE_SET_DATA(将选定的选项保留在表单中)和PRE_SUBMIT(将新选择的项添加到选择列表)中更改表单,最终是一个巨大的麻烦。总是需要一些复杂的调整。
因此,作为最终解决方案,我删除了自定义类型类的buildView方法中的所有选择,效果很好。
// in file App\Form\Type\AjaxEntityType.php
/**
* {@inheritdoc}
*/
public function buildView(FormView $view,FormInterface $form,array $options)
{
$view->vars['ajax_url'] = $options['ajax_url'];
// in case it's not a multiple select
if(!is_array($view->vars['value'])){
$selected = [$view->vars['value']];
}else{
$selected = $view->vars['value'];
}
foreach($view->vars['choices'] as $index => $choice){
if(!in_array($choice->value,$selected)){
unset($view->vars['choices'][$index]);
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。