为一个实体字段渲染多个表单字段
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,你不需要持久化它(没有意义将空值保存到数据库)。
更新
显然,在 CollectionType
上 buildForm
class 中,底层数据没有传递给子表单,不知道这是错误还是方式它有效,会在 slack 上询问,也许 post github 上的问题,但我们可以在 twig 中解决它:
{% for mr in form.menuReservation %}
{{ form_row(mr, {'label': mr.vars.data.menu.name}) }}
{% endfor %}
我在使用 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,你不需要持久化它(没有意义将空值保存到数据库)。
更新
显然,在 CollectionType
上 buildForm
class 中,底层数据没有传递给子表单,不知道这是错误还是方式它有效,会在 slack 上询问,也许 post github 上的问题,但我们可以在 twig 中解决它:
{% for mr in form.menuReservation %}
{{ form_row(mr, {'label': mr.vars.data.menu.name}) }}
{% endfor %}