mcpField
L
laravel-notification-patterns

laravel-notification-patterns

github.com/iserter開発4885 回インストール27 週間前に更新
リポジトリ

laravel-notification-patterns について

Laravel 開発者向けに、通知システムの実装パターンをまとめたスキルです。各種配信チャネルに加え、キュー処理、条件付き配信、テスト方法を扱います。

主な機能

  1. 01メール通知と Markdown テンプレート
  2. 02データベース通知の保存と取得
  3. 03ブロードキャストと Slack 通知
  4. 04キュー、遅延、失敗処理
  5. 05条件付き配信とカスタムチャネル
  6. 06通知の送信とテスト方法

活用シーン

  1. 01添付ファイル付き注文メールの実装
  2. 02未読通知の保存と管理
  3. 03トランザクション確定後の通知キュー送信
  4. 04通知チャネルとメール内容のテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/iserter/laravel-claude-agents --skill laravel-notification-patterns

インストール後、Claude Code で/laravel-notification-patternsを入力して読み込まれたか確認します。

詳細

通知パターン

通知クラスの構造

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class OrderShipped extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public readonly Order $order,
    ) {}

    public function via(object $notifiable): array
    {
        return ['mail', 'database', 'broadcast'];
    }

    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject('Order Shipped')
            ->greeting("Hello {$notifiable->name}!")
            ->line("Your order #{$this->order->number} has been shipped.")
            ->action('Track Order', url("/orders/{$this->order->id}/track"))
            ->line('Thank you for your purchase!');
    }

    public function toArray(object $notifiable): array
    {
        return [
            'order_id' => $this->order->id,
            'order_number' => $this->order->number,
            'message' => "Order #{$this->order->number} has been shipped.",
        ];
    }
}

メール通知

MailMessage ビルダー
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->from('[email protected]', 'App Name')
        ->subject('Invoice Paid')
        ->greeting('Hello!')
        ->line('One of your invoices has been paid.')
        ->lineIf($this->amount > 100, 'This was a large payment.')
        ->action('View Invoice', $this->invoiceUrl)
        ->line('Thank you for using our application!')
        ->salutation('Regards, The Team');
}
Markdown メールテンプレート
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject('Order Confirmation')
        ->markdown('mail.order.confirmed', [
            'order' => $this->order,
            'url' => route('orders.show', $this->order),
        ]);
}
{{-- resources/views/mail/order/confirmed.blade.php --}}
<x-mail::message>
# Order Confirmed

Your order **#{{ $order->number }}** has been confirmed.

<x-mail::table>
| Item       | Quantity | Price   |
|:-----------|:---------|:--------|
@foreach ($order->items as $item)
| {{ $item->name }} | {{ $item->quantity }} | ${{ $item->price }} |
@endforeach
</x-mail::table>

<x-mail::button :url="$url">
View Order
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
添付ファイル
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject('Monthly Report')
        ->line('Please find your monthly report attached.')
        ->attach($this->reportPath, [
            'as' => 'report.pdf',
            'mime' => 'application/pdf',
        ])
        ->attachData($this->csvContent, 'data.csv', [
            'mime' => 'text/csv',
        ]);
}

データベース通知

セットアップ
php artisan notifications:table
php artisan migrate
// Model must use Notifiable trait
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
}
通知の保存
public function toArray(object $notifiable): array
{
    return [
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
        'message' => "Invoice #{$this->invoice->number} paid.",
    ];
}
通知の読み取りと管理
// Get all notifications
$notifications = $user->notifications;

// Get unread notifications
$unread = $user->unreadNotifications;

// Mark as read
$user->unreadNotifications->markAsRead();

// Mark a single notification as read
$notification->markAsRead();

// Mark as unread
$notification->markAsUnread();

// Delete old notifications
$user->notifications()->where('created_at', '<', now()->subMonths(3))->delete();

ブロードキャスト通知

use Illuminate\Notifications\Messages\BroadcastMessage;

public function toBroadcast(object $notifiable): BroadcastMessage
{
    return new BroadcastMessage([
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ]);
}

// Custom channel name (optional)
public function broadcastType(): string
{
    return 'invoice.paid';
}
// Listening with Echo
Echo.private(`App.Models.User.${userId}`)
    .notification((notification) => {
        console.log(notification.type);
        console.log(notification.invoice_id);
    });

Slack 通知

use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\SlackMessage;

public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->text("Order #{$this->order->number} shipped")
        ->headerBlock("Order Shipped")
        ->sectionBlock(function (SectionBlock $block) {
            $block->text("Order *#{$this->order->number}* has been shipped.");
            $block->field("*Customer:*\n{$this->order->customer_name}")->markdown();
            $block->field("*Tracking:*\n{$this->order->tracking_number}")->markdown();
        });
}

通知のキュー処理

基本的なキュー処理
// ✅ Implement ShouldQueue
class OrderShipped extends Notification implements ShouldQueue
{
    use Queueable;

    // Per-channel queue configuration
    public function viaQueues(): array
    {
        return [
            'mail' => 'mail-queue',
            'database' => 'default',
            'slack' => 'slack-queue',
        ];
    }
}
コミット後
// ✅ Only dispatch after database transaction commits
class OrderShipped extends Notification implements ShouldQueue
{
    use Queueable;

    public $afterCommit = true;
}
遅延通知
$user->notify(
    (new OrderShipped($order))->delay([
        'mail' => now()->addMinutes(5),
        'database' => now(),
    ])
);
再試行と失敗時の処理
class OrderShipped extends Notification implements ShouldQueue
{
    use Queueable;

    public $tries = 3;
    public $backoff = [30, 60, 120];

    public function failed(\Throwable $exception): void
    {
        // Handle failure (log, alert, etc.)
        Log::error('OrderShipped notification failed', [
            'order_id' => $this->order->id,
            'error' => $exception->getMessage(),
        ]);
    }
}

条件付き送信

public function shouldSend(object $notifiable, string $channel): bool
{
    // Don't send mail if user disabled email notifications
    if ($channel === 'mail') {
        return $notifiable->prefers_email_notifications;
    }

    // Don't notify about small amounts
    return $this->invoice->amount > 10;
}

オンデマンド通知

use Illuminate\Support\Facades\Notification;

// ✅ Send to an email address without a user model
Notification::route('mail', '[email protected]')
    ->route('slack', '#alerts')
    ->notify(new ServerHealthReport($server));

// ✅ With recipient name
Notification::route('mail', ['[email protected]' => 'Admin User'])
    ->notify(new WeeklyDigest());

通知の送信

use Illuminate\Support\Facades\Notification;

// Via the Notifiable trait
$user->notify(new OrderShipped($order));

// Via the Notification facade (multiple recipients)
Notification::send($users, new OrderShipped($order));

// ❌ Don't loop to send individually
foreach ($users as $user) {
    $user->notify(new OrderShipped($order)); // Inefficient
}

// ✅ Send to a collection
Notification::send(User::all(), new SystemAnnouncement($message));

カスタム通知チャネル

class SmsChannel
{
    public function send(object $notifiable, Notification $notification): void
    {
        $message = $notification->toSms($notifiable);
        $phone = $notifiable->routeNotificationFor('sms', $notification);

        // Send SMS via your provider
        SmsProvider::send($phone, $message);
    }
}

// In the notification
public function via(object $notifiable): array
{
    return [SmsChannel::class, 'database'];
}

public function toSms(object $notifiable): string
{
    return "Your order #{$this->order->number} has been shipped.";
}

// On the notifiable model
public function routeNotificationForSms(Notification $notification): string
{
    return $this->phone_number;
}

通知のテスト

use Illuminate\Support\Facades\Notification;

public function test_order_shipped_notification_is_sent(): void
{
    Notification::fake();

    // Perform action that triggers notification
    $order = Order::factory()->create();
    $order->ship();

    // Assert notification was sent
    Notification::assertSentTo(
        $order->user,
        OrderShipped::class,
        function (OrderShipped $notification, array $channels) use ($order) {
            return $notification->order->id === $order->id
                && in_array('mail', $channels)
                && in_array('database', $channels);
        }
    );

    // Assert not sent to other users
    Notification::assertNotSentTo(
        User::factory()->create(),
        OrderShipped::class,
    );

    // Assert count
    Notification::assertSentToTimes($order->user, OrderShipped::class, 1);

    // Assert nothing sent
    Notification::assertNothingSent();
}
メール内容のテスト
public function test_order_shipped_mail_content(): void
{
    $order = Order::factory()->create();
    $notification = new OrderShipped($order);

    $mail = $notification->toMail($order->user);

    $this->assertEquals('Order Shipped', $mail->subject);
    $this->assertStringContainsString($order->number, $mail->render());
}

チェックリスト

  • 通知クラスで via() を使って適切なチャネルを指定している
  • メール通知で MailMessage ビルダーまたは Markdown テンプレートを使用している
  • データベース通知テーブルのマイグレーションが実行されている
  • toArray() がデータベース保存用にシリアライズ可能なデータのみを返す
  • 実行時間の長い通知で ShouldQueue を実装している
  • viaQueues() でチャネルごとのキュー名を設定している
  • データベーストランザクション内で送信する場合は afterCommit を設定している
  • 配信条件のロジックに shouldSend() を使用している
  • モデル以外の受信者にはオンデマンド通知を使用している
  • 一括送信ではループではなく Notification::send() を使用している
  • 通知のテストで Notification::fake() を使用している
  • キューに登録した通知の再試行と失敗時の処理を設定している

コメント