🤩

Laravel 12で管理者通知システムを実装する

に公開

概要

新規ユーザー登録やメール認証完了時に、管理者宛に自動で通知メールを送信するシステムを実装しました。この記事では、Laravel 12のイベントシステム、通知機能、重複送信防止など、実装した全ての要素を初心者にも分かりやすく解説します。

実装した機能

  • 新規ユーザー登録時に管理者に通知メール送信
  • メール認証完了時に管理者に通知メール送信
  • 重複送信防止機能
  • 非同期処理(キュー)対応

目次

  1. Laravelのイベントシステムとは
  2. 通知クラスの作成
  3. イベントリスナーの作成
  4. EventServiceProviderの設定
  5. 重複送信防止機能
  6. 設定ファイルの作成
  7. 実際の動作フロー
  8. まとめ

Laravelのイベントシステムとは

イベントとは?

イベントとは、アプリケーション内で「何かが起こった」ことを表す仕組みです。例えば:

  • ユーザーが新規登録した
  • メール認証が完了した
  • 注文が確定した

これらの「出来事」をイベントとして定義し、その出来事が発生した時に自動で処理を実行できます。

イベントの仕組み

ユーザー登録 → Registeredイベント発火 → リスナーが実行 → 管理者に通知メール送信

Laravel 12でのイベント登録方法

Laravel 12では、bootstrap/app.phpでイベントを登録する方法と、従来のEventServiceProviderを使う方法があります。今回は後者を採用しました。


通知クラスの作成

通知クラスとは?

通知クラスは、メールやSMSなどの通知を送信するための専用クラスです。LaravelのNotificationクラスを継承して作成します。

新規登録通知クラス

<?php

namespace App\Notifications;

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

class AdminNewUserRegistered extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(private readonly User $user)
    {
    }

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

    public function toMail(object $notifiable): MailMessage
    {
        $user = $this->user;

        return (new MailMessage)
            ->subject('[AWSの中学校] '.$user->name.' さんが新規登録しました')
            ->greeting('新規登録のご連絡')
            ->line($user->name.' さんが新規登録しました。')
            ->line('メールアドレス: '.$user->email)
            ->line('登録日時: '.now()->toDateTimeString())
            ->salutation('AWSの中学校');
    }
}

コード解説

1. クラス定義

class AdminNewUserRegistered extends Notification implements ShouldQueue
  • Notificationを継承:Laravelの通知機能を使う
  • ShouldQueueを実装:非同期処理(キュー)で実行

2. コンストラクタ

public function __construct(private readonly User $user)
  • private readonly:PHP 8.1の機能で、プロパティを自動で定義
  • ユーザー情報を受け取って保存

3. 通知方法の指定

public function via(object $notifiable): array
{
    return ['mail'];
}
  • viaメソッドで通知方法を指定
  • ['mail']:メール通知のみ
  • 他にも['database'](DB保存)、['slack'](Slack通知)など

4. メール内容の定義

public function toMail(object $notifiable): MailMessage
{
    $user = $this->user;

    return (new MailMessage)
        ->subject('[AWSの中学校] '.$user->name.' さんが新規登録しました')
        ->greeting('新規登録のご連絡')
        ->line($user->name.' さんが新規登録しました。')
        ->line('メールアドレス: '.$user->email)
        ->line('登録日時: '.now()->toDateTimeString())
        ->salutation('AWSの中学校');
}
  • MailMessage:Laravelのメールビルダー
  • subject():件名を設定
  • greeting():挨拶文を設定
  • line():本文の行を追加
  • salutation():署名を設定

認証完了通知クラス

<?php

namespace App\Notifications;

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

class AdminEmailVerified extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(private readonly User $user)
    {
    }

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

    public function toMail(object $notifiable): MailMessage
    {
        $user = $this->user;

        return (new MailMessage)
            ->subject('[AWSの中学校] '.$user->name.' さんのメール認証が完了しました')
            ->greeting('メール認証完了のお知らせ')
            ->line($user->name.' さんがメール認証を完了しました。')
            ->line('これで学習コンテンツにアクセスできるようになりました。')
            ->line('メールアドレス: '.$user->email)
            ->line('認証完了日時: '.now()->toDateTimeString())
            ->line('学習を開始する準備が整いました!')
            ->salutation('AWSの中学校');
    }
}

イベントリスナーの作成

リスナーとは?

リスナーは、イベントが発生した時に実行される処理を定義するクラスです。イベントを「聞いて」、指定した処理を実行します。

新規登録リスナー

<?php

namespace App\Listeners;

use App\Notifications\AdminNewUserRegistered;
use Illuminate\Auth\Events\Registered;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Cache;

class NotifyAdminOfNewRegistration implements ShouldQueue
{
    public function handle(Registered $event): void
    {
        $user = $event->user;
        $cacheKey = 'admin_notification_sent_' . $user->id;

        // 5分以内に同じユーザーからの通知が送信済みの場合はスキップ
        if (Cache::has($cacheKey)) {
            return;
        }

        $adminEmail = config('admin.register_notify_email');
        if (! $adminEmail) {
            return;
        }

        Notification::route('mail', $adminEmail)
            ->notify(new AdminNewUserRegistered($user));

        // 通知送信済みをキャッシュに記録(5分間)
        Cache::put($cacheKey, true, now()->addMinutes(5));
    }
}

コード解説

1. クラス定義

class NotifyAdminOfNewRegistration implements ShouldQueue
  • ShouldQueue:非同期処理で実行

2. handleメソッド

public function handle(Registered $event): void
  • Registered $event:Laravelの新規登録イベント
  • イベントが発生すると自動でこのメソッドが呼ばれる

3. 重複防止機能

$user = $event->user;
$cacheKey = 'admin_notification_sent_' . $user->id;

// 5分以内に同じユーザーからの通知が送信済みの場合はスキップ
if (Cache::has($cacheKey)) {
    return;
}
  • $event->user:イベントからユーザー情報を取得
  • $cacheKey:ユーザーIDベースのキャッシュキーを作成
  • Cache::has():キャッシュに存在するかチェック
  • 存在する場合は処理をスキップ(重複送信防止)

4. 管理者メールアドレスの取得

$adminEmail = config('admin.register_notify_email');
if (! $adminEmail) {
    return;
}
  • config('admin.register_notify_email'):設定ファイルから管理者メールアドレスを取得
  • 設定されていない場合は処理をスキップ

5. 通知送信

Notification::route('mail', $adminEmail)
    ->notify(new AdminNewUserRegistered($user));
  • Notification::route():特定の宛先に通知を送信
  • new AdminNewUserRegistered($user):通知クラスのインスタンスを作成

6. キャッシュ記録

Cache::put($cacheKey, true, now()->addMinutes(5));
  • 通知送信済みをキャッシュに記録
  • 5分間の有効期限を設定

認証完了リスナー

<?php

namespace App\Listeners;

use App\Notifications\AdminEmailVerified;
use Illuminate\Auth\Events\Verified;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Cache;

class NotifyAdminOfEmailVerification implements ShouldQueue
{
    public function handle(Verified $event): void
    {
        $user = $event->user;
        $cacheKey = 'admin_verification_notification_sent_' . $user->id;

        // 5分以内に同じユーザーからの認証完了通知が送信済みの場合はスキップ
        if (Cache::has($cacheKey)) {
            return;
        }

        $adminEmail = config('admin.register_notify_email');
        if (! $adminEmail) {
            return;
        }

        Notification::route('mail', $adminEmail)
            ->notify(new AdminEmailVerified($user));

        // 通知送信済みをキャッシュに記録(5分間)
        Cache::put($cacheKey, true, now()->addMinutes(5));
    }
}

EventServiceProviderの設定

EventServiceProviderとは?

イベントとリスナーを紐付ける設定ファイルです。どのイベントが発生した時に、どのリスナーを実行するかを定義します。

<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use App\Listeners\NotifyAdminOfNewRegistration;
use App\Listeners\NotifyAdminOfEmailVerification;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event to listener mappings for the application.
     *
     * @var array<class-string, array<int, class-string>>
     */
    protected $listen = [
        Registered::class => [
            NotifyAdminOfNewRegistration::class,
        ],
        Verified::class => [
            NotifyAdminOfEmailVerification::class,
        ],
    ];

    /**
     * Register any events for your application.
     */
    public function boot(): void
    {
        //
    }
}

コード解説

1. イベントとリスナーのマッピング

protected $listen = [
    Registered::class => [
        NotifyAdminOfNewRegistration::class,
    ],
    Verified::class => [
        NotifyAdminOfEmailVerification::class,
    ],
];
  • Registered::class:新規登録イベント
  • Verified::class:メール認証完了イベント
  • 配列形式で複数のリスナーを登録可能

2. プロバイダーの登録

bootstrap/app.phpでEventServiceProviderを登録:

->withProviders([
    EventServiceProvider::class,
])

重複送信防止機能

なぜ重複送信が起こる?

  1. イベントの重複発火:同じイベントが複数回発火する
  2. キュー処理の重複:同じジョブが複数回実行される
  3. ユーザーの重複操作:同じ操作を複数回実行する

解決方法:キャッシュベースの重複チェック

$cacheKey = 'admin_notification_sent_' . $user->id;

// 5分以内に同じユーザーからの通知が送信済みの場合はスキップ
if (Cache::has($cacheKey)) {
    return;
}

// 通知送信処理...

// 通知送信済みをキャッシュに記録(5分間)
Cache::put($cacheKey, true, now()->addMinutes(5));

キャッシュキーの設計

  • 新規登録: admin_notification_sent_{ユーザーID}
  • 認証完了: admin_verification_notification_sent_{ユーザーID}

有効期限の設定

  • 5分間:短すぎると通知が届かない、長すぎると重複防止が効かない
  • 必要に応じて調整可能

設定ファイルの作成

管理者設定ファイル

<?php

return [
    'register_notify_email' => env('ADMIN_REGISTER_NOTIFY_EMAIL'),
];

.envファイルの設定

# 管理者通知メールアドレス
ADMIN_REGISTER_NOTIFY_EMAIL=設定したいメールアドレス

設定の取得方法

$adminEmail = config('admin.register_notify_email');
  • config('admin.register_notify_email'):設定ファイルから値を取得
  • .envファイルで上書き可能
  • デフォルト値を設定可能

実際の動作フロー

新規登録時の流れ

1. ユーザーが新規登録
   ↓
2. Registeredイベントが発火
   ↓
3. NotifyAdminOfNewRegistrationリスナーが実行
   ↓
4. 重複チェック(キャッシュ確認)
   ↓
5. 管理者に通知メール送信
   ↓
6. 送信済みをキャッシュに記録

メール認証完了時の流れ

1. ユーザーがメール認証リンクをクリック
   ↓
2. Verifiedイベントが発火
   ↓
3. NotifyAdminOfEmailVerificationリスナーが実行
   ↓
4. 重複チェック(キャッシュ確認)
   ↓
5. 管理者に通知メール送信
   ↓
6. 送信済みをキャッシュに記録

送信されるメール内容

新規登録通知

  • 件名: 「[アプリ名] 田中太郎 さんが新規登録しました」
  • 内容: 登録日時、メールアドレス

認証完了通知

  • 件名: 「[アプリ名] 田中太郎 さんのメール認証が完了しました」
  • 内容: 認証完了日時、学習開始準備完了のメッセージ

まとめ

実装した機能

  1. イベントベースの通知システム

    • Laravelのイベントシステムを活用
    • 新規登録とメール認証完了で自動通知
  2. 重複送信防止

    • キャッシュベースの重複チェック
    • 5分間の有効期限で確実に1通のみ送信
  3. 非同期処理

    • キューを使用した非同期送信
    • ユーザー体験を損なわない
  4. 設定の外部化

    • .envファイルで管理者メールアドレスを管理
    • 環境ごとに異なる設定が可能

技術的なポイント

  • イベントリスナー: イベント駆動型の設計
  • 通知クラス: 再利用可能な通知機能
  • キャッシュ: 重複防止とパフォーマンス向上
  • キュー: 非同期処理によるUX向上

今後の拡張可能性

  • 複数宛先への通知: 配列形式で複数の管理者に送信
  • 通知方法の追加: Slack、SMS、プッシュ通知など
  • 通知内容のカスタマイズ: テンプレート化による柔軟な内容変更
  • 通知履歴の管理: データベースに通知履歴を保存

この実装により、管理者は新規ユーザーの登録状況をリアルタイムで把握でき、適切なフォローアップが可能になります。


参考リンク

Discussion