为一个实体字段呈现多个表单字段
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
,我假设)中,两个外键也是主键,这意味着您不能有多个具有相同 Menu
和 Reservation
的记录组合。您将需要删除 ManyToMany 关系并在 3 个参与实体之间创建一对多/多对一关联:Menu
、Reservation
并将 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' => $builder->getData()->getName()
工作,我想在这个标签中显示菜单名称,可以吗?我要跑题了,但我想知道是否可以使用某些配置属性为这些字段设置组合最大值(例如:预订不能超过 5 个菜单),有什么想法吗?
进行了上述更正。忘了我在 MenuReservationType 上,所以您需要先访问相关菜单才能获取名称。还添加了表单中的约束验证和 html 输入的属性(从不信任客户端输入)。
生成器$builder->getData()
字段为空,我无法初始化该字段。您能否更准确地解释如何访问Menu
名称?
在表单类型类中,您可以从 2 个来源获取基础实体:$builder->getData()
或 $options['data']
。仅当您没有将控制器中 createForm()
的第二个参数设为空时。 MenuReservationType
也应该在 configureOptions 方法中有 'data_class' => MenuReservation::class
选项。
该字段仍然为空,我不明白为什么,我没有将第二个参数设置为空:$form = $this->createForm(ReservationType::class, $reservation)
。 data_class
字段已设置。还有其他我错过的配置吗?以上是关于为一个实体字段呈现多个表单字段的主要内容,如果未能解决你的问题,请参考以下文章