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

标签 php mysql forms symfony doctrine-orm

我有一个名为 Contact 的实体,它有一个唯一字段 email。我还有一个用于管理界面的表单类型,我们称它为 ContactType。下面描述的所有事情都发生在使用 ContactType 构建的表单中:

假设我想添加一个电子邮件地址为 mr.validated@example.com 的联系人,当然可以。然后我再试一次,砰,验证开始了,错误消息说发生了什么。完美!

现在我想添加另一个联系人,这次使用电子邮件 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 而不是“Email has already taken”。

我的问题是:我如何让 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;
}

但这是错误的,每次我必须处理唯一约束时都需要复制粘贴代码,而且如果有多个唯一索引,那就很麻烦。

最佳答案

这可能不是 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 错误代码并没有那么错。

因为如果是合法用户:

  • 他会注册一次;
  • 他将使用他的电子邮件,这在定义上是独一无二的;
  • 他可以在出现某种错误页面的远程事件中重试。

相反,一个非合法用户(bot?)他会:

  • 多次注册;
  • 使用随机的或众所周知的或被盗的电子邮件地址;
  • 在一系列 HTTP 请求中重试。

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

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

顺便说一下,PHP 和 Symfony 经常会出现这样的问题,要以完美的方式解决它就意味着搞乱原本简单干净的代码。弄乱您的代码真的值得吗?

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

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

所有三个地址都将发送到同一个收件箱 (someone@somewhere.com),但它们真的是独一无二的吗?

也许您应该在这些细节上投入更多,而不是微秒级的故障窗口。 重要的是数据库不会插入多个,您已经通过唯一约束完成了这一点。不是很干净的错误消息确实是次要的。

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

关于php - Symfony2 - 如何在提交表单后从唯一约束错误中恢复?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20589216/

相关文章:

php - 在哪里存储登录尝试和当前登录状态、cookie 或 session ?

php - laravel 拆分数据 5.6

spring - 如何将表单数据发送到 Controller

jquery - 如何使用jquery检查当前表单中的所有复选框?

javascript - jQuery Validation : $. data($ ('form' )[0], 'validator' ).settings 返回 undefined

php - 在 1.2.0 RC4 中使用 Titanium.include() 是否存在问题?

php - 选择查询不适用于链接表(mysql)

php - 将表单中动态生成的名称获取到 $_POST 中

mysql - 顺序数据生成

php - 我可以在使用预加载时运行多个 Symfony 应用程序吗?