【Laravel/PHP】トランザクションについての備忘録

トランザクションについての備忘録記事アイキャッチ プログラミング
スポンサーリンク

学習の備忘録です。タスク管理アプリを作っています。

今回は、スタンプ獲得の際に作ったコードをトランザクション処理に変えていきます。

スタンプ獲得処理については以下の記事にてまとめてあります。

本記事は個人の学習記録です。内容の正確性を保証するものではありませんので、あらかじめご了承ください。

トランザクションとは

トランザクションとは、複数の登録処理などをひとまとまりの命令にまとめることを指します。

「ここからここまでの処理は一つの処理単位と見なすね」ということです。

今回はスタンプ獲得処理でトランザクションを設定していきますが、

  1. user_statsテーブルのperfect_stamp_countを増やす
  2. stamp_historiesテーブルのis_readカラムをfalseからtrueにする

この2つの処理をひとまとまりの処理にします。

トランザクションは何故必要か?

たとえば銀行の送金処理について、これは「送金する側の残高を減らして、送金される側の残高を増やす」という処理を行います。

「AさんからBさんへ10万円送金する」場合、

  • 「Aさんの残高を10万円分減らす」処理
  • 「Bさんの残高を10万円分増やす」処理

が必要になります。

もしも「Aさんの残高を10万円分減らす」処理は成功したのに、「Bさんの残高を10万円分増やす」処理が失敗したら、「Aさんの残高は減ったのに、Bさんの残高は増えない」事態になってしまいます。

「Aさん、送金できてないよ、早く送金して」「いや、もうBさんに送ってるよ、こっちの残高減ってるし」「いやいや、送られてないよ! こっちの残高増えてないし!」「いや送ったって!」「送られてないって!」

みたいに10万円が虚空へ消えてしまいます。

こういう事態にならないように、「もしBさんの残高を増やす処理が失敗したら、全部の処理をなかったことにする」という仕組みが必要になります。

トランザクションで処理をひとまとまりの命令にまとめることで、こういう「途中で処理が失敗した!」ということになっても、「これらの処理を全部なかったことにする!(rollBack)」という巻き戻しが可能になります。

トランザクションを設定

元々のコードは以下です。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;
use App\Models\StampHistory;
use Illuminate\Support\Facades\DB;

class StampController extends Controller
{   
    //スタンプ獲得
    public function confirmStamp(Request $request) {
        //ログインユーザー取得
        $user = User::findByToken($request);

        //未読スタンプを取得
        $unreadStamps = StampHistory::where('user_id',$user->id)
                                ->where('is_read', false)
                                ->count();

        if ($unreadStamps > 0) {
            $user->userStat->increment('perfect_stamp_count', $unreadStamps);

            StampHistory::where('user_id', $user->id)
                        ->where('is_read', false)
                        ->update(['is_read' => true]);
        }

        return response()->json(['success' => true]);
    }
}

この中の以下の部分をトランザクションにしていきます。

        if ($unreadStamps > 0) {
            $user->userStat->increment('perfect_stamp_count', $unreadStamps);

            StampHistory::where('user_id', $user->id)
                        ->where('is_read', false)
                        ->update(['is_read' => true]);
        }

        return response()->json(['success' => true]);

改修後は以下です。

        if ($unreadStamps > 0) {
            try {
                DB::beginTransaction();

                $user->userStat->increment('perfect_stamp_count', $unreadStamps);

                StampHistory::where('user_id', $user->id)
                            ->where('is_read', false)
                            ->update(['is_read' => true]);

                DB::commit();

            } catch (\Exception $e) {
                DB::rollBack();
                return response()->json(['success' => false], 500);
            }
        }

        return response()->json(['success' => true]);

try-catch

エラーが起きても、安全にエラー処理に移行させるための記述です。

tryの部分に実行したい処理を書き、catchの部分にエラーになった場合の処理を書きます。

今回の場合は「トランザクション処理を開始して、エラー無く処理を終えたらコミットしてね、もしエラーが出たらロールバックしてエラーを返してね」という処理になります。

DB::beginTransaction();/DB::commit();/DB::rollBack();

これらはセットで使います。

  • DB::beginTransaction();……トランザクションを開始
  • DB::commit();……トランザクション内の変更を確定
  • DB::rollBack();……トランザクション処理をなかったことにする

トランザクションを開始したら、必ず完了させる記述を入れます。

また、これらの命令文を使うためには、コントローラーの冒頭の「use」のところに

use Illuminate\Support\Facades\DB;

が必要になりますので、うっかり忘れないように気を付けます。

catch (\Exception $e)

\Exception $eは「どんな種類のエラーでも受け取る」という意味です。

\Exceptionは「あらゆる例外(エラー)の親クラス」なので、$eの中に以下のような情報が入ります。

  • $e->getMessage(); …… エラーメッセージを取得
  • $e->getLine(); …… 何行目で発生したか
  • $e->getFile(); ……どのファイルで発生したか

なんかたぶん頻発すると予想されるエラーは個別にログ出力できるようにした方がいいんでしょうけど、現状は大雑把にすべてのエラーをまとめて処理しています。

まとめ

以上、トランザクションについてでした。

try-catchとDB::beginTransaction();~DB::rollBack();を組み合わせればいいので簡単ですね。

「途中でデータの整合性が取れなくなったら困る」という場合に利用します。

コメント

タイトルとURLをコピーしました