Symfony 5 多对多具有额外字段的表单生成器

Symfony 5 Many to Many with extra fields form builder

我在项目中每个人都需要的(也许)基本功能上浪费了很多时间。

事实是我坚持使用表单生成器(这个框架中的一个真正的守护进程,我不明白为什么这么难用)。

我得到了三个实体:

Recette
Ingredient
RecettesIngredients

RecetteIngredient 都与 RecettesIngredients 进行一对多。这是一个多对多的额外字段。

我已经完成了一个表单生成器 RecetteType 可以:

        ->add('recettesIngredients', CollectionType::class, [
            'entry_type' => RecettesIngredientsType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'label' => 'XXX'
        ])

我做了一个表单生成器(他工作得很好)RecettesIngredientsType :

        ->add('ingredients', EntityType::class, [
            // looks for choices from this entity
            'class' => Ingredient::class,
            'choice_label' => 'nom',
            // used to render a select box, check boxes or radios
            'multiple' => true,
            'expanded' => true
        ])

当我尝试渲染它时:

{{ form_row(form.recettesIngredients) }}

如果我没有向我的表单生成器提供对象: 除了没有错误的空白页面外,我没有任何可显示的内容。

如果我给表单生成器一个对象: 我有一个错误:

"Unable to transform value for property path "ingredients": Expected a Doctrine\Common\Collections\Collection object."

如果我删除“多种”选项,我会得到一个成分列表,但没有select它们的复选框。

我的问题很简单:

如何正确显示具有额外字段关系上下文的多对多表单?

我按照 symfony documentation 做了你的例子。 以您为例的实体:

/**
 * @ORM\Entity(repositoryClass=RecetteRepository::class)
 */
class Recette
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\OneToMany(targetEntity=RecetteIngredient::class, mappedBy="recette")
     *
     */
    private $ingredients;
/**
 * @ORM\Entity(repositoryClass=IngredientRepository::class)
 */
class Ingredient
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\OneToMany(targetEntity=RecetteIngredient::class, mappedBy="ingredient")
     */
    private $recette;
/**
 * @ORM\Entity(repositoryClass=RecetteIngredientRepository::class)
 */
class RecetteIngredient
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $quantity;

    /**
     * @ORM\ManyToOne(targetEntity=Recette::class, inversedBy="ingredients")
     * @ORM\JoinColumn(nullable=false)
     */
    private $recette;

    /**
     * @ORM\ManyToOne(targetEntity=Ingredient::class, inversedBy="recette")
     * @ORM\JoinColumn(nullable=false)
     */
    private $ingredient;

我的 FormTypes 来自您的示例。

我在我的数据库中创建了两个 Ingredient

并制作了这个控制器代码进行测试

 $recette = new Recette();
 $recette->setName("Crèpes");

 $recetteIngredient = new RecetteIngredient();
 $recetteIngredient->setQuantity(5);
 $recette->addIngredient($recetteIngredient);

 $form = $this->createForm(RecetteType::class, $recette);

 return $this->render('demo.html.twig',['form'=>$form->createView()]);

我改变的是渲染。由于 Ingredient 的数量是动态的,Symfony(和 php 服务器的代码)不知道你想要添加或删除的相关实体的数量,你不能只做一个 form_row()集合类型。

这是我从文档中获取的代码(我没有更改 html 属性名称,这只是一个演示。

{{ form_start(form) }}
    {{ form_row(form.name) }}
    <ul id="email-fields-list"
        data-prototype="{{ form_widget(form.ingredients.vars.prototype)|e }}"
        data-widget-tags="{{ '<li></li>'|e }}"
        data-widget-counter="{{ form.ingredients|length }}">
        {% for ingredient in form.ingredients %}
            <li>
                {{ form_errors(ingredient) }}
                {{ form_widget(ingredient) }}
            </li>
        {% endfor %}
    </ul>

    <button type="button"
            class="add-another-collection-widget"
            data-list-selector="#email-fields-list">Add another ingredient</button>
    {{ form_end(form) }}

    <script
            src="https://code.jquery.com/jquery-3.6.0.slim.min.js"
            integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI="
            crossorigin="anonymous"></script>
    <script>
        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>

这是结果