Symfony 表单 CollectionType 字段的顺序
Order of Symfony form CollectionType field
在我的模型中,我有一个 Recipe 实体和 Ingredient 实体。在 Recipe 实体中,关系定义如下:
/**
* @ORM\OneToMany(targetEntity="Ingredient", mappedBy="recipe", cascade={"remove", "persist"}, orphanRemoval=true)
* @ORM\OrderBy({"priority" = "ASC"})
*/
private $ingredients;
在成分实体中:
/**
* @ORM\ManyToOne(targetEntity="Recipe", inversedBy="ingredients")
* @ORM\JoinColumn(name="recipe_id", referencedColumnName="id")
*/
private $recipe;
我正在为食谱开发 CRUD 控制器,我希望用户能够动态添加配料。我还希望用户拖放配料以在食谱中设置它们的优先级(顺序)。为此,我正在使用 CollectionType 表单字段。
并将此页面作为教程:
http://symfony.com/doc/current/cookbook/form/form_collections.html
到目前为止,配方的添加和显示工作正常,但是 Edit/Update 操作存在问题,我将在下面尝试描述:
在控制器中,我加载实体并创建如下表单:
public function updateAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$recipe = $em->getRepository('AppBundle:Recipe')->find($id);
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
...
}
由于优先级保存在数据库中,并且我有 @ORM\OrderBy({"priority" = "ASC"})
,因此初始加载和显示成分工作正常。但是,如果用户四处拖放配料,则优先级值会发生变化。如果存在表单验证错误并且需要重复显示表单,表单中的成分将按旧顺序显示,即使优先级值已更新。
例如,我在数据库中有以下初始成分 => 优先级值:
- A => 1
- B => 2
- C => 3
表格行按顺序显示:A,B,C;
用户改单后,我有:
- B => 1
- A => 2
- C => 3
但表单行仍显示为 A、B、C;
我了解表单已按顺序 A、B、C 初始化,更新 priority
不会更改 ArrayCollection 的元素顺序。但我(几乎)不知道如何改变它。
到目前为止我尝试过的:
$form->getData();
// sort in memory
$form->setData();
这不起作用,因为显然不允许在已经有输入的表单上使用 setData()。
我也曾尝试设置 DataTransformer 来对行进行排序,但表单忽略了新的顺序。
我也曾尝试在 FormType class 中使用 PRE/POST 提交处理程序来对行进行排序,但是表单仍然忽略新顺序。
最后(有点)起作用的是:
在 Recipe 实体中,定义 sortIngredients()
对内存中的 ArrayCollection 进行排序的方法,
public function sortIngredients()
{
$sort = \Doctrine\Common\Collections\Criteria::create();
$sort->orderBy(Array(
'priority' => \Doctrine\Common\Collections\Criteria::ASC
));
$this->ingredients = $this->ingredients->matching($sort);
return $this;
}
然后,在控制器中:
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
$recipe->sortIngredients();
// repeatedly create and process form with already sorted ingredients
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
// ... do the rest of the controller stuff, flush(), etc
这行得通,但是表单被创建和处理了两次,老实说它看起来像一个 hack...
我正在寻找解决问题的更好方法。
您需要使用您的表单类型的 finishView 方法。
代码示例如下:
public function finishView(FormView $view, FormInterface $form, array $options)
{
usort($view['photos']->children, function (FormView $a, FormView $b) {
/** @var Photo $objectA */
$objectA = $a->vars['data'];
/** @var Photo $objectB */
$objectB = $b->vars['data'];
$posA = $objectA->getSortOrder();
$posB = $objectB->getSortOrder();
if ($posA == $posB) {
return 0;
}
return ($posA < $posB) ? -1 : 1;
});
}
可以将箭头函数 https://www.php.net/manual/en/functions.arrow.php with spaceship https://www.php.net/manual/en/migration70.new-features.php 与 PHP 结合起来 7.
用前面的例子:
public function finishView(FormView $view, FormInterface $form, array $options)
{
usort($view['photos']->children, fn (FormView $a, FormView $b) => $a->vars['data']->getSortOrder() <=> $b->vars['data']->getSortOrder());
}
一个更高级的示例,使用任何有序字段从任何地方对任何集合进行排序:
public function finishView(FormView $view, FormInterface $form, array $options)
{
$view['blocks']->children = array_merge($view['blockTexts']->children, $view['blockImages']->children);
usort($view['blocks']->children, fn (FormView $a, FormView $b) => $a->vars['data']->orderNumber <=> $b->vars['data']->orderNumber);
}
在我的模型中,我有一个 Recipe 实体和 Ingredient 实体。在 Recipe 实体中,关系定义如下:
/**
* @ORM\OneToMany(targetEntity="Ingredient", mappedBy="recipe", cascade={"remove", "persist"}, orphanRemoval=true)
* @ORM\OrderBy({"priority" = "ASC"})
*/
private $ingredients;
在成分实体中:
/**
* @ORM\ManyToOne(targetEntity="Recipe", inversedBy="ingredients")
* @ORM\JoinColumn(name="recipe_id", referencedColumnName="id")
*/
private $recipe;
我正在为食谱开发 CRUD 控制器,我希望用户能够动态添加配料。我还希望用户拖放配料以在食谱中设置它们的优先级(顺序)。为此,我正在使用 CollectionType 表单字段。
并将此页面作为教程:
http://symfony.com/doc/current/cookbook/form/form_collections.html
到目前为止,配方的添加和显示工作正常,但是 Edit/Update 操作存在问题,我将在下面尝试描述:
在控制器中,我加载实体并创建如下表单:
public function updateAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$recipe = $em->getRepository('AppBundle:Recipe')->find($id);
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
...
}
由于优先级保存在数据库中,并且我有 @ORM\OrderBy({"priority" = "ASC"})
,因此初始加载和显示成分工作正常。但是,如果用户四处拖放配料,则优先级值会发生变化。如果存在表单验证错误并且需要重复显示表单,表单中的成分将按旧顺序显示,即使优先级值已更新。
例如,我在数据库中有以下初始成分 => 优先级值:
- A => 1
- B => 2
- C => 3
表格行按顺序显示:A,B,C;
用户改单后,我有:
- B => 1
- A => 2
- C => 3
但表单行仍显示为 A、B、C;
我了解表单已按顺序 A、B、C 初始化,更新 priority
不会更改 ArrayCollection 的元素顺序。但我(几乎)不知道如何改变它。
到目前为止我尝试过的:
$form->getData();
// sort in memory
$form->setData();
这不起作用,因为显然不允许在已经有输入的表单上使用 setData()。
我也曾尝试设置 DataTransformer 来对行进行排序,但表单忽略了新的顺序。
我也曾尝试在 FormType class 中使用 PRE/POST 提交处理程序来对行进行排序,但是表单仍然忽略新顺序。
最后(有点)起作用的是:
在 Recipe 实体中,定义 sortIngredients()
对内存中的 ArrayCollection 进行排序的方法,
public function sortIngredients()
{
$sort = \Doctrine\Common\Collections\Criteria::create();
$sort->orderBy(Array(
'priority' => \Doctrine\Common\Collections\Criteria::ASC
));
$this->ingredients = $this->ingredients->matching($sort);
return $this;
}
然后,在控制器中:
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
$recipe->sortIngredients();
// repeatedly create and process form with already sorted ingredients
$form = $this->createEditForm($recipe);
$form->handleRequest($request);
// ... do the rest of the controller stuff, flush(), etc
这行得通,但是表单被创建和处理了两次,老实说它看起来像一个 hack...
我正在寻找解决问题的更好方法。
您需要使用您的表单类型的 finishView 方法。
代码示例如下:
public function finishView(FormView $view, FormInterface $form, array $options)
{
usort($view['photos']->children, function (FormView $a, FormView $b) {
/** @var Photo $objectA */
$objectA = $a->vars['data'];
/** @var Photo $objectB */
$objectB = $b->vars['data'];
$posA = $objectA->getSortOrder();
$posB = $objectB->getSortOrder();
if ($posA == $posB) {
return 0;
}
return ($posA < $posB) ? -1 : 1;
});
}
可以将箭头函数 https://www.php.net/manual/en/functions.arrow.php with spaceship https://www.php.net/manual/en/migration70.new-features.php 与 PHP 结合起来 7.
用前面的例子:
public function finishView(FormView $view, FormInterface $form, array $options)
{
usort($view['photos']->children, fn (FormView $a, FormView $b) => $a->vars['data']->getSortOrder() <=> $b->vars['data']->getSortOrder());
}
一个更高级的示例,使用任何有序字段从任何地方对任何集合进行排序:
public function finishView(FormView $view, FormInterface $form, array $options)
{
$view['blocks']->children = array_merge($view['blockTexts']->children, $view['blockImages']->children);
usort($view['blocks']->children, fn (FormView $a, FormView $b) => $a->vars['data']->orderNumber <=> $b->vars['data']->orderNumber);
}