使用Doctrine 2检查重复键

时间:2010-10-19 10:05:32

标签: doctrine doctrine-orm

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

10 个答案:

答案 0 :(得分:46)

你可以这样抓住UniqueConstraintViolationException

{{1}}

答案 1 :(得分:19)

我使用此策略在 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;

不完美,但有效......

答案 2 :(得分:7)

如果在插入之前运行SELECT查询不是您想要的,则只能运行flush()并捕获异常。

在Doctrine DBAL 2.3中,您可以通过查看PDO异常错误代码(MySQL为23000,Postgres为23050)安全地检测到唯一约束错误,该错误代码由Doctrine DBALException包装:

        try {
            $em->flush($user);
        } catch (\Doctrine\DBAL\DBALException $e) {
            if ($e->getPrevious() &&  0 === strpos($e->getPrevious()->getCode(), '23')) {
                throw new YourCustomException();
            }
        }

答案 3 :(得分:4)

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

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

  1. 可以使用event listeners完成,尤其是onFlush事件。在将数据发送到数据库之前调用此事件(在计算更改集之后 - 因此您已经知道哪些实体已更改)。
  2. 在此事件侦听器中,您必须浏览所有已更改的实体以获取其键(对于主要内容,它将查看@Id的类元数据)。
  3. 然后你必须使用一个带有密钥标准的find方法。 如果你会找到一个结果,你有机会抛出你自己的异常,它不会关闭EntityManager,你可以在模型中捕获它并在再次尝试刷新之前对数据进行一些修正。
  4. 此解决方案的问题在于它可能会对数据库产生大量查询,因此需要进行大量优化。如果您只想在少数地方使用此类产品,我建议您检查可能出现重复的地方。因此,例如,您想要创建实体并保存它:

    $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)
    }
    

答案 4 :(得分:3)

如果你正在使用Symfony2,你可以使用UniqueEntity(…)form->isValid()来匹配flush()之前的重复项。

我在这里发布这个答案,但它似乎很有价值,因为很多的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
        }
    }
…

我建议将此与Peter的答案相结合,因为您的数据库架构也应该强制执行唯一性:

/**
 * @UniqueEntity('email')
 * @Orm\Entity()
 * @Orm\Table(name="table_name",
 *      uniqueConstraints={
 *          @UniqueConstraint(name="unique_email",columns={"email"})
 * })
 */

答案 5 :(得分:2)

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

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

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

                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 {
                        //....
                    }
                }

答案 6 :(得分:2)

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

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

}

$ e-&gt; getMessage()回声如下:

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

SQLSTATE [23000]:完整性约束违规:1062密钥'PRIMARY'重复输入'...'

答案 7 :(得分:0)

我想特别提到PDOExceptions -

23000错误代码是MySQL可以返回的一系列Integrity Constraint Violations的一揽子代码。

因此,处理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。

答案 8 :(得分:0)

最简单的方法应该是:

$product    = $entityManager->getRepository("\Api\Product\Entity\Product")->findBy(array('productName' => $data['product_name']));
if(!empty($product)){
 // duplicate
}

答案 9 :(得分:0)

我用过这个似乎很有用。它返回特定的MySQL错误号 - 即重复条目的1062 - 准备好您处理自己喜欢的方式。

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

我在其他几个场景中对此进行了测试,它将返回其他代码,如1146(表不存在)和1054(未知列)。