PHP
laravel
laravel5.6

なるべく少ない手順で Laravel の認証を拡張して最終ログイン時間を記録する

半分備忘録みたいなものですが、Laravel の標準認証では最終ログイン時間を記録してくれないためイベント/リスナーを利用する形で記録するようにしました。
前提条件として、すでに標準の認証が実装されている状態です。

マイグレーション

php artisan make:migration add_column_last_login_at_users_table

まずはマイグレーションの雛形をつくります。

中身はこのように変更します。

database/migrations/YYYY_MM_DD_HHiimm_add_column_last_login_at_users_table
<?php

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

class AddColumnLastLoginAtUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
          $table->timestamp('last_login_at')->nullable()->after('remember_token')->comment('最終ログイン');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
          $table->dropColumn('last_login_at');
        });
    }
}

up と down の中身を足してください。

after をどの後にするかもお好みで。(MySQL限定)

変更したら実行します。

php artisan migrate

リスナーの追加

イベントが実行されたタイミングで走る、リスナーを作成します。
これまた artisan で出来ちゃうんですね。なんでもあるな、Laravel...

php artisan make:listener LogSuccessfulLogin

app/Listeners/LogSuccessfulLogin.php に雛形が作成されるので、中身を変更します。
掴みたいイベントは認証のドキュメントにも記載がありますが、ログイン成功時のやつですね。

'Illuminate\Auth\Events\Login' => [
    'App\Listeners\LogSuccessfulLogin',
],

というわけで、リスナーのファイルは宣言部分にイベントを書きます。
handle ではイベントが渡されているので、Login イベントを指定します。(引数部分)
あと記録するためには Auth ファサードと時刻記録用の Carbon も宣言してあげます。

app/Listeners/LogSuccessfulLogin.php
<?php

namespace App\Listeners;

use App\Models\Users;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Auth\Events\Login;

use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;

class LogSuccessfulLogin
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  object  $event
     * @return void
     */
    public function handle(Login $event)
    {
        $user = Auth::user();

        if ( ! is_null($user))
        {
            $user->last_login_at = Carbon::now();
            $user->save();
        }
    }
}

イベント発火

最後にイベントのプロバイダーに発火させるための記述を追加します。

app/Providers/EventServiceProvider.php
protected $listen = [
    // ログオン記録イベント
    'Illuminate\Auth\Events\Login' => [
        'App\Listeners\LogSuccessfulLogin',
    ],
];

これで last_login_at にログイン時刻が打刻されるようになりました。
ただしこのやり方はマルチログイン(ガードが複数あるような場合)だと正しく動かない場合があります。

というのもイベントを捕まえる時に、ガードがデフォルトで別のガードを捕まえないからです。

マルチログインでの解決策

言うほど大変でもないのですが、前提条件としてAdminというテーブルが
認証先とした場合はこちらの書き方になります。

App/Listeners/LogSuccessfulLogin;
    public function handle(Login $event)
    {
        $user = (get_class($event->user) === 'App\Admin') ? $event->user : Auth::user();

        $user->last_login_at = Carbon::now();
        $user->save();
    }

とどのつまり、AuthファサードはLisnerの中でガードの自動判別をしてくれない、であれば出てくるモデルのオブジェクト名で無理やり判定してやろうということです。標準ガードであれば App/User あたりが出てきますし、管理系であれば上のようなコードで判定できると。
もっといいやり方があればどなたかご教授ください。