inertia.js+react通知(2) - queueの完了後に通知
こんな記事もあったな、、、途中で投げだしてるやんか
laravel通知の準備
artisanコマンドで作る
php artisan notifications:table
すると
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('notifications', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('type');
$table->morphs('notifiable');
$table->text('data');
$table->timestamp('read_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('notifications');
}
};
こんなmigrationができるけど、あんま気にしなくていいからとりあえず何らかの形でmigrateしてこれをDBにとりこんでおくこと。
sessionのインクリメンタルをやめてqueueに切り替える準備をする
これはDashboardにボタンを付けていて、今どうなってたかというと
resources/js/Pages/Dashboard.jsx
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { useLaravelReactI18n } from 'laravel-react-i18n';
import { Head, useForm } from '@inertiajs/react';
import PrimaryButton from '@/Components/PrimaryButton';
export default function Dashboard({ auth }) {
const { post, processing} = useForm();
const submit = (e) => {
e.preventDefault();
post(route('dashboard'));
};
const { t, currentLocale } = useLaravelReactI18n();
return (
<AuthenticatedLayout
user={auth.user}
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">{t("Dashboard")}</h2>}
>
<Head title={t("Dashboard")} />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div className="p-6 text-gray-900">{t("You're logged in!")}</div>
<form onSubmit={submit}>
<PrimaryButton className="ml-4" disabled={processing}>
Click
</PrimaryButton>
</form>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
となっていた。ここでdashboardルートにpostしてるんだけど
routes/web.php
use Illuminate\Http\Request;
Route::post('/dashboard', function (Request $request) {
$notificationsCount = $request->session()->get('notificationsCount', 0);
$request->session()->put('notificationsCount', $notificationsCount + 1);
return redirect()->back();
})->middleware(['auth', 'verified'])->name('dashboard');
sessionのincrementではあまりにも意味がないのでこれをやめて、queueに変更してみよう。
jobを作る
ここではAnalyzeFileJobという名前で作ってみよう
artisan make:job AnalyzeFileJob
こんなのが出来る app/Jobs/AnalyzeFileJob.php
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class AnalyzeFileJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct()
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
//
}
}
ここでダミーのIDを__constructから格納させる
protected $fileId;
protected $userId;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($fileId, $userId)
{
$this->fileId = $fileId;
$this->userId = $userId;
}
handle()には適当にlogを送る。sleepで時間かかってるテイを出しとく
public function handle(): void
{
sleep(10); // なんとなく10秒かかるような処理をシミュレート
\Log::info("Called: fileId {$this->fileId} / User ID: {$this->userId}");
}
とりあえずqueueに送る
では、やってみよう。これはroutes/web.php
Route::post('/dashboard', function (Request $request) {
# ここ
})->middleware(['auth', 'verified'])->name('dashboard');
を改良するのだった。
use Illuminate\Http\Request;
use App\Jobs\AnalyzeFileJob;
Route::post('/dashboard', function (Request $request) {
$fileId = 1; // dummy
$userId = $request->user()->id;
AnalyzeFileJob::dispatch($fileId, $userId);
return redirect()->back();
})->middleware(['auth', 'verified'])->name('dashboard');
このようにfileIdに1をダミーで詰め、送信している。実際にこのような処理を行う場合は対象となるfileIdが実際に入るんだろう。
で、このボタンは何度か押してもいいが、sleep(10)とか関係なくすぐ処理されるはずだ。これはまだ処理されておらず、実際の処理はqueue workerを起動するからである。要するに処理の重いやつを裏で処理させることでfrontからはすぐ断ち切ってしまうということである。
実際にやってみよう。
% ./vendor/bin/sail artisan queue:work
INFO Processing jobs from the [default] queue.
2024-05-08 15:12:34 App\Jobs\AnalyzeFileJob .............................. RUNNING
2024-05-08 15:12:44 App\Jobs\AnalyzeFileJob ............................. 10秒 DONE
2024-05-08 15:12:44 App\Jobs\AnalyzeFileJob .............................. RUNNING
2024-05-08 15:12:54 App\Jobs\AnalyzeFileJob ............................. 10秒 DONE
2024-05-08 15:12:54 App\Jobs\AnalyzeFileJob .............................. RUNNING
2024-05-08 15:13:04 App\Jobs\AnalyzeFileJob ............................. 10秒 DONE
2024-05-08 15:13:04 App\Jobs\AnalyzeFileJob .............................. RUNNING
2024-05-08 15:13:14 App\Jobs\AnalyzeFileJob ............................. 10秒 DONE
このように10秒かかって実行しきったのがわかる。logをみると
[2024-05-08 15:12:44] local.INFO: Called: fileId 1 / User ID: 1
[2024-05-08 15:12:54] local.INFO: Called: fileId 1 / User ID: 1
[2024-05-08 15:13:04] local.INFO: Called: fileId 1 / User ID: 1
[2024-05-08 15:13:14] local.INFO: Called: fileId 1 / User ID: 1
など出ており、うまくいっているようだ。
queueが成功したら通知を送る
ファイルの分析成功の通知なのでそれっぽい名前を付けておく
artisan make:notification FileAnalyzedNotification
そうすると、定型文のようなものが出力されてくる
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class FileAnalyzedNotification extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*/
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
//
];
}
}
ここで通知ドライバーをvia()に書くのだが、mailまたはdatabaseあるいはその両方みたいな設定ができるんだけど、今回はdatabaseしか取り扱わない。
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['database'];
}
/**
* Get the mail representation of the notification.
*/
/*
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}
*/
toDatabase()を書く。toArray()との違いはここでは議論しない
public function toDatabase(object $notifiable): array
{
return [
//
];
}
ここでのキーは何でもいいんだけど、複数の通知classから集約してトップで通知する場合に関しては共通キーが必要であり、ここではtitle、messageとする。そして当該のジャンプ先urlも指定する、ただ、ここではダミーとする。
public function toDatabase(object $notifiable): array
{
return [
'title' => 'ファイル分析完了',
'message' => '',
'url' => url('/'), // ダミーのジャンプ先
];
}
ここでmessageに関してはとりあえず空とし、jobに戻って再度考えてみよう。
app/Jobs/AnalyzeFileJob.php に戻る
結局
public function handle(): void
{
sleep(10);
\Log::info("Called: fileId {$this->fileId} / User ID: {$this->userId}");
}
ここんところのLogに書いてるものをそのまんまNotificationのconstructorに渡せばよいと思う。とりあえずはね。
use App\Notifications\FileAnalyzedNotification;
class AnalyzeFileJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $fileId;
protected $userId;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($fileId, $userId)
{
$this->fileId = $fileId;
$this->userId = $userId;
}
/**
* Execute the job.
*/
public function handle(): void
{
sleep(10);
$message = "Called: fileId {$this->fileId} / User ID: {$this->userId}";
$user = \App\Models\User::findOrFail($this->userId);
if ($user) {
$user->notify(new FileAnalyzedNotification($message));
}
}
}
ここでの注目点は
$user->notify(new FileAnalyzedNotification($message));
であり、このようにするとユーザーごとにnotificationが送られるようになっている。その実態の詳細が知りたければUserモデルとか見てみてね。Userクラスに関しては冒頭でuseしても、もちろんいい
app/Notifications/FileAnalyzedNotification.php に戻る
そしたら今constructorにmessageが渡ってきたので
class FileAnalyzedNotification extends Notification implements ShouldQueue
{
use Queueable;
protected $message;
public function __construct($message)
{
$this->message = $message;
}
public function via($notifiable)
{
return ['database']; // 必要に応じて 'mail' も追加
}
public function toDatabase($notifiable)
{
return [
'title' => 'ファイル処理完了',
'message' => $this->message,
'url' => url('/')
];
}
}
を参考に改良すること。chatgptが吐いたのやつだ
実行してみよう!
さて、ここまでうまいこと組めれたら、Dashboardのボタンを3つくらい押せばまたまたqueueに貯まるので、またworkerを起動する
2024-05-08 15:48:57 App\Jobs\AnalyzeFileJob .............................. RUNNING
2024-05-08 15:49:07 App\Jobs\AnalyzeFileJob ............................. 10秒 DONE
2024-05-08 15:49:16 App\Jobs\AnalyzeFileJob .............................. RUNNING
2024-05-08 15:49:26 App\Jobs\AnalyzeFileJob ............................. 10秒 DONE
2024-05-08 15:49:26 App\Jobs\AnalyzeFileJob .............................. RUNNING
2024-05-08 15:49:36 App\Jobs\AnalyzeFileJob ............................. 10秒 DONE
などと正常に処理されたならばNotificationが届いているはずだ。失敗した場合はstorage/logs/laravel.logとかを見ること。
通知を確認する
tinkerを使ったらよい
> $u = User::find(1)
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
= App\Models\User {#6407
id: 1,
name: "Admin User",
email: "admin@example.com",
email_verified_at: "2024-05-08 13:55:43",
last_login_at: "2024-05-08 13:59:34",
#password: "$2y$12$WBm8qcgFnFwfYQ9G4AwZyOAMJpXAqJJGZRtql949kQZj58cWESNYO",
#remember_token: "fx3j0gPqdX",
created_at: "2024-05-08 13:55:43",
updated_at: "2024-05-08 13:59:34",
}
> $n = $u->notifications;
= Illuminate\Notifications\DatabaseNotificationCollection {#6374
all: [
Illuminate\Notifications\DatabaseNotification {#6390
id: "18877b90-3f3f-4004-b81d-8345886b60d9",
type: "App\Notifications\FileAnalyzedNotification",
notifiable_type: "App\Models\User",
notifiable_id: 1,
data: "{"title":"\u30d5\u30a1\u30a4\u30eb\u5206\u6790\u5b8c\u4e86","message":"Called: fileId 1 \/ User ID: 1","url":"http:\/\/localhost"}",
read_at: null,
created_at: "2024-05-08 15:49:36",
updated_at: "2024-05-08 15:49:36",
},
Illuminate\Notifications\DatabaseNotification {#6391
id: "79493bc4-cb11-4571-91c5-5a6818d42a2e",
type: "App\Notifications\FileAnalyzedNotification",
notifiable_type: "App\Models\User",
notifiable_id: 1,
data: "{"title":"\u30d5\u30a1\u30a4\u30eb\u5206\u6790\u5b8c\u4e86","message":"Called: fileId 1 \/ User ID: 1","url":"http:\/\/localhost"}",
read_at: null,
created_at: "2024-05-08 15:49:26",
updated_at: "2024-05-08 15:49:26",
},
Illuminate\Notifications\DatabaseNotification {#6389
id: "22639400-ff19-4f63-b541-46d348797aac",
type: "App\Notifications\FileAnalyzedNotification",
notifiable_type: "App\Models\User",
notifiable_id: 1,
data: "{"title":"\u30d5\u30a1\u30a4\u30eb\u5206\u6790\u5b8c\u4e86","message":"Called: fileId 1 \/ User ID: 1","url":"http:\/\/localhost"}",
read_at: null,
created_at: "2024-05-08 15:49:07",
updated_at: "2024-05-08 15:49:07",
},
],
}
とまあこのように無事にjobから3つ、通知が送信されているものが受信できているはずだ。
次回は
この通知に関してベルのUIを完了させてみよう。
この記事が気に入ったらサポートをしてみませんか?