Symfony2 - 如何在表单提交后从唯一约束错误中恢复?

时间:2013-12-14 22:55:18

标签: php mysql forms symfony doctrine-orm

我有一个名为Contact的实体,其中包含一个唯一字段email。我还有一个用于管理界面的表单类型,我们称之为ContactType。下面描述的所有内容都使用使用ContactType构建的表单:

我们假设我想添加一封电子邮件mr.validated@example.com的联系人,当然可以。然后我再次尝试bam,验证启动,错误消息说明发生了什么。完美!

现在我想添加另一个联系人,这次是通过电子邮件mr.race.condition@example.com,但是哎呀,我不小心提交了两次表格!这两个请求都按如下方式处理:

 |    Request 1    |     Request 2
-+-----------------+-----------------
1|  $form->bind()  |   $form->bind()
2|   Validation    |    Validation    
3|   $em->flush()  |    $em->flush()

在这两种情况下验证都已传递,因为具有此类电子邮件的Contact实体尚未在数据库中。这导致两个Insert查询使用相同的电子邮件。 MySQL将阻止第二个,所以Doctrine将抛出异常,用户将看到错误500而不是“已经使用电子邮件”。

我的问题是:如何让Symfony为我处理?我只是想告诉用户他必须输入不同的电子邮件地址。

我当然可以这样做:

try {
    $this->getDoctrine()->getManager()->flush();
} catch (DBALException $e) {
    $pdoException = $e->getPrevious();
    if ($pdoException &&
        $pdoException instanceof PDOException &&
        $pdoException->getCode() === '23000'
    ) {
// let the form know about the error
    } else throw $e;
}

但这是错误的,每次我必须处理唯一约束时都需要复制粘贴代码,并且在有多个唯一索引时会出现问题。

1 个答案:

答案 0 :(得分:2)

这可能不是SO风格的真正答案,就像我个人的意见一样,但我只是想帮助你。我会有点批评并在这里获得很多投票,但只要你对手头的问题更有信心,那就没关系。


编辑:添加了一个代码示例

如果你真的,真的需要解决PHP mutex及其衍生物。只需在锁定的代码段内保护(包装)您的关键竞争条件代码,您就可以确保两个线程不能同时执行(只要您只有一台前端机器)。

示例用法如下:

$file = fopen("code_section_001.lock", "w+");

if (flock($file,LOCK_EX))
{
    // $form->bind()
    // Validation
    // $em->flush()

  flock($file,LOCK_UN);
}
else
{
  echo "Error locking file!";
}

fclose($file);

如果可以,请使用try {} finally {},这取决于您使用的PHP版本。

那就是说,我不鼓励这种做法,因为它有一些性能影响。


我的印象是你的过度。验证和插入之间经过了多长时间?微秒?有多少订阅者会在一天内输入他们的电子邮件?他们多久会使用相同的电子邮件?喜欢......从不?如果所有这些巧合都导致了这种竞争条件,那么HTTP 500错误代码就没那么错了。

因为如果它是合法用户:

  • 他会注册一次;
  • 他将根据定义使用他的电子邮件;
  • 他可以在某种错误页面的远程事件中重试。

相反,一个非合法用户(机器人?)他将:

  • 多次注册;
  • 使用随机或已知或被盗的电子邮件地址;
  • 在一连串的HTTP请求中重试。

在第二种情况下,我建议你回复500!这就是网站应该做的事情。

您已经采取了优雅错误恢复的步骤(自定义错误消息),因此真正的人类用户很可能看不到HTTP 500错误。

顺便说一句,PHP和Symfony经常出现这样的问题,并以完美的方式解决它意味着弄乱一个简单而干净的代码。这真的值得吗,你的代码混乱了吗?

并且考虑到电子邮件不一定是唯一的。例如,我可以使用不太知名的后缀功能。它受到gmail和其他人的支持,允许有+后缀,如下所示:

someone@somewhere.com
someone+one@somewhere.com
someone+two@somewhere.com

所有三个地址都会转到同一个收件箱(someone@somewhere.com),但这些地址确实是唯一的吗?

也许你应该投入更多的细节而不是微秒的失败之窗。 重要的是数据库不插入多次,这是您已经通过唯一约束完成的。该错误消息不是很干净,实际上是次要的。

你有没有制作过它?我的意思是测试设置并实际获得了500而不是错误页面?