Symfony 集合表单控制器问题

Posted

技术标签:

【中文标题】Symfony 集合表单控制器问题【英文标题】:Symfony Collection Form Controller Problem 【发布时间】:2021-10-25 02:30:00 【问题描述】:

我有一个实体产品。我创建了一个带有字段的表单 ProductType :

姓名 价格 参考

我想创建一个集合以允许用户一次创建和提交多个产品。 因此,我创建了一个没有实体 ProductsType 的新表单。 此表单包含一个字段:

产品 这是一个具有条目类型 ProductType 的 CollectionType 类。

在我的模板中,我使用了一个原型,并且 javascript 可以完美地创建它。 虽然,提交时我的条目都没有保留在数据库中。 我找了几个小时终于找到了一些鼓舞人心的东西,但仍然没有工作: Symfony: Access an unmapped form field from a CollectionType in Controller

你看到我的错误是什么(我猜是在控制器中)吗?

产品控制器

//[...]

class ProductController extends AbstractController

    /**
     * @Route("/product", name="product")
     */
    public function index(): Response
    
        $request = new Request();
        $formProduct = $this->createForm('App\Form\ProductsType');
        $product = new Product();
        $formProduct->handleRequest($request);
        if ($formProduct->isSubmitted() && $formProduct->isValid()) 
            foreach ($formProduct->get('products') as $formChild)
            
                $product->setName($formChild->get('name')->getData()); // That's it!
                $product->setPrice($formChild->get('price')->getData());
                $product->setReference($formChild->get('reference')->getData());
                $entityManager = $this->getDoctrine()->getManager();
                $entityManager->persist($product);
                $entityManager->flush();
            


            return $this->redirectToRoute('task_success');
        

        return $this->render('product/index.html.twig', [
            'formProduct' => $formProduct->createView(),
        ]);
    

产品类型

//[...]

class ProductType extends AbstractType

    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('name')

            ->add('price')

            ->add('reference')

        ;
    

    public function configureOptions(OptionsResolver $resolver)
    
        $resolver->setDefaults([
            'data_class' => Product::class,
        ]);
    

产品类型

//[...]

class ProductsType extends AbstractType

    public function buildForm(FormBuilderInterface $builder, array $options)
    
        $builder
            ->add('products', CollectionType::class, [
                'entry_type' => ProductType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'prototype' => true,
            ])
        ;
    

    public function configureOptions(OptionsResolver $resolver)
    
        $resolver->setDefaults([
            // Configure your form options here
        ]);
    

产品模板(树枝)

% extends 'base.html.twig' %

% block title %Hello ProductController!% endblock %

% block body %
 form_start(formProduct) 
    # store the prototype on the data-prototype attribute #
    <ul id="products-fields-list"
        data-prototype=" form_widget(formProduct.products.vars.prototype)|e "
        data-widget-tags=" '<li></li>'|e "
        data-widget-counter=" formProduct.products|length ">
        % for products in formProduct.products %
            <li>

                 form_row(products) 
            </li>

        % endfor %
    </ul>
    <input type="submit" value="Submit">
     form_end(formProduct) 
    <button type="button"
            class="add-another-collection-widget"
            data-list-selector="#products-fields-list">Add another email</button>

    <script>
        // add-collection-widget.js
        jQuery(document).ready(function () 
            jQuery('.add-another-collection-widget').click(function (e) 
                var list = jQuery(jQuery(this).attr('data-list-selector'));
                // Try to find the counter of the list or use the length of the list
                var counter = list.data('widget-counter') || list.children().length;

                // grab the prototype template
                var newWidget = list.attr('data-prototype');
                // replace the "__name__" used in the id and name of the prototype
                // with a number that's unique to your emails
                // end name attribute looks like name="contact[emails][2]"
                newWidget = newWidget.replace(/__name__/g, counter);
                // Increase the counter
                counter++;
                // And store it, the length cannot be used if deleting widgets is allowed
                list.data('widget-counter', counter);

                // create a new list element and add it to the list
                var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
                newElem.appendTo(list);
            );
        );

    </script>
% endblock %

希望您能看到我缺少的东西。谢谢你:)

【问题讨论】:

你说“我的条目都没有被持久化”,但你没有说实际发生了什么。您是被重定向到task_success 路由还是再次收到表单(已填写或未填写)? 确实,我没有重定向到那个页面。所以我想这意味着如果 $formProduct 已提交且 $formProduct 有效,我没有通过条件。我再次收到未填写的表格。 【参考方案1】:

您的控制器方法存在一些问题。主要问题是您使用$request = new Request();。那是在客户端发送的请求之外创建一个新的Request,因此在该空请求中没有提交表单数据。该请求应作为方法参数注入,因此自动连接将从客户端的当前 http 请求构建 Request。https://symfony.com/doc/current/forms.html#processing-forms

在进行更正后,您的代码应该会得到一个与表单中最后一个值保持一致的 Product,因为您只需创建一个新的 Product 并在每次循环时在其上设置新值.在下面的代码示例中查看我的 cmets:

use Symfony\Component\HttpFoundation\Request;

class ProductController extends AbstractController

    /**
     * @Route("/product", name="product")
     */
    public function index(Request $request): Response
    
        // line below would create empty Request
        // $request = new Request();
        $formProduct = $this->createForm('App\Form\ProductsType');
        // line below would create only ONE Product that would be overwritten in each iteration
        // $product = new Product();
        $formProduct->handleRequest($request);
        if ($formProduct->isSubmitted() && $formProduct->isValid()) 
            // you only need to fetch the entity manager once
            $entityManager = $this->getDoctrine()->getManager();
            foreach ($formProduct->get('products') as $formChild)
            
                // create a new Product foreach $formChild
                $product = new Product();
                $product->setName($formChild->get('name')->getData()); 
                $product->setPrice($formChild->get('price')->getData());
                $product->setReference($formChild->get('reference')->getData());
                // you only need to fetch the entity manager once
                // $entityManager = $this->getDoctrine()->getManager();
                $entityManager->persist($product);
                $entityManager->flush();
            


            return $this->redirectToRoute('task_success');
        

        return $this->render('product/index.html.twig', [
            'formProduct' => $formProduct->createView(),
        ]);
    

【讨论】:

感谢您的解释。有用。我会阅读表格的文档,因为我遗漏了您解释的内容。再次感谢:)

以上是关于Symfony 集合表单控制器问题的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 2:在控制器中返回一个表单

如何在 Symfony2 控制器中获取表单值

Symfony表单不向控制器发送文件

将选项从控制器传递到嵌入式 Symfony 表单时,选项不存在错误

Symfony2:KnpPaginator 仅显示带有 POST 表单的第一页

如何在 symfony2 控制器中发送 JSON 响应