開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. Laravel >
  5. 【Laravel】PostgreSQLのtrigger機能を使って履歴を取る方法
no-image

【Laravel】PostgreSQLのtrigger機能を使って履歴を取る方法

こんにちは。
ニシザワです。


本日は、Laravelに於いて、PostgreSQLのtrigger機能を用いた履歴作成について書いていきます。
本ブログではLaravelのインストール方法は説明しません

環境

Laravel:10.x
PostgreSQL:15.2

履歴作成

決済テーブルのステータス履歴を取りたいと仮定します。
 
    
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('settlements', function (Blueprint $table) {
            $table->uuid('id')->comment('ID');
            $table->string('status', 15)->comment('決済ステータス');
            $table->timestamp('updated_at')->comment('更新日時');
            $table->timestamp('created_at')->comment('作成日時');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('settlements');
    }
};
    

こちらの決済ステータスを更新する際に、履歴を作成するようにします。
最初に全コードを書いてしまいます。
説明はその下に書いていきます。
 
    
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('settlement_status_histories', function (Blueprint $table) {
            $table->uuid('settlement_id')->comment('決済ID');
            $table->string('history_type', 6)->comment('タイプ(create,update,delete');
            $table->string('status', 15)->comment('決済ステータス');
            $table->timestamp('created_at')->comment('作成日時');

            $table->index('settlement_id');
        });

        DB::unprepared('DROP FUNCTION IF EXISTS update_insert_settlement_status_history');
        DB::unprepared('
            CREATE FUNCTION update_insert_settlement_status_history() RETURNS TRIGGER AS
            $update_insert_settlement_status_history$
            BEGIN
                IF (TG_OP = \'UPDATE\') THEN
                    IF (NEW.status != OLD.status) THEN
                        INSERT INTO settlement_status_histories VALUES (NEW.id, \'update\', NEW.status, NEW.updated_at);
                        RETURN NEW;
                    END IF;
                ELSEIF (TG_OP = \'INSERT\') THEN
                    INSERT INTO settlement_status_histories VALUES (NEW.id, \'create\', NEW.status, NEW.created_at);
                    RETURN NEW;
                END IF;
            END;
            $update_insert_settlement_status_history$
                LANGUAGE plpgsql;

            CREATE TRIGGER trigger_settlement_status_update_or_create
                AFTER INSERT OR UPDATE
                ON settlements
                FOR EACH ROW
            EXECUTE FUNCTION update_insert_settlement_status_history();
        ');
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        DB::unprepared('DROP TRIGGER trigger_settlement_status_update_or_create ON settlements');
        DB::unprepared('DROP FUNCTION update_insert_settlement_status_history');
        Schema::dropIfExists('settlement_status_histories');
    }
};
    

説明

    
        DB::unprepared('DROP FUNCTION IF EXISTS update_insert_settlement_status_history');
    
php artisan migrate:fresh を実行した際にfunctionは削除されないため、存在していたら削除する記述を入れています。
    
            BEGIN
                IF (TG_OP = \'UPDATE\') THEN
                    IF (NEW.status != OLD.status) THEN
                        INSERT INTO settlement_status_histories VALUES (NEW.id, \'update\', NEW.status, NEW.updated_at);
                        RETURN NEW;
                    END IF;
                ELSEIF (TG_OP = \'INSERT\') THEN
                    INSERT INTO settlement_status_histories VALUES (NEW.id, \'create\', NEW.status, NEW.created_at);
                    RETURN NEW;
                END IF;
            END;
    
functionの実行内容になっています。
TG_OPで、実行された操作(insert,update,delete)を判定可能です。
NEWで、入力されたデータを取得できます。
こちらはinsertとupdate時しか使えません。
OLDで、更新前のデータを取得できます。
こちらはupdateとdelete時しか使えません。
NEW.status != OLD.statusで比較することで、変更があったかを判定します。
    
            CREATE TRIGGER trigger_settlement_status_update_or_create
                AFTER INSERT OR UPDATE
                ON settlements
                FOR EACH ROW
            EXECUTE FUNCTION update_insert_settlement_status_history();
    
AFTERの後に、実行されるタイミングを指定します。
ここを指定しないとfunction内のIF (TG_OP = \'UPDATE\')等が実行されないので注意してください。
EXECUTE FUNCTIONで、実行するfunctionを指定します。
    
        DB::unprepared('DROP TRIGGER trigger_settlement_status_update_or_create ON settlements');
        DB::unprepared('DROP FUNCTION update_insert_settlement_status_history');
    
downで、functionとtriggerを削除しています。

まとめ

PostgreSQLのtrigger機能を用いることで、履歴を作成することができます。
functionを使えば色々とできるので、ぜひ使ってみてください。

本日は以上となります。
TOPに戻る