为一个实体字段渲染多个表单字段

Rendering multiple Form fields for one Entity field

我在使用 Symfony 5 Forms 时遇到问题。

我有两个实体:

他们都有多对多关系。 我想为数据库中注册的每个菜单对象创建一个数字字段输入。

例如: 有 3 个菜单:A、B、C

我希望表格生成(在用于预订实体的其他生成字段中)3 个数字字段并在每个字段中键入我想要的数量 -->(3 个菜单 A、2 个菜单 B 和 1 个菜单C)

我的问题是所有这 3 个菜单都在预订实体中注册为“菜单”字段。

我试图遍历 Menu 对象以将字段添加到我的表单,但该表单似乎只采用最后一个 Menu 而不会呈现其他菜单。

有生成这些字段的想法吗?

Reservation.php

/**
 * @ORM\Entity(repositoryClass=ReservationRepository::class)
 */
class Reservation
{
...

    /**
     * @ORM\ManyToMany(targetEntity=Menu::class, mappedBy="reservation")
     */
    private $menus;
}

Menu.php

/**
 * @ORM\Entity(repositoryClass=MenuRepository::class)
 */
class Menu
{
...
    /**
     * @ORM\ManyToMany(targetEntity=Reservation::class, inversedBy="menus")
     */
    private $reservation;
...
}

ReservationType.php

class ReservationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstName', null, [
                'attr' => [
                    'class' => 'custom-input form-control-lg',
                    'placeholder' => 'First name'
                ],
                'label' => false
            ])
            ->add('lastName', null, [
                'attr' => [
                    'class' => 'custom-input form-control-lg',
                    'placeholder' => 'Last name'
                ],
                'label' => false
            ])
            ->add('phoneNumber', null, [
                'attr' => [
                    'class' => 'custom-input form-control-lg',
                    'placeholder' => 'Phone number'
                ],
                'label' => false
            ])
            ->add('paymentMethod', ChoiceType::class, [
                'attr' => [
                    'class' => 'form-control-lg'
                ],
                'placeholder' => 'Payment method',
                'choices' => [
                    "LYDIA" => true,
                    "CASH" => true
                ],
                'label' => false
            ])
        ;
    }

到目前为止我对表格的尝试

AppController.php

<?php
#[Route('/', name: 'home')]
public function index(TableRepository $tableRepository, MenuRepository $menuRepository, Request $request): Response
{
    //...

    $form = $this->createForm(ReservationType::class, $reservation);
    $menus = $menuRepository->findAll();

    //...

    foreach ($menus as $menu) {
        $form->add('menus', TextType::class, [
            'attr' => [
                'placeholder' => 'Menu "' . $menu->getName() . '"',
                'class' => 'custom-input form-control-lg'
            ],
            'label' => false
        ]);
    }

    //...

    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {
        // $reservation = $form->getData();
        dump($reservation);
        return $this->redirectToRoute('home');
    }
}

渲染后得到的结果(有 3 个注册菜单): Result after rendering the form

提交表单后,出现此错误(我知道这不是预期的对象,但我认为我可以在提交后创建菜单对象):Error

在评论线程和您的回答之后,我看到您将 ManyToMany 修改为两个 OneToMany/ManyToOne 关系,这样您就可以利用 CollectionType class 并让表单处理字段渲染,你只关注数据,不需要用foreach添加字段。

我假设您也可以编辑预订,所以在您的控制器中您需要在创建表单之前检查关系是否为空。

if (empty($reservation->getMenuReservations())){
    $menus = $this->getDoctrine()->getRepository(Menu::class)->findAll(); //I'm assuming you only have those 3 menus
    foreach ($menus as $menu){
        $mr = new MenuReservation();
        $mr->setMenu($menu)->setQuantity(0);
        $reservation->addMenuReservation($mr); //need to add them to the "owned" entity instead, to fill the `getMenuReservations` array
    }
}
$form = $this->createForm(ReservationType::class, $reservation);

有了这个,如果您正在编辑或表格无效,如果预订是“新的”,您只能预先填写菜单,否则,将使用现有的菜单。

现在,要使 CollectionType 魔术发挥作用,最简单的方法是仅为 MenuReservationEntity 创建一个单独的表单,其中包含一个字段,其中包含标签的数量和菜单名称:

class MenuReservationType extends AbstractType { 
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('quantity', IntegerType::class, [
            //'label' => $builder->getData()->getMenu()->getName(),
            'attr' => [
                'min' => 0, //cant' have negative menus (client side only)
                'max' => 5 //if you want the input to limit the menus (client side only)
            ],
            'constraints' => [
                //here you add the contraints for the form (server side)
                new Positive(),
                new LessThanOrEqual(5)
            ]
        ]);
    }
}

Here 是您可以添加的约束列表。 并让您的 CollectionType 知道它:

...
->add('menuReservation', CollectionType::class, [
    'entry_type' => MenuReservatioType::class,
    //if you have problems whtn submitting, it may be because the next option is false by default, uncomment if necessary 
    //'allow_add' => true
])
...

最后,如果你没有cascade persist的实体,你需要单独持久化它们,另外,如果数量为0,你不需要持久化它(没有意义将空值保存到数据库)。

更新

显然,在 CollectionTypebuildForm class 中,底层数据没有传递给子表单,不知道这是错误还是方式它有效,会在 slack 上询问,也许 post github 上的问题,但我们可以在 twig 中解决它:

{% for mr in form.menuReservation %}
    {{ form_row(mr, {'label': mr.vars.data.menu.name}) }}
{% endfor %}