Symfony 2 中的 ArrayCollection(表单集合)索引冲突

Posted

技术标签:

【中文标题】Symfony 2 中的 ArrayCollection(表单集合)索引冲突【英文标题】:ArrayCollection (Collection of forms) index collision in Symfony 2 【发布时间】:2015-09-14 10:49:54 【问题描述】:

我正在使用 Symfony2 来构建我的页面。 当我尝试更新表单集合时(如说明书条目“How to Embed a Collection of Forms”中所述),我遇到了前端索引和后端 ArrayCollection 索引的冲突。

我有关系用户 地址 (OneToMany)。用户想要创建/更新/删除他的地址,因此他可以在 javascript 部分新地址元素的帮助下在前端添加/删除。他做了以下事情:

(1) 添加新地址(索引为:0)

(2) 添加新地址(索引为:1)并立即再次删除该地址

(3) 添加新地址(索引:2)。

当他点击保存按钮时,以下代码保存/更新用户(及其地址):

 $this->em->persist($user);
 $this->em->flush();

然后,例如新地址会被正确地持久化到数据库中。 现在用户想要更新地址,例如索引为 0。 当他现在单击保存按钮时,它会使用“索引 0”更新地址,但同时,它会再次将“索引 2”的地址添加到数据库(对象)中。 为了更好地理解这个问题,我画了一个小插图(手工制作,对不起我的艺术水平不好):

现在,我的对象/数据库中有两倍的“索引 1”地址。 我知道为什么会发生这种情况,这是因为第一个“索引 1”地址被映射到 ArrayCollection 元素“数字 1”,而第二个地址被映射到“数字 2”(因为前端名称“索引 2”)。 你可以说:“它只是填满地址,直到它到达后端的前端索引”.. 但是我该如何解决这个问题呢?

网站说明: 使用 ajax 请求会发生这种行为,因为如果您在单击“保存按钮”后重新加载页面,它将使用后端中的索引正确地重新索引前端中的地址。

我对处理这种情况的建议:

在服务器端单击保存后重新索引前端索引 索引。这是我的问题的明确/唯一解决方案吗?

【问题讨论】:

【参考方案1】:

是的,这是 Symfony 表单收集的问题,恕我直言,它没有简单的解决方案。但我不得不问为什么你不做与页面刷新完全相同的事情?您只能用集合刷新 html sn-p。 sn-p 的 HTML 代码可以来自服务器端。回到您的问题 - 是的,在您不想尝试自己编写自定义集合类型之前,重新索引是一个很好的解决方案。

symfony/symfony/issues/7828

在集合中验证存在类似问题 - symfony/symfony/issues/7468。

我认为默认集合类型和 Symfony 文档中的教程有一些缺点。希望对您有所帮助。

【讨论】:

我想在保存新项目后仅“重新渲染/刷新”集合部分 div .. 但我怎样才能做到最好呢?我怎样才能只刷新收藏部分?几周前,当我需要验证集合项目时,我已经看过这两个问题,这部分工作正常我只需要重新索引/刷新集合项目,所以我很想听听你的解决方案关于你将如何做令人耳目一新的工作来给你你赚到的赏金=) 我的意思是你会在服务器端创建带有地址的整个 div 还是将新地址作为 json 传递给客户端并在 js 部分中创建新的容器/div? 嗯,这取决于什么对你更好(html vs. json)。你有三个选择。创建 sn-p 以响应保存 ajax 请求。它可能需要一些魔法形式的索引。或者您可以在保存请求后发出下一个 ajax 请求,创建清晰的表单并获取 sn-p。最后一个是在 javascript 中重新索引(但请确保您知道默认情况下服务器上的集合是如何排序的)。不幸的是,我没有工作代码。正如我所说 - 这很痛苦,如果有人知道好的解决方案,我正在倾听。【参考方案2】:

我通过修改 Symfony 文档中给出的 Javascript/Jquery 代码在客户端解决了这个问题。

我不是通过计算子元素来为新元素编号,而是查看最后一个元素的 id 并使用正则表达式提取其索引。

添加元素时,我将最后一个索引增加 1。这样,我从不使用相同的索引。

这是我的代码:

// Initializing default index at 0
var index = 0;

// Looking for collection fields in the form
var $findinput = $container.find(':input');

// If fields found then looking for last existing index
if ( $findinput.length > 0 ) 

    // Reading id of last field
    var myString = $findinput.last().attr('id')

    // Setting regular expression to extract number from id containing letters, hyphens and underscores
    var myRegex = /^[-_A-Za-z]+([0-9]+)[-_A-Za-z]*$/

    // Executing regular expression on last collection field id
    var test = myRegex.exec(myString);

    // Extracting last index and incrementing by 1
    if (test.length > 0) index = parseInt(test[1]) + 1;

【讨论】:

这听起来也不错,我现在只是在成功提交后重新索引表单字段,这也很可靠【参考方案3】:

在过去的两年里,我遇到过几次这个问题。通常,遵循 Symfony 教程 How to Embed a Collection of Forms 可以很好地完成这项工作。您需要做一些 javascript 编码来添加“编辑/更新”功能,但除此之外 - 使用这种方法应该没问题。

另一方面,如果您有一个非常复杂的表单,它使用AJAX 来验证/保存/计算/业务逻辑/等,我发现将最终数据存储到数组中通常更好会议。提交表单后,在 if($form->isValid())... 块内,您将拥有

$collection = new ArrayCollection($mySessionPlainArray);
$user->setAddress($collection);

我想警告您要小心数据的序列化 - 如果您使用实体,您可能会遇到一些尴尬的异常或不当行为(请参阅我的 question)。

很抱歉我不能提供更多代码,但这个问题的解决方案有时相当复杂。

【讨论】:

以上是关于Symfony 2 中的 ArrayCollection(表单集合)索引冲突的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 2 中的 ArrayCollection(表单集合)索引冲突

Symfony 3,一个应用程序中的 2 个防火墙

根据 symfony 2.0 中的路由自定义 403 错误页面

如何防止访问 Symfony 2 中的特定路由

XmlFileLoader.php 第 281 行中的 symfony 2.8 InvalidArgumentException

Symfony 2 中的 Mysql 查看表