51

在进行刷新之前,是否有一种简单的方法可以使用 Doctrine 2 检查重复键?

4

9 回答 9

56

您可以这样捕获UniqueConstraintViolationException

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;

// ...

try {
   // ...
   $em->flush();
}
catch (UniqueConstraintViolationException $e) {
    // ....
}
于 2016-03-02T16:03:08.893 回答
20

我使用此策略在flush()之后检查唯一约束,可能不是您想要的,但可能对其他人有所帮助。


当您调用flush()时,如果唯一约束失败,则会引发PDOException ,代码为23000

try {
    // ...
    $em->flush();
}
catch( \PDOException $e )
{
    if( $e->getCode() === '23000' )
    {
        echo $e->getMessage();

        // Will output an SQLSTATE[23000] message, similar to:
        // Integrity constraint violation: 1062 Duplicate entry 'x'
        // ... for key 'UNIQ_BB4A8E30E7927C74'
    }

    else throw $e;
}

如果您需要获取失败列的名称

创建带有前缀名称的表索引,例如。'独特的_'

 * @Entity
 * @Table(name="table_name",
 *      uniqueConstraints={
 *          @UniqueConstraint(name="unique_name",columns={"name"}),
 *          @UniqueConstraint(name="unique_email",columns={"email"})
 *      })

不要在 @Column 定义中将您的列指定为唯一的

这似乎用随机名称覆盖了索引名称......

 **ie.** Do not have 'unique=true' in your @Column definition

重新生成表后(您可能需要删除它并重建),您应该能够从异常消息中提取列名。

// ...
if( $e->getCode() === '23000' )
{
    if( \preg_match( "%key 'unique_(?P<key>.+)'%", $e->getMessage(), $match ) )
    {
        echo 'Unique constraint failed for key "' . $match[ 'key' ] . '"';
    }

    else throw $e;
}

else throw $e;

不完美,但它的工作...

于 2011-05-22T19:56:54.913 回答
4

我前段时间也遇到过这个问题。主要问题不是特定于数据库的异常,而是当抛出 PDOException 时 EntityManager 关闭的事实。这意味着您无法确定要刷新的数据会发生什么。但可能它不会保存在数据库中,因为我认为这是在事务中完成的。

所以当我在思考这个问题时,我想出了这个解决方案,但我还没有时间实际编写它。

  1. 可以使用事件侦听器来完成,尤其是 onFlush 事件。在将数据发送到数据库之前调用此事件(在计算变更集之后 - 因此您已经知道哪些实体已更改)。
  2. 在此事件侦听器中,您必须浏览所有已更改实体的键(对于主要实体,它将在类元数据中查找 @Id)。
  3. 然后,您将不得不使用带有键标准的 find 方法。如果您找到结果,您有机会抛出您自己的异常,该异常不会关闭 EntityManager,您可以在模型中捕获它并对数据进行一些更正,然后再次尝试刷新。

这个解决方案的问题在于它可能会生成大量对数据库的查询,因此需要进行大量优化。如果您只想在少数地方使用这种东西,我建议您检查可能出现重复的地方。例如,您要在哪里创建实体并保存它:

$user = new User('login');
$presentUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('login' => 'login'));
if (count($presentUsers)>0) {
    // this login is already taken (throw exception)
}
于 2011-01-08T10:51:40.713 回答
4

如果你使用 Symfony2,你可以在 flush() 之前使用UniqueEntity(...)form->isValid()捕获重复项。

我在这里发布这个答案,但它似乎很有价值,因为很多Doctrine 用户也将使用 Symfony2。需要明确的是:这使用了 Symfony 的验证类,该类在后台使用实体存储库进行检查(可配置,但默认为findBy)。

在您的实体上,您可以添加注释:

use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @UniqueEntity("email")
 */
class YourEntity {

然后在您的控制器中,将请求提交给表单后,您可以检查您的验证。

$form->handleRequest($request);

if ( ! $form->isValid())
{
    if ($email_errors = $form['email']->getErrors())
    {
        foreach($email_errors as $error) {
           // all validation errors related to email
        }
    }
…

我建议将此与彼得的答案结合起来,因为您的数据库模式也应该强制执行唯一性:

/**
 * @UniqueEntity('email')
 * @Orm\Entity()
 * @Orm\Table(name="table_name",
 *      uniqueConstraints={
 *          @UniqueConstraint(name="unique_email",columns={"email"})
 * })
 */
于 2014-06-14T01:09:04.147 回答
2

如果您只想捕获重复的错误。你不应该只检查代码

$e->getCode() === '23000'

因为这将捕获其他错误,例如字段“用户”不能为空。我的解决方案是检查错误消息,如果它包含文本“重复条目”

                try {
                    $em->flush();
                } catch (\Doctrine\DBAL\DBALException $e) {

                    if (is_int(strpos($e->getPrevious()->getMessage(), 'Duplicate entry'))) {
                        $error = 'The name of the site must be a unique name!';
                    } else {
                        //....
                    }
                }
于 2014-02-01T21:02:50.733 回答
2

在 Symfony 2 中,它实际上抛出了一个 \Exception,而不是一个 \PDOException

try {
    // ...
    $em->flush();
}
catch( \Exception $e )
{
   echo $e->getMessage();
   echo  $e->getCode(); //shows '0'
   ### handle ###

}

$e->getMessage() 回显如下内容:

使用参数 [...] 执行 'INSERT INTO (...) VALUES (?, ?, ?, ?)' 时发生异常:

SQLSTATE [23000]:违反完整性约束:1062 键 'PRIMARY' 的重复条目 '...'

于 2014-02-15T09:42:52.860 回答
0

最简单的方法应该是这样的:

$product    = $entityManager->getRepository("\Api\Product\Entity\Product")->findBy(array('productName' => $data['product_name']));
if(!empty($product)){
 // duplicate
}
于 2013-01-21T08:12:40.707 回答
0

我想特别添加关于 PDOExceptions 的内容——

23000 错误代码是 MySQL 可以返回的一系列完整性约束违规的总括代码。

因此,处理 23000 错误代码对于某些用例来说不够具体。

例如,您可能希望对重复记录违规与对丢失外键违规的反应不同。

以下是如何处理此问题的示例:

try {
     $pdo -> executeDoomedToFailQuery();
} catch(\PDOException $e) {
     // log the actual exception here
     $code = PDOCode::get($e);
     // Decide what to do next based on meaningful MySQL code
}

// ... The PDOCode::get function

public static function get(\PDOException $e) {
    $message = $e -> getMessage();
    $matches = array();
    $code = preg_match('/ (\d\d\d\d) / ', $message, $matches);
    return $code;
}

我意识到这并不像问题所问的那么详细,但我发现这在很多情况下都非常有用,并且不是特定于 Doctrine2 的。

于 2012-07-18T10:00:31.813 回答
0

我用过这个,它似乎工作。它返回特定的 MySQL 错误号 - 即重复条目的 1062 - 准备好让您按照自己的喜好处理。

try
{
    $em->flush();
}
catch(\PDOException $e)
{
    $code = $e->errorInfo[1];
    // Do stuff with error code
    echo $code;
}

我用其他一些场景对此进行了测试,它也会返回其他代码,例如 1146(表不存在)和 1054(未知列)。

于 2013-07-08T07:42:00.063 回答