Doctrine 使用原则2检查重复密钥

Doctrine 使用原则2检查重复密钥,doctrine,doctrine-orm,Doctrine,Doctrine Orm,在进行刷新之前,有没有一种简单的方法可以用Doctrine 2检查重复的密钥?我以前也遇到过这个问题。主要问题不是特定于数据库的异常,而是当引发PDOException时EntityManager关闭的事实。这意味着您无法确定要刷新的数据会发生什么情况。但它可能不会保存在数据库中,因为我认为这是在事务中完成的 所以当我思考这个问题的时候,我想出了这个解决方案,但我还没有时间去写它 可以使用,特别是onFlush事件来完成。此事件在数据发送到数据库之前调用(在计算变更集之后-因此您已经知道哪些实体

在进行刷新之前,有没有一种简单的方法可以用Doctrine 2检查重复的密钥?

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

所以当我思考这个问题的时候,我想出了这个解决方案,但我还没有时间去写它

  • 可以使用,特别是onFlush事件来完成。此事件在数据发送到数据库之前调用(在计算变更集之后-因此您已经知道哪些实体被更改)
  • 在这个事件监听器中,您必须浏览所有更改的实体以查找它们的键(对于主监听器,它将在类元数据中查找@Id)
  • 然后,您必须使用一个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)
    }
    

    我使用此策略检查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;
    }
    

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

    创建带有前缀名称的表索引,例如“unique”

     * @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;
    
    /。。。
    如果($e->getCode()=='23000')
    {
    if(\preg_match(“%key”unique_(?P.+)“%”,$e->getMessage(),$match))
    {
    回显键“.”的唯一约束失败。$match['key'].'”;
    }
    否则抛出$e;
    }
    否则抛出$e;
    


    虽然不完美,但它可以工作…

    我想补充一点,特别是关于PDO例外--

    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;
    }
    

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

    最简单的方法应该是:

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

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

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

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

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

    $e->getCode() === '23000'
    
    因为这将捕获其他错误,例如字段“user”不能为空。我的解决方案是检查错误消息,如果它包含文本“Duplicate entry”

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

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

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

    使用参数[…]执行“插入到(…)值(?,,,?)”时发生异常:


    SQLSTATE[23000]:完整性约束冲突:1062键“PRIMARY”的重复条目“…”

    如果您使用的是Symfony2,则可以与
    form->isValid()
    一起使用,以在flush()之前捕获重复项

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

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

    使用Symfony\Bridge\Doctrine\Validator\Constraints\uniquentity;
    /**
    *@uniquentity(“电子邮件”)
    */
    将您的实体分类{
    
    然后在控制器中,将请求传递给表单后,您可以检查验证

    $form->handleRequest($request);
    如果(!$form->isValid())
    {
    如果($email_errors=$form['email']->getErrors())
    {
    foreach($email\u errors作为$error){
    //与电子邮件相关的所有验证错误
    }
    }
    …
    
    我建议将这一点与Peter的答案结合起来,因为您的数据库模式也应该强制唯一性:

    /**
    *@uniquentity('email')
    *@Orm\Entity()
    *@Orm\Table(name=“Table\u name”,
    *唯一约束={
    *@UniqueConstraint(name=“unique_email”,columns={“email”})
    * })
    */
    
    您可以捕获以下内容:


    我真的没有答案,但我想知道刷新前的检查与刷新和处理错误有什么不同(假设存在重复的密钥)。刷新时会引发特定于数据库的异常。此处提供的大多数解决方案都没有考虑到这样一个事实,即您无法事先检查重复项,因为这不是一个原子操作,因此,如果其他线程插入,您仍然可以有重复的值
    use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
    
    // ...
    
    try {
       // ...
       $em->flush();
    }
    catch (UniqueConstraintViolationException $e) {
        // ....
    }