PHP
laravel
Carbon
authentication
middleware

Laravelアプリにアクセスしてきたユーザーのアクセス日時を記録する(全ページ共通の前処理)

なにがしたい?

表題の通り、アクセスしてきたユーザーごとに「最終アクセス日時」というのを記録したい、という案件です。そう、「最終ログイン日時」とは違います。ログインした状態(クッキーにセッションが保存されている期間での再訪も含む)でトップページにアクセスしただけで記録したいんです。

できあがったものは、特にログイン日時の記録に限らず「すべてのページで共通の前処理」をすることを想定しています。

サンプルコードは Laravel 5.2 です。最新のものはちょっと異なるかもしれません。

結論

新しいミドルウェアの登場

専用のミドルウェアを追加しちゃいましょう。場所はAuthentificateというミドルウェアがあるところでOK。内容もほとんどそれと同じです。

/app/Http/Middleware/LoginCounter.php
<?php

namespace App\Http\Middleware;

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

class LoginCounter
{
    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->guest()) {
          // ログインしていないユーザーがアクセスしてきたときの処理

        } else {
          // ログインしているユーザーがアクセスしてきたときの処理
          $user     = Auth::user();
          $now      = Carbon::now();
          $prevtime = Carbon::parse( $user->last_logged_in_at );

          // 前回アクセスしてきたときから5分以上経過していたらタイムスタンプを更新
          if( $prevtime->diffInMinutes( $now, false ) >= 5 ){
            $user->last_logged_in_at = $now;
            $user->login_count++;
            $user->save();
          }
        }
        return $next($request);
    }
}

新しく作ったミドルウェアをねじ込みます。

/Http/Karnel.php
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /* ... */

    /**
     * アプリケーションのルートミドルウェアグループ
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \App\Http\Middleware\LoginCounter::class, // <-- 追加
        ],

    /* ... */

パフォーマンス 30ms程度

全ページで毎回やるので、毎回記録していたらリソースがもったいないので5分間隔にしています。その記録時以外で一番重いのは Auth:user() で、自分のローカル環境で30msくらい。それがない場合(未ログインユーザーの場合)は全体で5msくらいと、許容範囲かと思います。Carbonが重いのかも。

解説

代案と選ばなかった理由

他には、コントローラーに書く、ComposerServiceProviderに書く、AuthやUserモデルに書くといった方法も検討しました。

コントローラ

新しく基底クラスを作るのが大変そうだったのと、そもそもその目的のためにミドルウェアがあるんじゃないかなぁと。

ComposerServiceProvider

「全VIEWに共通の変数を用意する」という目的っぽかったので今回は避けました。

AuthやUserに書く

それなりに筋が通っている気はしますが、ちょっと書くのが大変になりそうな予感。モデルはどちらかというとビジネスロジック=内部処理を書くべきところで、今回はどちらかというと「ページ表示時のユーザー操作に対するリアクション」に近いのでコントローラ側に書くのがスマートじゃないかと思いました。

感想

実は結構どうしたものかと悩んでいたのですが、しばらく塩漬けにしていたら、すっと答えが降りてきました。もっといいやり方があったらコメントいただけると幸いです。

こうしてしまうと、逆に、「最終ログイン日時」を記録しようとするほうが、どこでやるのか探すのがめんどくさいような気がしてきました。