学習の備忘録です。タスク管理アプリを作っています。
今回は、スタンプ獲得機能の中身であるバッチ処理について備忘録を書いていきます。
仕様設計については以下の記事にまとめているので、よろしければこちらもご確認ください。
バッチ処理の実装について、全体の流れはこんな感じです。
- コマンドクラスを作る(バッチの処理内容を書くファイル)
- スケジュールに登録する(いつ実行するか設定)
- サーバーのcronに1行追加する(Laravelのスケジューラーを動かす)
ただ、今回はローカル環境でとりあえず実装させるので、「cronに1行追加する」という手順は踏みません。(いずれ本番環境に移行した時に追加します。)
また、スタンプ獲得のアニメーション演出などの見た目の部分はとりあえず置いといて、今回は中身だけ書いていきます。
トップ画面のスタンプカードアイコン切り替えは実装済なので、スタンプ獲得のアニメーション演出が未実装でも、スタンプが増えたかどうかの確認(テスト)は可能です。
ちなみに、トップ画面のスタンプカードアイコン切り替えについては以下の記事にまとめています。
本記事は個人の学習記録です。内容の正確性を保証するものではありませんので、あらかじめご了承ください。
コマンドクラスを作る
ターミナルで以下のコマンドを打つだけでファイルが自動生成されます。
※プロジェクトまでディレクトリ移動している前提で書いています。
php artisan make:command EvaluateStampCommand
この「EvaluateStampCommand」のところに好きなコマンドクラス名を入れます。
Laravel Sail(Docker)を使っている場合
私のようにDockerでLaravelプロジェクトを作っている人は、php artisan ...ではエラーになる場合があります。
そういう場合は以下のように書きます。
./vendor/bin/sail artisan make:command EvaluateStampCommand
すると、app/Console/Commands/の下に以下のようなファイルが自動生成されます。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class EvaluateStampCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:evaluate-stamp-command';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
//
}
}
コマンドクラスの中身を書いていく
こうして生成されたクラスの中に中身を書いていきます。以下の感じ。
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\Task;
use App\Models\StampHistory;
class EvaluateStampCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'stamp:evaluate'; // コマンド名(後でスケジュール登録に使う)
/**
* The console command description.
*
* @var string
*/
protected $description = '前日のタスク達成率を判定してスタンプを付与する'; // 説明文(なんでもOK)
/**
* Execute the console command.
*/
public function handle()
{
$targetDate = today()->subDay(); // 昨日の日付
$users = User::with(['tasks' => function($query) use ($targetDate) {
$query->whereDate('task_date', $targetDate);
}, 'userStat'])->get();
foreach ($users as $user) {
$tasks = $user->tasks;
if ($tasks->isEmpty()) continue;
//達成率取得
$rate = Task::calAchievementRate($tasks);
//達成率が100%の時の処理
if ($rate === 100) {
$user->userStat->increment('perfect_stamp_count');
StampHistory::firstOrCreate([
'user_id' => $user->id,
'stamp_date' => $targetDate,
], [
'is_read' => false,
]);
}
}
$this->info('スタンプ判定完了: ' . $targetDate);
}
}
$users取得の記述
$users = User::with(['tasks' => function($query) use ($targetDate) {
$query->whereDate('task_date', $targetDate);
}, 'userStat'])->get();
ごちゃごちゃしてて分かりにくいので整理します。
そもそもwithって何? というところですが、これは「データベースへの呼び出し回数を激減させて、処理を早くさせるための機能」です。
withを使うことで、「全員分のユーザーデータを持ってきて! その人たちのタスクとステータスも一緒に、全部まとめて頂戴!」という命令が出せます。
そして、普通にUser::with([‘tasks’ , ‘userStat’])->get();だったら「全ユーザーのデータとタスクとステータスを持ってくる」という命令になるのですが、’tasks’ については「昨日のデータだけ欲しい」ので、以下のように条件付けをしています。
'tasks' => function($query) use ($targetDate) {
$query->whereDate('task_date', $targetDate);
}
なお、function(){}の外側で定義された変数はそのままじゃ使えないので、use ($targetDate)を付けて「{}の中でこの変数($targetDate)を使ってね」とデータを渡しています。
よって、use ($targetDate)の記述は必須になります。
foreachでユーザー一人一人のタスク達成率を確認
取得した$usersは、「アプリに登録している全ユーザーの、昨日のタスク情報」です。
これをforeachで回して、
- タスクは登録されているか
- タスクの達成率は何%か
を見ていきます。
その結果、タスクの達成率が100%ならuser_statsテーブルのperfect_stamp_countの数値を+1します。それが$user->userStat->increment('perfect_stamp_count');です。
increment()は該当カラムの数字を自動で+1してくれるLaravelのコマンドです。
そして、stamp_historiesテーブルに「誰が(user_id)」、「何日に(stamp_date)」スタンプを獲得したのか、「アニメーション演出は済んでいるかどうか(is_read)」を登録します。
is_readについては、次にアプリを開くまでは「アニメーション演出が済んでいない状態」にしたいので、このバッチ処理の時点では「false」で登録します。
StampHistory::firstOrCreate([
'user_id' => $user->id,
'stamp_date' => $targetDate,
], [
'is_read' => false,
]);
ここでポイントなのはfirstOrCreate()関数です。
Laravelにて用意されている関数で、
firstOrCreate([第一引数(検索条件)],[第二引数(追加登録データ)])
みたいに使います。
つまり、今回の例で行くと、
「user_idとstamp_dateが既にデータベースに登録されてないか確認して、もしされていないならis_readも合わせて3つのデータを登録する」
みたいな感じになります。
もしも既にuser_idとstamp_dateがあるなら登録処理は行われません。
firstOrCreate()関数を使うことで、データの重複(スタンプの二重獲得)を防ぐことができます。
スケジュールに登録する
コマンドクラスの中身を書き終えたら、スケジュールの登録を行います。
routes/console.phpの中に、以下の記述を追加します。
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule; //追加
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
Schedule::command('stamp:evaluate')->dailyAt('00:00'); //追加
これで「毎日0時に実行」という設定になります。
ローカル環境でのテスト
本番環境ならサーバーのcronに1行追加することでスケジュールの監視ができ、設定時刻になったら自動でバッチ処理が走ります。
しかしローカル環境だとサーバーに監視させることができないので、テストするなら手動でコマンドを実行させる必要があります。
ターミナルで該当プロジェクトのディレクトリに移動し、以下のコマンドを叩きます。
※プロジェクトまでディレクトリ移動している前提で書いています。
php artisan コマンド名(例:stamp:evaluate)
※Laravel Sail(Docker)の場合は以下
./vendor/bin/sail artisan コマンド名(例:stamp:evaluate)
これにより、作成したコマンドクラスの中身が実行されます。
実際にテストしてみた記録
実際に手動でやってみた結果を書いていきます。
まず、「昨日のタスク達成率を調べて、100%ならスタンプを獲得する」という処理なので、データベースに昨日の分のデータを登録します。


stamp_historiesテーブルの中身は以下。

tasksテーブルのis_completedが全部1(達成率100%)で、stamp_historiesテーブルの中には何も入っていません。
想定通りに動作するなら、コマンド実行するとstamp_historiesテーブルの中にuser_id=1、stamp_date=2026-05-30、is_read=0のデータが入るはず。
ということで、ターミナルでコマンド叩いてバッチ処理を手動実行してみます。

「スタンプ判定完了:2026-05-30 00:00:00」と出たので、処理自体はできている様子。
そして肝心のデータ登録については以下の通りです。

想定通りのデータがちゃんと登録されていますね!
また、user_statsテーブルのperfect_stamp_countも0→1になりました。

ちなみに「タスク達成率が100%じゃない場合はstamp_historiesにデータを登録しない」という分岐が正しく機能しているか確認も行いました。
stamp_historiesの中身を空にして、tasksテーブルの中身を以下のようにして再度コマンドを叩いたところ、想定通りstamp_historiesには何も登録されませんでした。
※user_statsテーブルのperfect_stamp_countも増えてませんでした。

まとめ
以上、スタンプ獲得のバッチ処理についてでした。
本番環境でサーバーにスケジュール監視させる場合は、
- サーバーで
crontab -eを実行する - エディタが開くので、一番下の行に
* * * * * cd /var/www/your-app && php artisan schedule:run >> /dev/null 2>&1
を追加する(※/var/www/your-appはアプリのパスに変えてください。)
みたいな設定が必要みたいです。
役割ごとに整理すると、
- コマンドクラス→実際にどんな処理をしたいかを設定する
- スケジュール→何時に処理を実行するかを設定する
- cron→スケジュールを確認して、「今実行するべきコマンドがないか」の監視設定をする
こんな感じです。
動作を書き! 実行時刻を設定し! その時刻を監視してその時間になったらコマンド実行!
このバッチ処理で登録されたstamp_historiesのデータを基に、スタンプ獲得のアニメーション演出をするフロント側の処理については、また別途記事にまとめようと思います。
アニメーション……アニメーションってどうするんだよ……?




コメント