Symfony 5 多对多具有额外字段的表单生成器
Symfony 5 Many to Many with extra fields form builder
我在项目中每个人都需要的(也许)基本功能上浪费了很多时间。
事实是我坚持使用表单生成器(这个框架中的一个真正的守护进程,我不明白为什么这么难用)。
我得到了三个实体:
Recette
Ingredient
RecettesIngredients
Recette
和 Ingredient
都与 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>
这是结果
我在项目中每个人都需要的(也许)基本功能上浪费了很多时间。
事实是我坚持使用表单生成器(这个框架中的一个真正的守护进程,我不明白为什么这么难用)。
我得到了三个实体:
Recette
Ingredient
RecettesIngredients
Recette
和 Ingredient
都与 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>
这是结果