フラッシュを実行する前に、Doctrine 2で重複キーをチェックする簡単な方法はありますか?
9 に答える
UniqueConstraintViolationException
あなたはそのように捕まえることができます:
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
// ...
try {
// ...
$em->flush();
}
catch (UniqueConstraintViolationException $e) {
// ....
}
私はこの戦略を使用して、flush()の後に一意の制約をチェックします。これはあなたが望むものではないかもしれませんが、他の誰かを助けるかもしれません。
flush()を呼び出すときに、一意の制約が失敗すると、コード23000で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;
}
失敗した列の名前を取得する必要がある場合:
接頭辞付きの名前でテーブルインデックスを作成します。'個性的_'
* @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;
完璧ではありませんが、機能します...
私も少し前にこの問題に遭遇しました。主な問題はデータベース固有の例外ではなく、PDOExceptionがスローされるとEntityManagerが閉じられるという事実です。つまり、フラッシュしたいデータがどうなるかわからないということです。しかし、これはトランザクション内で行われると思うので、おそらくデータベースには保存されないでしょう。
ですから、この問題について考えていたときに、この解決策を思いつきましたが、実際にそれを書く時間はまだありませんでした。
- これは、イベントリスナー、特にonFlushイベントを使用して実行できます。このイベントは、データがデータベースに送信される前に呼び出されます(変更セットが計算された後、つまり、どのエンティティが変更されたかはすでにわかっています)。
- このイベントリスナーでは、変更されたすべてのエンティティでキーを参照する必要があります(プライマリの場合、@ Idのクラスメタデータを検索します)。
- 次に、キーの基準で検索メソッドを使用する必要があります。結果が見つかった場合は、独自の例外をスローする機会があります。これにより、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を使用している場合は、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"})
* })
*/
重複するエラーをキャッチしたいだけの場合。コード番号を確認するだけではいけません
$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 {
//....
}
}
Symfony 2では、実際には\PDOExceptionではなく\Exceptionをスローします
try {
// ...
$em->flush();
}
catch( \Exception $e )
{
echo $e->getMessage();
echo $e->getCode(); //shows '0'
### handle ###
}
$ e-> getMessage()は、次のようなものをエコーします。
'INSERT INTO(...)VALUES(?、?、?、?)'をparams [...]で実行中に例外が発生しました:
SQLSTATE [23000]:整合性制約違反:1062キー'PRIMARY'の重複エントリ'...'
最も簡単な方法はこれです:
$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(不明な列)などの他のコードも返されます。