フラッシュする前にDoctrine 2で重複キーをチェックする簡単な方法はありますか?
UniqueConstraintViolationException
をキャッチできます:
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
// ...
try {
// ...
$em->flush();
}
catch (UniqueConstraintViolationException $e) {
// ....
}
この戦略を使用して、flush()の後に一意の制約をチェックします。これはあなたが望むものではないかもしれませんが、他の誰かを助けるかもしれません。
flush()を呼び出すと、一意の制約が失敗すると、コード230でPDOExceptionがスローされます。
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;
完璧ではありませんが、うまくいきます...
挿入の前に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();
}
}
私も先ほどこの問題に遭遇しました。主な問題は、データベース固有の例外ではなく、PDOExceptionがスローされたときに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)
}
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"})
* })
*/
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'の重複したエントリ '...'
重複エラーをキャッチしたいだけの場合。コード番号を確認するだけではいけません
$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
}
特に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固有ではないことがわかります。
私はこれを使用し、うまくいくようです。特定のMySQLエラー番号(つまり、重複エントリの1062)が返されます。
try
{
$em->flush();
}
catch(\PDOException $e)
{
$code = $e->errorInfo[1];
// Do stuff with error code
echo $code;
}
他のいくつかのシナリオでこれをテストしましたが、1146(テーブルは存在しません)や1054(不明な列)のような他のコードを返します。