我有一个名为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;
}
但这是错误的,每次我必须处理唯一约束时都需要复制粘贴代码,并且在有多个唯一索引时会出现问题。
答案 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错误代码就没那么错了。
因为如果它是合法用户:
相反,一个非合法用户(机器人?)他将:
在第二种情况下,我建议你回复500!这就是网站应该做的事情。
您已经采取了优雅错误恢复的步骤(自定义错误消息),因此真正的人类用户很可能看不到HTTP 500错误。
顺便说一句,PHP和Symfony经常出现这样的问题,并以完美的方式解决它意味着弄乱一个简单而干净的代码。这真的值得吗,你的代码混乱了吗?
并且考虑到电子邮件不一定是唯一的。例如,我可以使用不太知名的后缀功能。它受到gmail和其他人的支持,允许有+
后缀,如下所示:
someone@somewhere.com
someone+one@somewhere.com
someone+two@somewhere.com
所有三个地址都会转到同一个收件箱(someone@somewhere.com
),但这些地址确实是唯一的吗?
也许你应该投入更多的细节而不是微秒的失败之窗。 重要的是数据库不插入多次,这是您已经通过唯一约束完成的。该错误消息不是很干净,实际上是次要的。
你有没有制作过它?我的意思是测试设置并实际获得了500而不是错误页面?