Symfony 集合表单控制器问题

Symfony Collection Form Controller Problem

我有一个实体产品。我创建了一个包含以下字段的 ProductType 表单:

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

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

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

ProductController

//[...]

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 %}

希望您能看到我遗漏的东西。谢谢:)

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

进行该更正后,您的代码应该正好得到一个 Product 与表单中最后一个的值保持一致,因为您只创建了一个新的 Product 并设置了新值它每次都通过循环。在下面的代码示例中查看我的评论:

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(),
        ]);
    }
}