forms - Symfony 表单 CollectionType 字段的顺序

标签 forms symfony doctrine-orm arraycollection

在我的模型中,我有一个 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 Controller ,我希望用户能够动态添加成分。我还希望用户拖放成分以在配方中设置它们的优先级(顺序)。我为此使用 CollectionType 表单字段。

和这个页面作为教程:

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

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

在 Controller 中,我加载实体并创建如下表单:
  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 类中使用 PRE/POST 提交处理程序来对行进行排序,但是表单仍然忽略新的顺序。

    (有点)工作的最后一件事是:

    在配方实体中,定义 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;
      }
    

    然后,在 Controller 中:
      $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
    

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

    我正在寻找更好的方法来解决问题。

    最佳答案

    您需要使用表单类型的 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;
        });
    }
    

    关于forms - Symfony 表单 CollectionType 字段的顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34967795/

    相关文章:

    symfony - 使用 DoctrineFixturesBundle 为生产数据库做种子是否安全?

    php - 仅选择页面上链接的相关新闻项(多一二多关系)

    php - 将 jQuery 表单序列化数据发布到 php

    html - 在哪里可以找到 HTML5 Range 输入的 Bootstrap 样式?

    php - Controller 没有容器集,是不是忘记定义为服务订阅者了

    php - Sf2 RedirectResponse 不工作

    javascript - textarea 命名难题 - 使用 php 和 javascript

    php - 验证表单并提交而不刷新

    php - 如何在奏鸣曲管理中显示自定义错误

    symfony - 在级联中持久化 FOSUser 实体的好方法