我有一个名为 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/