Порядок создания поля CollectionType формы Symfony
В моей модели у меня есть объект рецепта и объект Ingredient. В объекте Рецепт отношение определяется следующим образом:
/**
* @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"})
, начальная загрузка и отображение ингредиентов прекрасно работает. Однако, если пользователь перетаскивает и опускает ингредиенты, значения приоритета изменяются. Если есть ошибки проверки формы и форма должна отображаться повторно, компоненты внутри формы отображаются в прежнем порядке, даже если значения приоритета обновляются.
Например, у меня есть следующие начальные значения Ingredient = > priority в DB:
Строки формы отображаются в порядке: A, B, C;
После того как пользователь изменит порядок, я:
но строки формы все еще отображаются как A, B, C;
Я понимаю, что форма была инициализирована с помощью порядка A, B, C и обновления priority
не меняет порядок элементов ArrayCollection. Но я (почти) не знаю, как его изменить.
То, что я пробовал до сих пор:
$form->getData();
// sort in memory
$form->setData();
Это не работает, поскольку, по-видимому, не разрешено использовать setData() в форме, которая уже имеет вход.
Я также попытался установить DataTransformer для упорядочения строк, но форма игнорирует новый порядок.
Я также пытался использовать обработчики отправки PRE/POST в классе FormType для упорядочения строк, однако форма все еще игнорирует новый порядок.
Последнее, что работает (вид):
В сущности рецепта определите метод 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
Это работает, но форма создается и обрабатывается дважды, и, честно говоря, это выглядит как хак...
Я ищу лучший способ решить проблему.
Ответы
Ответ 1
Вам нужно использовать метод 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;
});
}