web-dev-qa-db-ja.com

Doctrine 2を使用した重複キーの確認

フラッシュする前にDoctrine 2で重複キーをチェックする簡単な方法はありますか?

48
tom

UniqueConstraintViolationException をキャッチできます:

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;

// ...

try {
   // ...
   $em->flush();
}
catch (UniqueConstraintViolationException $e) {
    // ....
}
47
DonCallisto

この戦略を使用して、flush()の後に一意の制約をチェックします。これはあなたが望むものではないかもしれませんが、他の誰かを助けるかもしれません。


flush()を呼び出すと、一意の制約が失敗すると、コード230PDOExceptionがスローされます。

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;

完璧ではありませんが、うまくいきます...

20
Peter Johnson

挿入の前にSELECTクエリを実行することが望んでいない場合、flush()を実行して例外をキャッチすることしかできません。

Doctrine DBAL 2.3では、Doctrine DBALException:

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

私も先ほどこの問題に遭遇しました。主な問題は、データベース固有の例外ではなく、PDOExceptionがスローされたときにEntityManagerが閉じられるという事実です。これは、フラッシュしたいデータがどうなるかわからないことを意味します。しかし、おそらくトランザクション内で行われるため、データベースに保存されないでしょう。

そのため、この問題について考えていたときにこの解決策を思いつきましたが、実際に書く時間はまだありませんでした。

  1. event listeners 、特に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)
}
4
Vašek Purchart

Symfony2を使用している場合、 niqueEntity(…)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"})
 * })
 */
3
Mark Fox

Symfony 2では、\ PDOExceptionではなく、\ Exceptionを実際にスローします

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

}

$ e-> getMessage()は次のようにエコーします:

Params [...]で 'INSERT INTO(...)VALUES(?、?、?、?)'を実行中に例外が発生しました:

SQLSTATE [23000]:整合性制約違反:1062キー 'PRIMARY'の重複したエントリ '...'

2
Aris

重複エラーをキャッチしたいだけの場合。コード番号を確認するだけではいけません

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

最も簡単な方法はこれです:

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

特に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固有ではないことがわかります。

0
Peter M. Elias

私はこれを使用し、うまくいくようです。特定のMySQLエラー番号(つまり、重複エントリの1062)が返されます。

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

他のいくつかのシナリオでこれをテストしましたが、1146(テーブルは存在しません)や1054(不明な列)のような他のコードを返します。

0
Jeff S.