個人への通知
運営からのお知らせ(全員への共通のメッセージ)とは別に、個人に届く通知の仕組みです。アプリ内の通知(ヘッダーのベル)にいつも残し、メール・プッシュ通知などは受け取る人の設定に従って送ります。
設計
宛先はいつも人(users)、届いた先はコンテキスト
通知は notifications.user_id で人(users)に外部キーで結びます。受信者は user_id で決まり、context はその人のどこに届いたか(どのコンテキストの画面に出すか)を表します。組織に届いたものは tenant_id も持ちます。コンテキストは、Lase が authorize:tenant・authorize:admin・authorize:partner で分けているものと同じです。
コンテキスト(context) | 受け取るモデル | 見える画面 |
|---|---|---|
TENANT | TenantUser | その組織の画面 |
ADMIN | AdminUser | 管理画面 |
PARTNER | PartnerUser | パートナー画面 |
ACCOUNT | User | どの画面でも(その人のアカウントそのものへの通知。メールアドレスの変更など) |
| アプリ独自の値(大文字) | アプリのモデル | アプリが決める |
ポリモーフィック(notifiable_type / notifiable_id)にはしません。どのコンテキストの受信者も最後は人(users)にたどり着くため、人への外部キーとコンテキストの文字列で足り、外部キー制約で消し忘れを防げるからです。アプリ独自のコンテキストは、NotificationRecipient を実装したモデルの notificationContext() で好きな値を返せば使えます。
種別は Enum、DB は文字列
アプリは通知の種別を string の Enum で定義し、NotificationTypeDefinition を実装します。DB(notifications.type・notification_preferences.type)には値の文字列を入れるため、種別を足しても DB は変えなくてよく、Lase とアプリの種別を同じ列に入れられます。値を変えると、届いた通知と設定が引けなくなるので変えないでください。
enum NotificationType: string implements NotificationTypeDefinition
{
case TASK_ASSIGNED = 'TASK_ASSIGNED';
public function label(): string { return 'タスクの担当になったとき'; } // 通知の設定に出す名前
public function group(): string { return 'タスク'; } // 通知の設定の区切り
public function context(): string { return NotificationContext::TENANT->value; }
public function channels(): array { return ['mail', 'push']; } // アプリ内のほかに選べる受け取り方
public function defaultChannels(): array { return ['mail', 'push']; } // 設定していない人に使う受け取り方
}受け取り方の設定
アプリ内の通知にはいつも届けます。ほかの受け取り方(メール・プッシュ通知など)は、人ごと・種別ごとに notification_preferences に持ちます。変えたものだけを持ち、無いものは種別の defaultChannels() に従います。設定は人(users)ごとで、組織が違っても同じ設定を使います。
アプリ内の通知も止められる種別
既定では、アプリ内の通知は止められません。種別に MutableInAppNotificationType を足して実装し、canMuteInApp() が true の種別は、アプリ内の通知も止められます。通知の設定に「アプリ内」(受け取り方のキー in_app)の列が出て、止めた人にはアプリ内の通知を残しません。メールなどほかの受け取り方は、それぞれの設定に従います。
canMuteInApp() が false の種別(配信のエラーなど、見逃すと困るもの)は、「アプリ内」の列に止められないスイッチとして出します。
組織によって設定に出さない種別
種別に ListedNotificationType を足して実装すると、isListedInSettings(?Tenant $tenant) が false の種別を通知の設定に出しません(受け取り方も変えられません)。契約に機能が無い組織で、その機能の通知を出さないときに使います。組織の画面では今の組織を、管理画面・パートナー画面では null を渡します。届けることは止めません。
enum NotificationType: string implements ListedNotificationType, MutableInAppNotificationType
{
case ERROR = 'ERROR';
case SUPPORT_REQUIRED = 'SUPPORT_REQUIRED';
// label()・group()・context()・channels()・defaultChannels() は上と同じ
public function canMuteInApp(): bool
{
return $this !== self::ERROR; // エラーは止めさせない
}
public function isListedInSettings(?Tenant $tenant): bool
{
return $this !== self::SUPPORT_REQUIRED || (bool) $tenant?->hasFeature('LINE_ENABLED');
}
}文面は送ったときに残す
一覧に出す文面(title・body)と開くところ(url)は、送ったときにアプリが決めたものを残します。あとで種別の定義が変わっても、届いた通知はそのまま読めます。
使い方
1. 種別を登録する
// config/lase.php
'notifications' => [
'types' => [App\Enums\NotificationType::class],
// アプリ内のほかの受け取り方(メールは Lase が持つ)
'channels' => [
'push' => ['label' => 'プッシュ通知', 'channel' => NotificationChannels\WebPush\WebPushChannel::class],
],
],| 設定 | 説明 |
|---|---|
types | 種別の Enum。並べた順に探し、通知の設定にもこの順に並ぶ。Lase の種別(下記)はいつも最後に足すので、書かなくてよい |
channels | 受け取り方のキー => 名前と Laravel の通知チャンネル。既定で mail を持つ |
retention_days | この日数より古い通知は model:prune で消す(既定 90。LASE_NOTIFICATION_RETENTION_DAYS) |
2. 通知を書く
LaseNotification を継承し、type() と toInApp() を書きます。メールなどを送るなら、toMail() などをこれまでどおり書きます。via() は Lase が受け取る人の設定から決めます。
class TaskAssigned extends LaseNotification
{
public function __construct(private Task $task) {}
public function type(): NotificationType
{
return NotificationType::TASK_ASSIGNED;
}
public function toInApp(NotificationRecipient $notifiable): NotificationMessage
{
return new NotificationMessage(
title: 'タスク「'.$this->task->name.'」の担当になりました',
body: $this->task->operation->name,
url: '/tasks/'.$this->task->code, // アプリ内はパス
actorUserId: Auth::id(), // きっかけを作った人(一覧に頭文字を出す)
);
}
public function toMail($notifiable): MailMessage { /* ... */ }
}
$tenantUser->notify(new TaskAssigned($task));3. 古い通知を消す
$schedule->command('model:prune', ['--model' => [CodebaseJp\Lase\Models\Notification::class]])->daily();4. 画面に置く
| 部品・ルート | 説明 |
|---|---|
NotificationMenu | ヘッダーに置くベル。未読の数を出し(1分ごと・タブに戻ったときに読み直す)、開くと最近の10件を並べる(すべて / 未読)。押すと既読にして開く。すべて既読・通知の設定・すべての通知へのリンクを持つ |
NotificationRoutes | 組織の画面の通知の一覧(notifications.index、/notifications)。日付の区切りごとに並べ、「もっと見る」でさかのぼる。1件ずつ既読・未読を切り替えられる |
createAccountRoutes() | 個人の設定の「通知」(account.notifications)。種別ごとに受け取り方をスイッチで選び、すぐ保存する。createTenantAccountRoutes() と、管理画面・パートナー画面の CommonRoutes も持つ |
部品はどの画面にも置けます。いまの画面のコンテキストは useNotificationContext().setContext() で決め、そのコンテキストの API を使います。Lase の管理画面(AdminLayout)は ADMIN、パートナー画面(PartnerLayout)は PARTNER にしていて、ベル・通知の一覧(/admin/notifications・/partner/notifications)・通知の設定を持っています。組織の画面は既定の TENANT のままでよいです。
その画面のコンテキストにもアカウントにも通知の種別が無いとき(/config の notification_contexts に無いとき)は、ベルも通知の設定も出しません。
Lase の通知
Lase は運営の管理者へ次の通知を送ります(LaseNotificationType)。宛先は有効な管理者(AdminUser::active())で、アプリ内の通知にはいつも残り、メールは各自が個人の設定の「通知」で選びます(既定は受け取る)。
| 種別 | いつ | 開くところ | メール |
|---|---|---|---|
LASE_TENANT_REGISTERED(AdminTenantRegistered) | 新しいテナントが登録されたとき | テナントの詳細 | NewTenantNotification |
LASE_INQUIRY_RECEIVED(AdminInquiryReceived) | お問い合わせが届いたとき | お問い合わせの詳細 | InquiryMail |
これまで管理者ごとに持っていた設定(admin_users.notify_tenant_registration・notify_inquiry)は、v3.25.0 で通知の受け取り方の設定に移しました。
API
組織・管理・パートナーの画面に、同じ形の API があります。それぞれのコンテキストに届いたものと、アカウントへの通知(ACCOUNT)を扱います。
| 画面 | 先頭 | コントローラー |
|---|---|---|
| 組織 | /api/tenant | Tenant\NotificationController |
| 管理 | /api/admin | Admin\NotificationController |
| パートナー | /api/partner | Partner\NotificationController |
| メソッド | パス | 説明 |
|---|---|---|
| GET | /notifications | 自分への通知(新しい順。unread=1 で未読だけ、perPage は最大50) |
| GET | /notifications/unread-count | 未読の数 |
| POST | /notifications/read-all | すべて既読にする |
| PUT | /notifications/{id}/read | 既読にする |
| DELETE | /notifications/{id}/read | 未読に戻す |
| GET | /notification-settings | 受け取り方の設定 |
| PUT | /notification-settings | 受け取り方を1つ変える(type・channel・enabled)。アプリ内は channel に in_app) |
アプリ独自のコンテキストの画面も、HandlesNotifications を使ったコントローラーを置けば同じ API を持てます。
注意
- 通知はリクエストの中で送ります。メールやプッシュ通知を送り終えるまで応答が返らないため、キューのワーカーを動かせる環境なら、通知に
ShouldQueueを付けて後回しにしてください。そのときtoInApp()のactorUserIdはAuth::id()では取れないので、通知を作るときに渡してください - エージェント(
UserType::AGENT)にもアプリ内の通知は残ります(メールは送りません)