Symfony 集合表单控制器问题
Symfony Collection Form Controller Problem
我有一个实体产品。我创建了一个包含以下字段的 ProductType 表单:
- 姓名
- 价格
- 参考
我想创建一个集合以允许用户一次创建和提交多个产品。
因此,我创建了一个没有实体 ProductsType 的新表单。
此表单包含一个字段:
- 产品
这是条目类型为 ProductType 的 CollectionType class。
在我的模板中,我使用了一个原型,Javascript 完美地创建了它。
虽然,none 我的条目在提交时保留在数据库中。
我搜索了几个小时终于找到了一些鼓舞人心但仍然没有用的东西:
Symfony: Access an unmapped form field from a CollectionType in Controller
你看到我的错误是什么了吗(我猜是在控制器中)?
ProductController
//[...]
class ProductController extends AbstractController
{
/**
* @Route("/product", name="product")
*/
public function index(): Response
{
$request = new Request();
$formProduct = $this->createForm('App\Form\ProductsType');
$product = new Product();
$formProduct->handleRequest($request);
if ($formProduct->isSubmitted() && $formProduct->isValid()) {
foreach ($formProduct->get('products') as $formChild)
{
$product->setName($formChild->get('name')->getData()); // That's it!
$product->setPrice($formChild->get('price')->getData());
$product->setReference($formChild->get('reference')->getData());
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($product);
$entityManager->flush();
}
return $this->redirectToRoute('task_success');
}
return $this->render('product/index.html.twig', [
'formProduct' => $formProduct->createView(),
]);
}
}
产品类型
//[...]
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('price')
->add('reference')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Product::class,
]);
}
}
产品类型
//[...]
class ProductsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('products', CollectionType::class, [
'entry_type' => ProductType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}
产品模板(树枝)
{% extends 'base.html.twig' %}
{% block title %}Hello ProductController!{% endblock %}
{% block body %}
{{ form_start(formProduct) }}
{# store the prototype on the data-prototype attribute #}
<ul id="products-fields-list"
data-prototype="{{ form_widget(formProduct.products.vars.prototype)|e }}"
data-widget-tags="{{ '<li></li>'|e }}"
data-widget-counter="{{ formProduct.products|length }}">
{% for products in formProduct.products %}
<li>
{{ form_row(products) }}
</li>
{% endfor %}
</ul>
<input type="submit" value="Submit">
{{ form_end(formProduct) }}
<button type="button"
class="add-another-collection-widget"
data-list-selector="#products-fields-list">Add another email</button>
<script>
// add-collection-widget.js
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>
{% endblock %}
希望您能看到我遗漏的东西。谢谢:)
您的控制器方法有几个问题。主要问题是您使用 $request = new Request();
。那是创建一个新的 Request
而不是客户端发送的请求,因此在该空请求中没有提交表单数据。该请求应作为方法参数注入,以便自动连接将从客户端的当前 http 请求构建 Request
。
https://symfony.com/doc/current/forms.html#processing-forms
进行该更正后,您的代码应该正好得到一个 Product
与表单中最后一个的值保持一致,因为您只创建了一个新的 Product
并设置了新值它每次都通过循环。在下面的代码示例中查看我的评论:
use Symfony\Component\HttpFoundation\Request;
class ProductController extends AbstractController
{
/**
* @Route("/product", name="product")
*/
public function index(Request $request): Response
{
// line below would create empty Request
// $request = new Request();
$formProduct = $this->createForm('App\Form\ProductsType');
// line below would create only ONE Product that would be overwritten in each iteration
// $product = new Product();
$formProduct->handleRequest($request);
if ($formProduct->isSubmitted() && $formProduct->isValid()) {
// you only need to fetch the entity manager once
$entityManager = $this->getDoctrine()->getManager();
foreach ($formProduct->get('products') as $formChild)
{
// create a new Product foreach $formChild
$product = new Product();
$product->setName($formChild->get('name')->getData());
$product->setPrice($formChild->get('price')->getData());
$product->setReference($formChild->get('reference')->getData());
// you only need to fetch the entity manager once
// $entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($product);
$entityManager->flush();
}
return $this->redirectToRoute('task_success');
}
return $this->render('product/index.html.twig', [
'formProduct' => $formProduct->createView(),
]);
}
}
我有一个实体产品。我创建了一个包含以下字段的 ProductType 表单:
- 姓名
- 价格
- 参考
我想创建一个集合以允许用户一次创建和提交多个产品。 因此,我创建了一个没有实体 ProductsType 的新表单。 此表单包含一个字段:
- 产品 这是条目类型为 ProductType 的 CollectionType class。
在我的模板中,我使用了一个原型,Javascript 完美地创建了它。 虽然,none 我的条目在提交时保留在数据库中。 我搜索了几个小时终于找到了一些鼓舞人心但仍然没有用的东西: Symfony: Access an unmapped form field from a CollectionType in Controller
你看到我的错误是什么了吗(我猜是在控制器中)?
ProductController
//[...]
class ProductController extends AbstractController
{
/**
* @Route("/product", name="product")
*/
public function index(): Response
{
$request = new Request();
$formProduct = $this->createForm('App\Form\ProductsType');
$product = new Product();
$formProduct->handleRequest($request);
if ($formProduct->isSubmitted() && $formProduct->isValid()) {
foreach ($formProduct->get('products') as $formChild)
{
$product->setName($formChild->get('name')->getData()); // That's it!
$product->setPrice($formChild->get('price')->getData());
$product->setReference($formChild->get('reference')->getData());
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($product);
$entityManager->flush();
}
return $this->redirectToRoute('task_success');
}
return $this->render('product/index.html.twig', [
'formProduct' => $formProduct->createView(),
]);
}
}
产品类型
//[...]
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('price')
->add('reference')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Product::class,
]);
}
}
产品类型
//[...]
class ProductsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('products', CollectionType::class, [
'entry_type' => ProductType::class,
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// Configure your form options here
]);
}
}
产品模板(树枝)
{% extends 'base.html.twig' %}
{% block title %}Hello ProductController!{% endblock %}
{% block body %}
{{ form_start(formProduct) }}
{# store the prototype on the data-prototype attribute #}
<ul id="products-fields-list"
data-prototype="{{ form_widget(formProduct.products.vars.prototype)|e }}"
data-widget-tags="{{ '<li></li>'|e }}"
data-widget-counter="{{ formProduct.products|length }}">
{% for products in formProduct.products %}
<li>
{{ form_row(products) }}
</li>
{% endfor %}
</ul>
<input type="submit" value="Submit">
{{ form_end(formProduct) }}
<button type="button"
class="add-another-collection-widget"
data-list-selector="#products-fields-list">Add another email</button>
<script>
// add-collection-widget.js
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>
{% endblock %}
希望您能看到我遗漏的东西。谢谢:)
您的控制器方法有几个问题。主要问题是您使用 $request = new Request();
。那是创建一个新的 Request
而不是客户端发送的请求,因此在该空请求中没有提交表单数据。该请求应作为方法参数注入,以便自动连接将从客户端的当前 http 请求构建 Request
。
https://symfony.com/doc/current/forms.html#processing-forms
进行该更正后,您的代码应该正好得到一个 Product
与表单中最后一个的值保持一致,因为您只创建了一个新的 Product
并设置了新值它每次都通过循环。在下面的代码示例中查看我的评论:
use Symfony\Component\HttpFoundation\Request;
class ProductController extends AbstractController
{
/**
* @Route("/product", name="product")
*/
public function index(Request $request): Response
{
// line below would create empty Request
// $request = new Request();
$formProduct = $this->createForm('App\Form\ProductsType');
// line below would create only ONE Product that would be overwritten in each iteration
// $product = new Product();
$formProduct->handleRequest($request);
if ($formProduct->isSubmitted() && $formProduct->isValid()) {
// you only need to fetch the entity manager once
$entityManager = $this->getDoctrine()->getManager();
foreach ($formProduct->get('products') as $formChild)
{
// create a new Product foreach $formChild
$product = new Product();
$product->setName($formChild->get('name')->getData());
$product->setPrice($formChild->get('price')->getData());
$product->setReference($formChild->get('reference')->getData());
// you only need to fetch the entity manager once
// $entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($product);
$entityManager->flush();
}
return $this->redirectToRoute('task_success');
}
return $this->render('product/index.html.twig', [
'formProduct' => $formProduct->createView(),
]);
}
}