【Laravel】ログイン日時の統計をとって棒グラフにしてみる

こんにちは❗フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、ここ数回Laravelの話題をお届けしましたので、この流れでもう1つLaravelの記事をお届けしたいと思います。

今回はおそらく訪問ユーザー向けと言うよりは、運営者側がほしがるような内容かもしれませんが、その内容はというと、

サイトの集計データをチャートで表示する

というものです。

サイトの集計データといえば、Google Analyticsが有名ですがあれはブラウザでのアクセスに限られたデータです。

そのため、例えば「注文を受けた回数の集計データ」などより複雑なデータ集計をしようと思うと、自前で実装するしかありません。

そこで!

今回は、ログインした日時を集計して「ログインするのが多い時間帯はいつ?」が分かるコンテンツをLaravelで作ってみたいと思います。

ぜひ皆さんのお役に立てると嬉しいです😊✨
(最後に今回開発したソースコード一式をダウンロードすることができます)

開発環境: Laravel 6.x

やりたいこと

今回は「各時間ごとのログイン回数を月単位で」集計することにします。
つまり、以下のようなことができるようになれば成功です。

  • 「先月は14時台が一番ログインが多かったのか😊」
  • 「今月は19時台のログインが少ないぞ😫」
  • 「意外と深夜1時でもログイン回数は少なくないなー😳」

では、実際にやっていきましょう!

前提として

今回もログイン機能がLaravelにインストールされていることが前提となっています。もしインストールがまだの方は以下を参考してください。

モデルとマイグレーションをつくる

まずは、以下のコマンドでログイン集計に必要なモデル&マイグレーションを作成します。

php artisan make:model Login -m

コマンドを実行したらdatabase/migrations/****_**_**_******_create_logins_table.phpというファイルが作成されますので、それらを開いて中身を次のようにします。

public function up()
{
    Schema::create('logins', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->unsignedBigInteger('user_id')->comment('ユーザーID');
        $table->unsignedInteger('year')->comment('ログイン・年');
        $table->unsignedInteger('month')->comment('ログイン・月');
        $table->unsignedInteger('day')->comment('ログイン・日');
        $table->unsignedInteger('hour')->comment('ログイン・時');
        $table->unsignedInteger('minute')->comment('ログイン・分');
        $table->unsignedInteger('second')->comment('ログイン・秒');

        $table->foreign('user_id')->references('id')->on('users');
        $table->index(['year', 'month']);
    });
}

なお、集計が高速になるように通常のcreated_atupdated_atは使わず各「年・月・日・時・分・秒」をデータで保持するようにしています。また、同じ理由からインデックスをyear + monthでつけています。

そのため、Loginモデルがタイムスタンプを使わないようにapp/Login.phpに以下のコードを追加しておいてください。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Login extends Model
{
    public $timestamps = false;  //created_at,updated_at は使わない
}

では、マイグレーションを実行してテーブルを作成しましょう。

php artisan migrate

すると、テーブルはこのようになります。

ログイン日時を保存するようにする

続いて、ユーザーがログインをしたら自動的に先ほど作成したloginsテーブルにデータが書き込まれるようにしておきましょう。

app/Http/Controllers/Auth/LoginController.phpの中に以下のコードを追加してください。

// 省略

class LoginController extends Controller
{
    // 省略

    protected function authenticated(Request $request, $user)
    {
        $dt = now();

        $login = new \App\Login();
        $login->user_id = $user->id;
        $login->year = $dt->year;
        $login->month = $dt->month;
        $login->day = $dt->day;
        $login->hour = $dt->hour;
        $login->minute = $dt->minute;
        $login->second = $dt->second;
        $login->save();
    }
}

これで、ログインをするたびに以下のようなデータがloginsテーブルに追加されることになります。

Seederでテストデータを用意する

このまま集計部分をつくってもいいのですが、何回もログインをしてデータを追加するのは時間がかかりますので、Seederでテストデータをつくることにします。

以下のコマンドを実行してください。

php artisan make:seed LoginsTableSeeder

すると、database/seeds/LoginsTableSeeder.phpというファイルが作成されるのでrun()内を以下のように変更してください。

<?php

use Illuminate\Database\Seeder;

class LoginsTableSeeder extends Seeder
{
    public function run()
    {
        $today = today();

        for($i = 0 ; $i < 500 ; $i++) {

            $random_days = rand(1, 100);
            $random_hours = rand(1, 23);
            $dt = $today->copy()
                ->subDays(rand(1, 100))  // -1〜100日前
                ->addHours(rand(1, 23))  // +1〜23時間
                ->addMinutes(rand(1, 59))  // +1〜23分
                ->addSeconds(rand(1, 59));  // +1〜23秒

            $login = new \App\Login();
            $login->user_id = 1;    // テストなのでユーザーIDは "1" で固定
            $login->year = $dt->year;
            $login->month = $dt->month;
            $login->day = $dt->day;
            $login->hour = $dt->hour;
            $login->minute = $dt->minute;
            $login->second = $dt->second;
            $login->save();

        }
    }
}

ファイルを変更したら、database/seeds/DatabaseSeeder.phpに以下のように登録します。

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
         // 省略

         $this->call(LoginsTableSeeder::class);
    }
}

では、artisanコマンドを使ってSeederを実行してみましょう。

php artisan db:seed

うまくいくとloginsテーブルはこのようになります。

では、このデータを使って実際に集計をしていきましょう。

集計した棒グラフをつくる

では、ここからが実際に集計データを表示していく作業になります。
クリック1つで「年・月」を変更できるようにしたいので、データはAjaxを通して取得することにします。

ルートをつくる

routes/web.phpに以下を追加してください。

Route::get('login_count', 'LoginCountController@index');
Route::get('ajax/login_count', 'LoginCountController@ajax_index');

上がブラウザで、下がAjaxでアクセスするページになります。

コントローラーをつくる

続いてコントローラーです。
以下のコマンドを実行してください。

php artisan make:controller LoginCountController

app/Http/Controllers/LoginCountController.phpというファイルが作成されるので、中身を次のようにします。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class LoginCountController extends Controller
{
    public function index() {

        return view('login_count');

    }

    public function ajax_index(Request $request) {

        $year = date('Y');
        $month = date('m');

        if($request->filled('year', 'month')) {

            $year = $request->year;
            $month = $request->month;

        }

        $logins = \App\Login::select('hour', \DB::raw('COUNT(id) AS login_count'))
            ->where('year', $year)
            ->where('month', $month)
            ->groupBy('hour')
            ->pluck('login_count', 'hour');

        $counts = [];

        for($i = 0 ; $i < 24 ; $i++) {

            $counts[$i] = $logins->get($i, 0);  // 存在しない時間は "0" 回

        }

        return $counts;

    }
}

この中では、「年・月」で絞り込んだデータをグループ化して時間(hour)ごとにデータ数をカウントしています。

ただし、例えば12時台にデータが存在していない場合は、集計データが存在しないことになってしまいますので、存在しない時間は0にするコードを追加してデータを修正しています。

ビューをつくる

では、実際ブラウザで目にする棒グラフの部分をつくっていきましょう。resources/views/login_count.blade.phpというファイルを作成して以下のようにしてください。

<html>
<body>
    <div id="app">
        <div>
            集計:
            <span v-text="year"></span>年
            <span v-text="month"></span>月
            <button type="button" @click="moveMonth(-1)">前へ</button>
            <button type="button" @click="moveMonth(1)">次へ</button>
        </div>
        <canvas id="chart"></canvas>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.2/dist/Chart.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                year: '',
                month: '',
                chart: null
            },
            methods: {
                drawChart() {

                    const url = '/ajax/login_count?year='+ this.year +'&month='+ this.month;

                    axios.get(url)
                        .then(response => {

                            const data = response.data;
                            const ctx = document.getElementById('chart').getContext('2d');
                            let labels = [];

                            for(let i = 0 ; i < 24 ; i++) {

                                labels.push(i +'時');

                            }

                            if(this.chart) {

                                this.chart.destroy();

                            }

                            this.chart = new Chart(ctx, {
                                type: 'bar',
                                data: {
                                    labels: labels,
                                    datasets: [{
                                        label: 'ログイン回数',
                                        data: data
                                    }]
                                },
                                options: {
                                    scales: {
                                        yAxes: [{
                                            ticks: {
                                                stepSize: 1
                                            }
                                        }]
                                    }
                                }
                            });

                        });

                },
                moveMonth(number) {

                    this.month += number;

                    if(this.month === 0) {

                        this.year--;
                        this.month = 12;

                    } else if(this.month === 13) {

                        this.year++;
                        this.month = 1;

                    }

                    this.drawChart();

                }
            },
            mounted() {

                const date = new Date();
                this.year = date.getFullYear();
                this.month = date.getMonth() + 1;
                this.drawChart();

            }
        });

    </script>
</body>
</html>

少しコードが長いですが、重要なのはdrawChart()の部分だけです。

この中では、まず先ほど作成した集計プログラムにAjaxでアクセスしてデータをとってきます。

そして、そのデータをChart.jsにセットをしています。

なお、以下の部分でChart.jsのインスタンスをクリアしていることに注目してください。こうしないと「年・月」を変更した場合に前のデータが残ってしまうことになるからです。(描画は上手く行きますが、ポップアップがおかしくなります)

if(this.chart) {

    this.chart.destroy();

}

お疲れ様でした😊✨

テストしてみる

では、実際にテストしてみましょう!

まずは今月の統計チャートです。

そして、先月。

はい!
うまくいきました😊✨

ダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

※ ただし、マイグレーションなどはご自身で実行していただく必要があります。

【Laravel】ログイン日時の統計をとって棒グラフにしてみる

おわりに

ということで、今回はログインした日時を集計して棒グラフをつくってみました。

ちなみに、今回使ったChart.jsは棒グラフだけでなく、円グラフやレイダーチャートなど様々なチャート作成ができますので、もし興味がある方はこちらのサンプルページをご覧になってください。

なお、今回のケースではデータ量が多くなってくるとレスポンスが極端に遅くなることが想定されます。そのため、もしそうならないように対処したい場合は、例えばlogin_countsテーブルを作ってcronなどで定期的にデータを集計するようにし、Ajaxの取得はこの集計テーブルから取得するようにするといいでしょう。

ぜひ皆さんも集計データのコンテンツを作ってみてくださいね。

ではでは〜!

開発のご依頼お待ちしております 😊✨
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly  

開発効率を上げるための機材・まとめ