为一个实体字段呈现多个表单字段

Posted

技术标签:

【中文标题】为一个实体字段呈现多个表单字段【英文标题】:Rendering multiple Form fields for one Entity field 【发布时间】:2021-11-15 03:23:09 【问题描述】:

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

我有两个实体:

预订 菜单

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

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

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

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

我尝试遍历 Menu 对象以向我的表单添加字段,但似乎该表单只采用最后一个菜单并且不呈现其他菜单。

生成这些字段的任何想法?

Reservation.php

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

...

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

菜单.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

提交表单后,我收到此错误(我知道它不是预期的对象,但我认为我可以在提交后创建 Menu 对象):Error

【问题讨论】:

菜单数量如何保存在数据库中?如果我想要“B 菜单”x4,预订和菜单之间的关系是如何完成的?您的实体中的 ManyToMany 不允许在桥表中有额外的字段。 按照我的做法,菜单的数量间接地由预订“菜单”字段中的菜单实体的数量表示。在您的情况下,菜单 ArrayCollection 将包含 4 个 Menu B 实体。我不知道这是否是实现这一点的正确方法,我愿意接受建议。 这不是多对多的工作方式。如果您检查从您的实体创建的结果模式,在桥表(menu_reservation,我假设)中,两个外键也是主键,这意味着您不能有多个具有相同 MenuReservation 的记录组合。您将需要删除 ManyToMany 关系并在 3 个参与实体之间创建一对多/多对一关联:MenuReservation 并将 menu_reservation 提升为具有自己的完整实体主键、两个外键和一个保存数量的新字段。 谢谢你的回答,我会照你说的做的。这解决了未来的问题,但主要问题保持不变。我的意思是,它不是 Menu 的 ArrayCollection,而是 MenuReservation 的 ArrayCollection。您对生成这些表单字段有任何见解吗? 【参考方案1】:

在评论线程和您的回答之后,我看到您将 ManyToMany 修改为两个 OneToMany/ManyToOne 关系,这样您就可以利用 CollectionType 类并让表单处理字段渲染,而您只专注于数据,无需使用 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,你不需要持久化它(将空值保存到数据库)。

更新

显然,不可靠的数据没有传递给CollectionType 上的buildForm 类中的子表单,不知道这是一个错误还是它的工作方式,会在松弛时询问并可能发布github上的一个问题,但我们可以在twig中解决它:

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

【讨论】:

我按照您建议的方式进行了修改以生成字段。这行得通,但我无法让选项'label' =&gt; $builder-&gt;getData()-&gt;getName() 工作,我想在这个标签中显示菜单名称,可以吗?我要跑题了,但我想知道是否可以使用某些配置属性为这些字段设置组合最大值(例如:预订不能超过 5 个菜单),有什么想法吗? 进行了上述更正。忘了我在 MenuReservationType 上,所以您需要先访问相关菜单才能获取名称。还添加了表单中的约束验证和 html 输入的属性(从不信任客户端输入)。 生成器$builder-&gt;getData() 字段为空,我无法初始化该字段。您能否更准确地解释如何访问Menu 名称? 在表单类型类中,您可以从 2 个来源获取基础实体:$builder-&gt;getData()$options['data']。仅当您没有将控制器中 createForm() 的第二个参数设为空时。 MenuReservationType 也应该在 configureOptions 方法中有 'data_class' =&gt; MenuReservation::class 选项。 该字段仍然为空,我不明白为什么,我没有将第二个参数设置为空:$form = $this-&gt;createForm(ReservationType::class, $reservation)data_class字段已设置。还有其他我错过的配置吗?

以上是关于为一个实体字段呈现多个表单字段的主要内容,如果未能解决你的问题,请参考以下文章

如何检查表单字段中是不是有呈现控制器的树枝模板中的数据

模型表单集 - 默认情况下,模型表单集呈现一个额外的字段(总共 2 个字段)

以编程方式呈现 Redux 表单字段

将值呈现为文本而不是 Django 表单中的字段

如何在同一视图中处理同一实体类型的多个表单

Rails cocoon嵌套字段,在编辑表单上呈现每个字段旁边的现有图像