Symfony表单CollectionType字段的顺序

Kar*_*lis 12 forms arraycollection symfony doctrine-orm

在我的模型中,我有一个食谱实体和成分实体.在Recipe实体中,关系定义如下:

/**
 * @ORM\OneToMany(targetEntity="Ingredient", mappedBy="recipe", cascade={"remove", "persist"}, orphanRemoval=true) 
 * @ORM\OrderBy({"priority" = "ASC"})
 */
private $ingredients;
Run Code Online (Sandbox Code Playgroud)

在Ingredient实体中:

/**
 * @ORM\ManyToOne(targetEntity="Recipe", inversedBy="ingredients")
 * @ORM\JoinColumn(name="recipe_id", referencedColumnName="id")
 */
private $recipe;
Run Code Online (Sandbox Code Playgroud)

我正在研究配方的CRUD控制器,我希望用户能够动态添加配料.我还希望用户拖放成分以在配方中设置其优先级(顺序).我正在使用CollectionType表单字段.

这个页面作为教程:

http://symfony.com/doc/current/cookbook/form/form_collections.html

到目前为止,添加和显示配方工作正常,但是编辑/更新操作存在问题,我将在下面尝试描述:

在控制器中,我加载实体并创建如下形式:

  public function updateAction($id, Request $request)
  {
      $em = $this->getDoctrine()->getManager();
      $recipe = $em->getRepository('AppBundle:Recipe')->find($id);


      $form = $this->createEditForm($recipe);
      $form->handleRequest($request);

      ...

    }
Run Code Online (Sandbox Code Playgroud)

由于优先级保存在DB中,而且我有@ORM\OrderBy({"priority" = "ASC"}),原料的初始加载和显示工作正常.但是,如果用户拖放成分,则优先级值会发生变化.如果存在表单验证错误并且需要重复显示表单,则表单中的成分将按旧顺序显示,即使优先级值已更新.

例如,我在DB中有以下初始Ingredient => priority值:

  • 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();
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为显然不允许在已经有输入的表单上使用setData().

我还尝试设置DataTransformer来对行进行排序,但表单忽略了新的顺序.

我还尝试在FormType类中使用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;
  }
Run Code Online (Sandbox Code Playgroud)

然后,在控制器中:

  $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
Run Code Online (Sandbox Code Playgroud)

这是有效的,但表单是创建和处理两次,老实说,它看起来像一个黑客......

我正在寻找一种更好的方法来解决问题.

小智 17

您需要使用表单类型的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;
    });
}
Run Code Online (Sandbox Code Playgroud)

  • 作品!!!花了半天时间......谢谢.太糟糕了,Symfony网站上关于这种方法的文档并不多. (3认同)