> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# パッケージのカスタム通知チャネルと配信検証

> Laravel 13の通知チャネルをパッケージから提供し、クラス名での解決、宛先ルーティング、キュー処理、実際の配信テストを解説します。

パッケージから通知の配信方法を増やしたいときは、**チャネルの選択**と**通知先の決定**を分けて設計します。このページでは運用イベントをログに記録する小さなチャネルを例に、パッケージで提供するクラスの解決経路と、更新時に必要な検証を説明します。外部サービスへの送信に置き換える場合も、チャネルと通知の責任分担は同じです。

Laravel Framework `v13.38.0` の実装を確認しています。通知の基本操作は[通知](/jp/notifications.md)、パッケージのテスト環境は[パッケージのテスト](/jp/advanced/package-testing.md)を参照してください。

## クラス名は配信方法、ルートは宛先

`via()` に返すチャネルクラス名は、どのオブジェクトの `send()` を呼ぶかを指定します。一方、`routeNotificationFor()` から取得する値は、そのチャネルが使う宛先です。両者を同じ名前にする必要はありません。

| 指定箇所 | このページの例 | 役割 |
| - | - | - |
| 通知の `via()` | `OperationsLogChannel::class` | チャネルの解決と配信 |
| 通知可能オブジェクトのルート | `operationsLog` → `operations` | チャネル内で使う宛先 |
| 通知の `viaQueues()` | `OperationsLogChannel::class` → `operations-log` | キューに入れる場合のジョブの行き先 |

`NotificationSender` は `via()` が選んだチャネル名を `ChannelManager::driver()` に渡します。`ChannelManager` は組み込み名や登録済みの名前で見つからず、名前が実在するクラスなら、コンテナでそのクラスを解決します。**クラス名で使うだけなら `Notification::extend()` やパッケージのサービスプロバイダーは不要**です。Composerのオートロードでクラスが見つかり、コンストラクターの依存をコンテナが解決できるようにします。

```mermaid theme={null}
flowchart LR
    A["via()でクラス名を選択"] --> B["ChannelManager::driver()"]
    B --> C{"解決済み？"}
    C -->|はい| D["保持したチャネル"]
    C -->|いいえ| E["コンテナで生成"]
    E --> D
    D --> F["send()で宛先を取得して配信"]
```

## チャネル本体をパッケージで提供する

例の `Acme\OperationsLog` パッケージは、機密情報を含まないイベント名を、通知先の運用チーム名とともに記録します。Laravelアプリケーションでは `Psr\Log\LoggerInterface` がログサービスに割り当てられるため、コンストラクターから受け取れます。

```php theme={null}
<?php

namespace Acme\OperationsLog;

use Illuminate\Notifications\Notification;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;

final class OperationsLogChannel
{
    public function __construct(private readonly LoggerInterface $logger)
    {
    }

    public function send(object $notifiable, Notification $notification): void
    {
        $destination = $notifiable->routeNotificationFor('operationsLog', $notification);

        if (! is_string($destination) || $destination === '') {
            throw new InvalidArgumentException('Operations log destination is required.');
        }

        $this->logger->info('Operations notification', [
            'destination' => $destination,
            'event' => $notification->toOperationsLog($notifiable),
        ]);
    }
}
```

受け取るオブジェクトは `Notifiable` トレイトを使うモデルか、`Notification::route()` が作るオンデマンド通知先を想定しています。前者では `routeNotificationForOperationsLog()`、後者では `route('operationsLog', ...)` の登録値が返ります。宛先がないまま配信済みとして扱わないよう、この例では例外にします。実際の外部配信に置き換える場合も、宛先や送信結果を確認し、失敗を成功として握りつぶさない設計にします。

> [!WARNING]
>
> ログに記録するイベント名や宛先に、アクセストークンや個人情報を含めないでください。この例はチャネル解決とテストを示すためのログ送信であり、外部サービスへの配信実装ではありません。

## 利用側で通知と宛先を定義する

アプリケーションの通知はチャネルクラスを返し、チャネルに渡すメッセージだけを定義します。`ShouldQueue` と `Queueable` を使うと、通常の `notify()` 呼び出しはキュー経由になります。

```php theme={null}
<?php

namespace App\Notifications;

use Acme\OperationsLog\OperationsLogChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class HealthCheckPassed extends Notification implements ShouldQueue
{
    use Queueable;

    public function via(object $notifiable): array
    {
        return [OperationsLogChannel::class];
    }

    public function toOperationsLog(object $notifiable): string
    {
        return 'health-check-passed';
    }

    public function viaQueues(): array
    {
        return [OperationsLogChannel::class => 'operations-log'];
    }
}
```

宛先をその場で指定する場合、`Notification::route()` のキーは**チャネルクラス名ではなく**、チャネル内で呼ぶ `routeNotificationFor('operationsLog', ...)` のキーです。

```php theme={null}
use App\Notifications\HealthCheckPassed;
use Illuminate\Support\Facades\Notification;

Notification::route('operationsLog', 'operations')
    ->notify(new HealthCheckPassed());
```

アプリケーションのモデルから送る場合は、`Notifiable` を使うモデルに次のメソッドを用意します。

```php theme={null}
use Illuminate\Notifications\Notification;

public function routeNotificationForOperationsLog(Notification $notification): string
{
    return 'operations';
}
```

このモデルに対する `$user->notify(new HealthCheckPassed())` でも同じチャネルが選ばれます。キュー接続を設定し、`operations-log` キューを処理するワーカーを起動してください。

```shell theme={null}
php artisan queue:work --queue=operations-log
```

`OperationsLogChannel` 自体はチャネルごとにManagerへ保持されるため、**通知先や通知本文をチャネルのプロパティに保存しない**で、毎回の `send()` の引数から取り出してください。長時間稼働するキューワーカーでも前の通知の情報を持ち越さないためです。

## 名前の短いエイリアスが必要な場合

利用者に `via()` でクラス名でなく短い名前を指定させたい場合だけ、パッケージの通常のサービスプロバイダーで `Notification::extend()` を使います。

```php theme={null}
<?php

namespace Acme\OperationsLog;

use Illuminate\Support\Facades\Notification;
use Illuminate\Support\ServiceProvider;

class OperationsLogServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Notification::extend(
            'acme-operations-log',
            fn ($app) => $app->make(OperationsLogChannel::class),
        );
    }
}
```

プロバイダーは[パッケージの自動検出](/jp/advanced/package-discovery.md)などで、配信処理が始まる前に読み込ませます。エイリアスを選ぶ通知では `via()` を `['acme-operations-log']` に、`viaQueues()` のキーも同じ名前に変更します。**宛先取得用の `operationsLog` は別のキー**なので変更不要です。組み込みチャネル名と衝突しない名前を使ってください。

Managerは解決したチャネルを名前ごとに保持します。同じプロセスでエイリアスの生成処理を再登録しても、すでに生成済みのインスタンスは置き換わりません。登録方法の変更は、新しく起動したアプリケーションやワーカーでも検証してください。

## fakeとは別に配信処理をテストする

`Notification::fake()` は通知を送る**意図**を検証するためのものです。チャネルの `send()` やルートの取得が動くことは確認できません。配信のテストでは本物の通知Managerを使い、`LoggerInterface` をテスト用に置き換えます。

次は[Testbenchの `TestCase`](/jp/advanced/package-testing) から実行する例です。`notifyNow()` を使うと、`ShouldQueue` 通知でもワーカーを起動せずチャネルの処理を実行できます。

```php theme={null}
<?php

namespace Acme\OperationsLog\Tests;

use Acme\OperationsLog\OperationsLogChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification as BaseNotification;
use Illuminate\Support\Facades\Notification;
use Orchestra\Testbench\TestCase;
use Psr\Log\LoggerInterface;

class OperationsLogChannelTest extends TestCase
{
    public function test_channel_writes_the_routed_event(): void
    {
        $notification = new class extends BaseNotification implements ShouldQueue {
            use Queueable;

            public function via(object $notifiable): array
            {
                return [OperationsLogChannel::class];
            }

            public function toOperationsLog(object $notifiable): string
            {
                return 'health-check-passed';
            }
        };

        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('info')->with(
            'Operations notification',
            ['destination' => 'operations', 'event' => 'health-check-passed'],
        );

        $this->app->instance(LoggerInterface::class, $logger);

        Notification::route('operationsLog', 'operations')->notifyNow($notification);
    }
}
```

加えて、宛先がない場合に例外になること、エイリアスを提供する場合に新規起動から解決できることを確認します。キュー経由で使う場合は、別途キューワーカーを動かす検証アプリケーションで、`operations-log` キューにジョブが届き処理されることもテストします。

`NotificationSender` は `ShouldQueue` の通知をチャネルごとにジョブへ分け、`viaQueues()` のキーでキューを選びます。チャネルの `send()` が例外を投げた場合は `NotificationFailed` を発火して例外を再送出し、正常に戻った場合に `NotificationSent` を発火します。キューの再試行設定はアプリケーションの設定に従います。例外をチャネル内で握りつぶすと、配信に失敗しても成功として扱われる点に注意してください。

## 参照した一次情報

* [Laravel 13公式ドキュメント: カスタム通知チャネルとキュー](https://github.com/laravel/docs/blob/13.x/notifications.md#custom-channels)
* [Laravel 13公式ドキュメント: パッケージの自動検出](https://github.com/laravel/docs/blob/13.x/packages.md#package-discovery)
* [ChannelManager: クラス名からの解決](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/ChannelManager.php)
* [NotificationSender: チャネルごとの配信・キュー・イベント](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/NotificationSender.php)
* [RoutesNotificationsとAnonymousNotifiable: 通知先ルート](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Notifications/RoutesNotifications.php)
* [Manager: ドライバーの保持と拡張](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Support/Manager.php)
* [Application: LoggerInterfaceのバインディング](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Foundation/Application.php)


## Related topics

- [通知(Notifications)](/jp/notifications.md)
- [通知チャンネル - LINE SDK for Laravel](/jp/packages/laravel-line-sdk/notification.md)
- [通知チャンネル - Laravel Bluesky](/jp/packages/laravel-bluesky/notification.md)
- [応用トピック](/jp/advanced/index.md)
- [Boostのカスタムエージェントを作る](/jp/advanced/boost-custom-agent.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.