はじめに
自分が見てきたコードでよく間違えられて書かれるトランザクション処理をまとめました。
原因として多いのが、例外処理が正しく書けていないパターンです。
開発環境
- PHP 7.4.12
- CakePHP 4.2.5
NG集
/** @var \Cake\Database\Connection $connection */
$connection = ConnectionManager::get('default');
try {
// トランザクション開始
$connection->begin();
$this->Articles->save($saveData);
$connection->commit();
} catch (Exception $e) {
// キャッチされない
$connection->rollback();
echo $e->getMessage();
}
/** @var \Cake\Database\Connection $connection */
$connection = ConnectionManager::get('default');
try {
// トランザクション開始
$connection->transactional(function ($connection) use ($saveData) {
return $this->Articles->save($saveData);
});
} catch (Exception $e) {
// キャッチされない
echo $e->getMessage();
}
- 〇 問題点
- ConnectionManagerのtransactionalメソッドはfalse を返したら、ロールバックを発行して false を返すため、例外をcatchできない。(※トランザクション処理自体は正しい)
- 例外を代入する変数には何も入らない。
OK集
/** @var \Cake\Database\Connection $connection */
$connection = ConnectionManager::get('default');
try {
// トランザクション開始
$connection->begin();
if ($this->Articles->save($saveData) === false) {
throw new Exception('保存に失敗しました');
}
$connection->commit();
} catch (Exception $e) {
$connection->rollback();
echo $e->getMessage();
}
/** @var \Cake\Database\Connection $connection */
$connection = ConnectionManager::get('default');
try {
// トランザクション開始
$connection->transactional(function ($connection) use ($saveData) {
$this->Articles->saveOrFail($saveData);
});
} catch (\Cake\ORM\Exception\PersistenceFailedException $e) {
echo $e->getMessage();
}
- 〇 改善点
- saveOrFail()により、PersistenceFailedExceptionを投げているため、再スローでキャッチが可能
補足
CakePHP4 transactionalについて
このコネクションインスタンスへのインターフェースに加えて、さらに begin/commit/rollback を 簡単にハンドリングする transactional() メソッドが提供されています。
- 引数で渡されたクロージャーを実行します。
- もしクロージャー内で例外が発生したら、ロールバックを発行して例外を再度 throw します。
- クロージャーが false を返したら、ロールバックを発行して false を返します。
- クロージャーが正常終了したら、トランザクションをコミットします。
CakePHP4 saveOrFailについて
このメソッドを使用すると、次の条件で Cake\ORM\Exception\PersistenceFailedException を投げます。
- アプリケーションルールのチェックに失敗した場合
- エンティティーにエラーが含まれている場合
- 保存がコールバックによって中断された場合