データベースに保存されているデータはいつも現時点での値であり、過去の値は保存されていません。しかし、レコードのデータがどう変更したかという情報は重要です。どう変更したかだけではなく、誰がいつ、アプリのどの機能を使用して、さらにはどういう理由で、という情報も必要になってきます。例えば、カスタマサポートにおいて、会員の住所が変更されて注文した商品が届かなかったとき、それらの情報があれば、いついつにお客様が住所を変更しましたね、とか即答できます。さて、これらの変更イベント時の変更情報(監査あるいはAudit情報)、Laravelではどのように効率的にDBに保存することが可能でしょうか?

Eloquentのイベントメソッド

LaravelのEloquentを利用すると、DBのイベント、つまりレコードの作成、変更、削除の3つのイベントに連結したメソッドが提供されています。そして、それらのイベントが発生する前あるいは後においてユーザー定義のコードをコールバックさせることが可能です。これらを利用したら、希望するAuditの情報をキャプチャすることが簡単にできます。

さてLaravelのデフォルトのプロジェクトを使って見てみましょう。
まず、UserのクラスにDBのレコード変更後のイベントのコールバックを作成します。以下のようにbootメソッドにおいて見慣れないクラスメソッドupdatedをコールすることで可能となります。

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
...
    protected static function boot()
    {
        parent::boot();

        static::updated(function () {
            echo "updated\n";
        });
    }
}

ちなみに、updatedは更新後にコールされるものですが、新規作成後は、created、削除後はdeletedというメソッドになります。

tinkerを使用して実行してみます。

>>> $user = User::find(1);
=> App\User {#3048
     id: 1,
     name: "近藤 篤司",
     email: "manabu.takahashi@example.net",
     email_verified_at: "2020-02-13 04:27:15",
     created_at: "2020-02-13 04:27:15",
     updated_at: "2020-05-17 18:31:32",
   }
>>> $user->update(['email' => 'test@example.com']);
updated
=> true

コールバックで定義したupdatedが表示されましたね!

変更前後の情報の取得

DBのレコード変更イベントでユーザー定義の関数をコールバックを確認できたら、次は変更前後でのレコードの情報をどうやって取得するかです。以下見てください。コールバックにそれらの情報を表示するコードを追加しました。

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
...
    protected static function boot()
    {
        parent::boot();

        static::updated(function ($model) {
            echo "updated\n";
            print_r($model->original);
            print_r($model->attributes);
        });
    }
}

tinkerで実行してみます。

>>> $user = User::find(1);
[!] Aliasing 'User' to 'App\User' for this Tinker session.
=> App\User {#3067
     id: 1,
     name: "近藤 篤司",
     email: "test@example.com",
     email_verified_at: "2020-02-13 04:27:15",
     created_at: "2020-02-13 04:27:15",
     updated_at: "2020-05-17 18:59:31",
   }
>>> $user->update(['email' => 'test2@example.com']);
updated
Array
(
    [id] => 1
    [name] => 近藤 篤司
    [email] => test@example.com
    [email_verified_at] => 2020-02-13 04:27:15
    [password] => $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi
    [remember_token] => cDvZkS5nOA
    [created_at] => 2020-02-13 04:27:15
    [updated_at] => 2020-05-17 18:59:31
)
Array
(
    [id] => 1
    [name] => 近藤 篤司
    [email] => test2@example.com
    [email_verified_at] => 2020-02-13 04:27:15
    [password] => $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi
    [remember_token] => cDvZkS5nOA
    [created_at] => 2020-02-13 04:27:15
    [updated_at] => 2020-05-17 19:03:27
)
=> true

前後の値、キャプチャできましたね。これで変更履歴を作成するには十分な準備ができました。これらを利用すれば、変更前後の値あるいは差分を保存できますね。

By khino