使用表单事件Symfony 5动态修改自定义FormType中的表单选择

如何解决使用表单事件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并考虑了一段时间后,我有了一个理论。

首先,表单提交的工作方式如下(简化):

因此它是递归的,子窗体在父窗体继续之前经历了整个周期。

这是怎么回事:

轮到该子窗体时,您的子窗体会做一些复杂的工作来创建一个新的子窗体(该子窗体将不包含在父窗体的循环中)并将覆盖原始窗体。但是,由于新的子表单没有自己的循环,因此基本上为空。 (此外,父表单会删除已处理的表单的提交数据,以检测其他数据)。因此,我们现在有了一个独立的原始表单来接收数据,而有一个新的链接表单不包含数据。

父窗体现在将为其自身(及其子窗体)调用数据映射器,以将数据映射回视图->规范化->模型数据。由于您的旧子表单不再是子表单,而新子表单为空,因此结果为空。

如何解决此问题?

您必须在子窗体循环浏览子窗体之前添加子窗体。因此,实现此目的的一种方法是将PRE_SUBMIT事件侦听器添加到 parent 表单,然后在其中添加 sub 表单,而不是在父级中覆盖自身的子表单。显然,这并不像您希望的那样可重用。 也许您可以在子表单的buildForm方法中将事件侦听器添加到父表单中,但这听起来有点脏(不过,它并不是编辑父表单开头的地方)。 / p>

旁注:我也希望您知道,如果表单产生错误,则您更改后的表单将以非常有限的选项集显示给用户...-如果您对表单响应的处理良好这应该没问题。

更新

好吧,显然它应该是可重用的...在这种情况下,我看到了两种选择,其中两种可能都不起作用:

  1. new 表单上将empty_data设置为实体,由于该表单未接收任何数据,因此可能会使用它。也许您也可以设置data

  2. 不替换表单,而是实际实现实际设置数据的事件处理程序。 SUBMIT处理程序应have direct access to the normData(通过$event->setData(...)设置新值)然后传播。

  3. 您可以选择不扩展 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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-