データベース通知とは
- 通知の内容 (通知種別や通知内容を含むJSONデータ)をデータベースに保存する
- 上記データを使用して、Webアプリ上で通知を表示することが可能
- よくあるWebサイトのヘッダーのベル通知などに使用できて便利
以下、実装手順を記載してく
1. 通知データを保存するテーブルを作成する
- Laravelが用意してくれてるコマンドを利用して通知テーブルを作成する
php artisan notifications:table
php artisan migrate
これで notifications
テーブルが作成された
構成内容はこんな感じ
カラム | 型 | 内容 |
id | uuid | ユニークID |
type | varchar | 通知の種別 |
notifiable_type | varchar | 通知対象のモデル |
notifiable_id | bigint | 通知対象のモデルのレコードID |
data | text | 通知内容 (json) |
read_at | timestamp | 既読日時 |
上記に加え、従来の$timestamps
カラムも完備
notifiable_typeとnotifiable_idはmorphs
で自動作成される
実際入るデータはこんな感じ
2. 通知クラスを作成する
通知テーブルに保存するためには、通知クラスにtoArray
メソッドを定義する必要がある
ここで返す配列がJSONエンコードされてnortificatons
テーブルのdata
カラムに保存される
- 実装例
public function toArray($notifiable)
{
return [
'invoice_id' => $this->invoice->id,
'amount' => $this->invoice->amount,
];
}
toArrayメソッドではなくtoDatabaseメソッドでも可
toArrayメソッドは「databaseチャンネル」と「broadcastチャンネル」両方に使用され、toDatabaseメソッドは「databaseチャンネル」でのみ使用される
databaseとbroadcastでデータの内容を分けたい場合は
toDatabaseのほうを使用すると良い感じ
- さっそく通知クラスを作成する
今回は、お知らせ (日付・タイトル・内容) の通知クラスを作成してみる
php artisan make:notification InformationNotification
- app/Notifications/InformationNotification.php
<?php
namespace App\Notifications;
use App\Models\Information;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
class InformationNotification extends Notification
{
use Queueable;
private Information $information;
/**
* Create a new notification instance.
*
* @param Information $information
*/
public function __construct(Information $information)
{
$this->information = $information;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['database'];
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
'date' => $this->information->date,
'title' => $this->information->title,
'content' => $this->information->content,
// ⭐️ 通知からリンクしたいURLがあれば設定しておくと便利
'url' => route('infos.show', ['information' => $this->information])
];
}
コマンドで自動生成されたデフォルトの通知クラスから変更した点はざっくり下記
__construct()
でInformation
モデル (=お知らせ) を受け取るvia()
では['database']
を返す- メール通知は使用しないので
toMail()
を削除 toArray()
メソッドで通知に使用したいデータを配列で返す
3. 通知を行う (notificationsテーブルへ保存)
続いて、作成した通知クラスを使用して通知テーブルに登録してみる
通知を行う方法は2つある
- Notifiableトレイトを使用する
$model->notify(new 通知クラス)
- Notificationファサードを使用する
Notification::send(モデルのCollection, new 通知クラス))
どちらでも大丈夫だけど、
単独のユーザーに送る場合はNotifiableトレイト、
複数のユーザーに送る場合はNotificationファサードが便利なので
2通り実装してみる
1. 単独のユーザーに通知する (Notifiableトレイト使用)
まず、通知を行うモデルでNotifiableトレイトをインポートしているか確認する
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; // ⭐️ ここ
class User extends Authenticatable
{
use Notifiable; // ⭐️ ここ
}
続いて、登録部分の実装
- InformationController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Notifications\InformationNotification;
class InformationController extends Controller
{
public function store(Request $request)
{
// お知らせテーブルへ登録
$information = Information::create([
'date' => $request->get('date'),
'title' => $request->get('title'),
'content' => $request->get('content'),
]);
// お知らせ内容を対象ユーザー宛てに通知登録
$user = User::find($request->get('user_id'));
$user->notify(
new InformationNotification($information)
);
}
}
通知するモデルクラスでNotifiableトレイトをuseしておかないと、$model->notify()
を実行してもそんなメソッドないですよと怒られる
デフォルトのUserモデルではすでに記載済みだが、
新しく作成したモデルなどでは注意が必要
2. 複数のユーザーに通知する (Notificationファサード使用)
- InformationController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Notifications\InformationNotification;
use Illuminate\Support\Facades\Notification;
class InformationController extends Controller
{
public function store(Request $request)
{
// お知らせテーブルへ登録
$information = Information::create([
'date' => $request->get('date'),
'title' => $request->get('title'),
'content' => $request->get('content'),
]);
// お知らせ内容を全ユーザー宛てに通知登録
$users = User::all();
Notification::send($users, new InformationNotification($information));
}
}
これでDBを確認してみると、登録されていることを確認
$model->notify(new 通知クラス)
・ Notification::send(モデルのCollection, new 通知クラス))
実行により、対象モデル宛てへの通知レコードがnotifications
テーブルに作成される
notifications.notifiable_type
= $modelのEloquentクラスの場所notifications.notifiable_id
= $model->id
4. 登録した通知を取得 & 表示する
登録した通知をWeb上でユーザーに伝えるためには、まず通知を取得する必要がある
これも、前述のNotificationトレイトを使用することで簡単に取得することができる
- まず、通知対象のモデルでNotificationトレイトをuseする
(※ 既にしている場合は不要)
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; // ⭐️ ここ
class User extends Authenticatable
{
use Notifiable; // ⭐️ ここ
}
- Notificationトレイトが提供する
notifications
リレーションを使って取得する
// ユーザーはとりあえず決めうち
$user = User::find(1);
// ⭐️ 全通知を取得
foreach ($user->notifications as $notification) {
echo $notification->type;
}
// ⭐️ 未読の通知のみを取得
foreach ($user->unreadNotifications as $notification) {
echo $notification->type;
}
上記のようにNotificationトレイトは、
対象のモデルに紐づく通知データを取得するための
各種notifications
リレーションを用意してくれてる
これにより、従来のリレーションと同様の方法で通知へのアクセスが可能になる
- viewで表示する場合はこんな感じ
( Controller側であらかじめ変数に入れてviewに渡してあげてももちろんOK )
<div class="notifications">
@forelse(auth()->user()->notifications()->take(5)->get() as $notification)
<div class="{{ is_null($notification->read_at) ? 'un-read' : '' }}">
<p>{{ $notification->data['date'] }}</p>
<p>{{ $notification->data['title'] }}</p>
<p>{{ $notification->data['content'] }}</p>
</div>
@empty
<p>まだ通知はありません</p>
@endforelse
</div>
リレーションなので、上記のようにtake()
などで取得件数指定などもできて便利
また、notifications.data
のjsonには配列でアクセスできるようにしてくれてるので、$notification->data['key名']
で取得可能になってる
5. 通知を既読にする
ユーザーが通知を確認すると、その通知は既読としてマークする必要がある
今回は呼び出し元のルーティングは省略してメソッドだけ記載する
1つのみ既読にする場合と、ユーザーに紐づく全ての通知を既読にする場合の2種類
- NotificationController
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Notifications\DatabaseNotification;
class NotificationController extends Controller
{
/**
* 通知を既読にする
*
* @param DatabaseNotification $notification
* @return \Illuminate\Http\RedirectResponse
*/
public function read(DatabaseNotification $notification)
{
$notification->markAsRead();
return redirect($notification->data['url']);
}
/**
* 全ての通知を既読にする
*
* @param DatabaseNotification $notification
* @return \Illuminate\Http\RedirectResponse
*/
public function readAll(DatabaseNotification $notification)
{
auth()->user()->unreadNotifications->markAsRead();
return redirect(route('notifications.index'));
}
}
read()
メソッド:
既読にする対象の通知モデルを引数に受け取って更新後、通知に設定されているURLにリダイレクトする
通知一覧から任意の通知を押下された時などにpostで呼び出すと良きreadAll()
メソッド:
ログインユーザーに紐づく通知を全て既読に更新する
通知一覧などに「全て既読にする」などのボタンを設置して押下時にpostで呼び出すと良き
- routes/web.php
Route::post('/notifications/{notification}/read', [App\Http\Controllers\NotificationController::class, 'read'])->name('notifications.read');
Route::post('/notifications/read-all', [App\Http\Controllers\NotificationController::class, 'readAll'])->name('notifications.read-all');
- viewでリンク設定
<div class="notifications">
@forelse(auth()->user()->notifications()->take(5)->get() as $notification)
<a href="{{ route('notifications.read', $notification) }}" class="{{ is_null($notification->read_at) ? 'un-read' : '' }}">
<p>{{ $notification->data['date'] }}</p>
<p>{{ $notification->data['title'] }}</p>
<p>{{ $notification->data['content'] }}</p>
</a>
@empty
<p>まだ通知はありません</p>
@endforelse
<a href="{{ route('notifications.read-all') }}">全て既読にする</a>
</div>
その他応用
- notifications通知一覧をページネーションで取得する
public function index(Request $request)
{
// 対象のページ番号取得
$page = $request->get('page', 1);
// ページネーションで取得
$notifications = auth()->user()->notifications()
->paginate(20, ['*'], 'page', $page);
return view('notifications.index', compact('notifications'));
}
notifications
テーブルのdata
カラムのJSON値を対象にwhere検索する
public function index(Request $request)
{
// 対象のページ番号取得
$page = $request->get('page', 1);
// dataカラムをwhere検索
$infoNotifications = auth()->user()->notifications()
->where('data->title', $request->title)
->get();
return view('notifications.index', compact('notifications'));
}
- 通知を削除する (ユーザー削除時などに)
$user->notifications()->delete();
notifiable_type
に登録されているモデルのデータうち、notifiable_id
と同一IDのレコードに対して、data
の内容を通知する仕組みまた
read_at
の値がnullかどうかで未読・既読の判断が可能