Laravelのdatabase-notificationsのデフォルトのテーブルを変更する

# バージョン
* Laravel 11
* PHP 8.3

# デフォルトの挙動

まずデフォルトのnotificationsテーブルへのデータ保存 + メール送信が動くようにします。

https://laravel.com/docs/11.x/notifications#database-notifications

`php artisan make:notifications-table`コマンドで通知データを保存するためのmigrationファイルが作成できます。

```php
<?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');
    }
};
```

UserモデルはNotifiableトレイトを利用する必要があります。

```diff_php:app/Models/User.php
class User extends Authenticatable
{
+    use Notifiable;
```

:::note info
usersテーブルとは別のテーブルにメールアドレスを保存している場合は、routeNotificationForMailメソッドをUserモデルに作成する必要があります。

```diff_php:app/Models/User.php
class User extends Authenticatable
{
    use Notifiable;

+    public function routeNotificationForMail(): array|string
+    {
+        // usersテーブルではなくrelation先のprofilesテーブルにメールアドレスを保存している場合など
+        return $this->profile->email;
+    }
```
:::

`php artisan make:notification TestNotification`で通知用のクラスを作成します。


```diff_php:app/Notifications/TestNotification.php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class TestNotification 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'];
+        return ['database', '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 [
            //
        ];
    }
}
```


あとは任意のUserモデルを取得しnotifyメソッドを実行するとnotificationsテーブルにデータが保存されるとともにメールが送信されます。

```php
/** @var User $user */
$user = User::find($id);
$user->notify(new TestNotification());
```

notifications
---
| id | type | notifiable_type | notifiable_id | data | read_at | created_at | updated_at | 
| --- | --- | --- | ---: | --- | --- | --- | --- | 
| 591dd764-5a26-42ff-b71c-2815d01aded3 | App\Notifications\TestNotification | App\Models\User | 1 | [] | NULL | 2024-06-10 18:10:21 | 2024-06-10 18:10:21 | 



# テーブル&カラムの変更

以下のように`notifications`テーブルを`user_notifications`テーブルに変更、不要なカラムも削除します。

```php
<?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('user_notifications', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->morphs('notifiable');
            $table->text('data');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('user_notifications');
    }
};
```

## モデル作成

`php artisan make:model UserNotification`でモデルを作成しDatabaseNotificationクラスを継承しテーブル名を設定します。

```diff_php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Notifications\DatabaseNotification;

-class UserNotification extends Model
+class UserNotification extends DatabaseNotification
{
    use HasFactory;

+   protected $table = 'user_notifications';
}

```

Userモデルのnotificationメソッドを作成します。

```php:app/Models/User.php
    public function notifications()
    {
        return $this->morphMany(UserNotification::class, 'notifiable')->latest();
    }
```

:::note info
デフォルトのnotificationsメソッドはnotifiableトレイトのさらにHasDatabaseNotificationsトレイトにあります。

```php:vendor/laravel/framework/src/Illuminate/Notifications/HasDatabaseNotifications.php
trait HasDatabaseNotifications
{
    /**
     * Get the entity's notifications.
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
     */
    public function notifications()
    {
        return $this->morphMany(DatabaseNotification::class, 'notifiable')->latest();
    }
```
:::

## CustomDatabaseChannelの作成

`php artisan make:channel CustomDatabaseChannel`コマンドでCustomDatabaseChannelを作成しbuildPayloadメソッドをオーバーライドします。
第1引数の$notifiableはnotifiableトレイトを使用しているUserモデルのインスタンスが渡されます。第2引数はnotifyメソッドの引数で渡したNotificationクラスが渡されます。

```php
<?php

namespace App\Broadcasting;

use App\Models\User;
use App\Notifications\TestNotification;
use Illuminate\Notifications\Channels\DatabaseChannel;
use Illuminate\Notifications\Notification;

class CustomDatabaseChannel extends DatabaseChannel
{
    /**
     * @param User $notifiable
     */
    protected function buildPayload($notifiable, TestNotification|Notification $notification): array
    {
        return [
            'id' => $notification->id,
            'user_id' => $notifiable->id,
            'data' => $this->getData($notifiable, $notification),
        ];
    }
}
```

:::note info
TestNotificationクラスで作成したデータはtoArrayメソッド(もしくはtoDatabaseメソッド)を通して、CustomDatabaseChannelクラスのgetDataメソッドを使用して取得することができます。(https://laravel.com/docs/11.x/notifications#formatting-database-notifications)

```php:app/Notifications/TestNotification.php
public function toArray(object $notifiable): array
{
    return [
        'key' => '任意の値',
    ];
}
```

```php
protected function buildPayload($notifiable, TestNotification|Notification $notification): array
{
    // toArrayの返り値をgetDataメソッドで取得することができる
    $data = $this->getData($notifiable, $notification);
```

:::

TestNotificationクラスのviaメソッドのdatabaseをCustomDatabaseChannelに書き換えます。

```diff_php:app/Notifications/TestNotification.php
public function via(object $notifiable): array
{
-   return ['database', 'mail'];
+   return [CustomDatabaseChannel::class, 'mail'];
}
```

以上でデフォルトのnotificationsテーブルとは異なるテーブルにデータを保存することができるようになりました。

user_notifications
---
| id | user_id | notifiable_type | notifiable_id | data | created_at | updated_at | 
| --- | ---: | --- | ---: | --- | --- | --- | 
| 89dd877f-c637-4728-a292-76ce5a5beb5b | 1 | App\Models\User | 1 | [] | 2024-06-10 18:52:59 | 2024-06-10 18:52:59 | 


:::note info
user_notificationsテーブルのnotifiable_type、notifiable_idカラムを削除して、以下のようにすることも可能です。

```php
public function notifications(): HasMany
{
    return $this->hasMany(UserNotification::class)->latest();
}
```
:::