それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

laravel のNotificationをデータベースに入れる

Notificationをデータベースに入れる

Notificationはジョブキュー的に処理されるし、別にメール通知とは限らない。

そもそもイベント・リスナみたいなもの。イベントが発火してリスナが受け取るのと同じように、通知が発生して通知連絡のクラスが起動する。

今回は、Notificationをデータベースにキューイングして構造を見ておこうと思う。

実験環境を準備する。

プロジェクトを作る

composer create-project laravel/laravel database-notification-sample
cd database-notification-sample
sed -i config/app.php -e 's|UTC|Asia/Tokyo|'
sed -i .env -e 's/DB_/#DB_/' -e '/mysql/i DB_CONNECTION=sqlite'
php artisan migrate

通知をやり取りするテーブルを作成する。

php artisan notifications:table
php artisan migrate

クラスを作る。送出側

まずは、通知を送る側クラスを作る。

通知の本体を作る

php artisan make:notification MyNotification

このような感じで作られる。

app/Notifications/MyNotification.php

<?php

namespace App\Notifications;

use App\Models\MyModel;
use Illuminate\Notifications\Notification;

class MyNotification extends Notification {

  public function __construct( protected MyModel $model) { }

  public function via(){
    return ['database'];
  }
  public function toArray(){
    return ['testing' => 'Test'];
  }
}

通知を送る側を作る

通知を送る側は、コマンドとして作り、任意のタイミングで実行できるようにする。

php artisan make:command SendNotification

app/Console/Commands/SendNotification.php

<?php

namespace App\Console\Commands;

use App\Models\MyModel;
use Illuminate\Console\Command;
use App\Notifications\MyNotification;
use Illuminate\Support\Facades\Notification;

class SendNotification extends Command {
  
  protected $signature = 'app:send-notification';
  
  public function handle() {
    $model = MyModel::first();
    Notification::send($model,new MyNotification($model));
  }
}

サンプルのモデルを作る

通知は、モデルに対し行われる。なので、モデルを用意する。

php artisan make:model MyModel -m

app/Models/MyModel.php

use Notifiable; で通知を受け取るフラグを立てる。

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;

class MyModel extends Model {
  use Notifiable;
}

データベースに通知を置く

データベースに通知を置くのが本題なので、通知を設置するテーブルを用意する。

Seederを作る

php artisan make:seeder MyModelSeeder

Seeder を実行する。

php artisan migrate
php artisan db:seed --class MyModelSeeder

database/seeders/MyModelSeeder.php

seeder は次のようにして、いくつかモデルのレコードを用意した。

<?php

namespace Database\Seeders;

use App\Models\MyModel;
use Illuminate\Database\Seeder;

class MyModelSeeder extends Seeder {
  
  public function run():void {
    foreach (range(0, 10) as $idx) {
      ( new MyModel() )->save();
    }
  }
}   

通知を送る。

コマンドからモデルへ通知を送信する。

php artisan app:send-notification

通知を送出後にDBのテーブルを確認する。

id type notifiable_type notifiable_id data read_at created_at updated_at
a3df8a96-f7ae-44ca-bf55-ba94662128f1 App\Notifications\MyNotification App\Models\MyModel 1 {"testing":"Test"} 2024-02-13 14:39:13 2024-02-13 14:39:13

通知を見ればわかるが、どのモデルのID番号へ、通知を送る。そしてReadフラグ(日付)をつけて区別している。これで、データベース通知の仕様が見えてくる

model <- notification 

通知を読み取る。

通知の処理をする起動ポイントを作る。

php artisan make:command ReadNotification

app/Console/Commands/ReadNotification.php

<?php

namespace App\Console\Commands;

use App\Models\MyModel;
use Illuminate\Console\Command;

class ReadNotification extends Command {
  
  protected $signature = 'app:read-notification';
  
  public function handle() {
    /** @var MyModel $model */
    foreach (MyModel::all() as $model) {
      /** @var \Illuminate\Notifications\DatabaseNotification $notification */
      foreach ($model->notifications as $notification) {
        dump($notification->type);
        dump($notification->data);
      }
    }
  }
}

通知の読み取りを実行する

実行結果

# app/Console/Commands/ReadNotification.php:18
"App\Notifications\MyNotification" 
# app/Console/Commands/ReadNotification.php:19
array:1 [
  "testing" => "Test"
] 

覚えること。

DatabaseNotificationは、notificationsテーブルmodelとモデル・リレーションしている。

mophyを使ってる。

Notificationはモデルへ通知するのが基本。

ReadAtをnull / not null を使ってフラグとして管理している。

マニュアルを一読すると、「イベント」のように見えるけど、イベントとはぜんぜん違う仕組みだとわかる。

どこにもキューが出てこないし、queue:workも出てこない。

通知(notification)を消すには、通常のデータベースと同じ処理になる。

DatabaseNotificationは次のように定義されている

class DatabaseNotification extends Model

xxXXXedと書かれるので、イベントのように見えるがイベントではないし、Notificationのチャンネルがあるわけでもない。よってこれは、ただの1対多のモデル・リレーションである。

モデルに「メッセージ(Data)」を紐づけて送っている。モデルにイベントを通知すると考えると、データベース通知は、やや過剰な仕様であり、Dataにデータを紐付ける程度のものである。

サンプルやドキュメントで「InvoicePaid」「XXXCreated」みたいな通知クラスをみかける。まるでイベント名であるが、イベント・リスナの枠組みではないとわかる。メール・Slackと、データベース通知は、2つはまったく違う。Slack・Mainではキューも使っている。一方でデータベース通知はテーブルに書くだけであり、キューの枠組みではないとわかる。これは初見殺しで、ややこしい。

この違いは、use ShouldQueue;use Queueable; の有無である。データベース通知をキューにいれるかは、コーディングで明確に書く必要がある。ただ、データベース通知ではキューをは必要がないので特に書かれない。

2024-05-30

下書きから更新