前回においては、Eloquentbootメソッドを利用してDB変更の情報(監査の情報)を取得することができました。今回は、これをDBに記録する部分を考えてみましょう。

監査のデータを保存するテーブル

監査のデータを保存するテーブルを、audit_logsとしてmigrationファイルを作成します。

$ php artisan make:migration create_audit_logs_table  
Created Migration: 2020_05_20_024714_create_audit_logs_table

作成されたファイルを編集して、テーブルの項目定義を入れます。

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

class CreateAuditLogsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('audit_logs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id')->default(0)->index(); // Userモデルのid
            $table->string('path')->default(''); // 実行したプログラムのパス名
            $table->longText('old_value')->nullable(); // 変更前の値
            $table->longText('new_value')->nullable(); // 変更後の値
            $table->timestamps();
        });
    }

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

old_valuenew_valueには、レコードの項目のすべての値をserializeして入れるために大きいデータタイプとしています。

以下を実行してテーブルを作成します。

$ php artisan migrate
Migrating: 2020_05_20_024714_create_audit_logs_table
Migrated:  2020_05_20_024714_create_audit_logs_table (0.02 seconds)

AuditLogのクラスも作成します。

namespace App;

use Illuminate\Database\Eloquent\Model;

class AuditLog extends Model
{
    protected $guarded = [];
}

そして、Userクラスのプログラムを書き替えます。


namespace App;

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

use Request;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    protected static function boot()
    {
        parent::boot();

        static::created(function ($model) {
            AuditLog::create([
                'user_id'    => $model->id,
                'path'       => Request::path(),
                'old_value'  => null,
                'new_value'  => serialize($model->attributes),
            ]);
        });

        static::updated(function ($model) {
            AuditLog::create([
                'user_id'    => $model->id,
                'path'       => Request::path(),
                'old_value'  => serialize($model->original),
                'new_value'  => serialize($model->attributes),
            ]);
        });

        static::deleted(function ($model) {
            AuditLog::create([
                'user_id'    => $model->id,
                'path'       => Request::path(),
                'old_value'  => serialize($model->original),
                'new_value'  => null,
            ]);
        });
    }
}

tinkerでレコードの作成、Eメールアドレスの変更、そしてレコードの削除を実行します。

Psy Shell v0.10.2 (PHP 7.2.16 — cli) by Justin Hileman
>>> use App\AuditLog;
>>> $user = User::create(['name' => 'name1', 'email' => 'test1@example.com', 'password' => 'test']);
[!] Aliasing 'User' to 'App\User' for this Tinker session.
=> App\User {#3058
     name: "name1",
     email: "test1@example.com",
     updated_at: "2020-05-20 03:28:18",
     created_at: "2020-05-20 03:28:18",
     id: 1,
   }
>>> $user->update(['email' => 'test2@example.com']);
=> true
>>> $user->delete();
=> true

AuditLogのレコードを取り出してみると、すべてのの変更履歴、監査の情報が保存されています。

>>> AuditLog::all();
=> Illuminate\Database\Eloquent\Collection {#3072
     all: [
       App\AuditLog {#3073
         id: 1,
         user_id: 1,
         path: "/",
         old_value: null,
         new_value: "a:6:{s:4:"name";s:5:"name1";s:5:"email";s:17:"test1@example.com";s:8:"password";s:4:"test";s:10:"updated_at";s:19:"2020-05-20 03:28:18";s:10:"created_at";s:19:"2020-05-20 03:28:18";s:2:"id";i:1;}",
         created_at: "2020-05-20 03:28:18",
         updated_at: "2020-05-20 03:28:18",
       },
       App\AuditLog {#3074
         id: 2,
         user_id: 1,
         path: "/",
         old_value: "a:6:{s:4:"name";s:5:"name1";s:5:"email";s:17:"test1@example.com";s:8:"password";s:4:"test";s:10:"updated_at";s:19:"2020-05-20 03:28:18";s:10:"created_at";s:19:"2020-05-20 03:28:18";s:2:"id";i:1;}",
         new_value: "a:6:{s:4:"name";s:5:"name1";s:5:"email";s:17:"test2@example.com";s:8:"password";s:4:"test";s:10:"updated_at";s:19:"2020-05-20 03:28:38";s:10:"created_at";s:19:"2020-05-20 03:28:18";s:2:"id";i:1;}",
         created_at: "2020-05-20 03:28:38",
         updated_at: "2020-05-20 03:28:38",
       },
       App\AuditLog {#3075
         id: 3,
         user_id: 1,
         path: "/",
         old_value: "a:6:{s:4:"name";s:5:"name1";s:5:"email";s:17:"test2@example.com";s:8:"password";s:4:"test";s:10:"updated_at";s:19:"2020-05-20 03:28:38";s:10:"created_at";s:19:"2020-05-20 03:28:18";s:2:"id";i:1;}",
         new_value: null,
         created_at: "2020-05-20 03:28:47",
         updated_at: "2020-05-20 03:28:47",
       },
     ],
   }

By khino