4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel 通知機能実装 通知送信から既読まで

Last updated at Posted at 2024-03-24

通知送信機能

メール通知とデータベース通知を行います。

通知テーブルを作成

php artisan notifications:table
php artisan migrate

作成されたマイグレーションファイル

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

作成されたテーブル

カラム データ型 説明
id uuid PK
type string 通知のタイプ
notifiable_type string 通知対象のモデル
notifiable_id bigint 通知対象のユーザーid
data text 通知内容
read_at time_stamps 既読日時

通知クラスを作成

今回はメール通知とデータベース通知を行います。
Markdownメール通知ではBladeコンポーネントとMarkdown記法が利用でき、メールメッセージを簡単に構築できると同時に、Laravelが用意している通知コンポーネントも活用できます。

php artisan make:notification InformationNotification --markdown=mail.notification

コード全体図

app/Notifications/InformationNotification.php
<?php

namespace App\Notifications;

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

class InformationNotification extends Notification
{
    use Queueable;

    protected $title;
    protected $content;

    /**
     * Create a new notification instance.
     */
    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content = $content;
        //
    }

    /**
     * メール、データベース通知を指定
     *
     * @return array<int, string>
     */
    public function via(object $notifiable): array
    {
        return ['mail', 'database'];
    }

    /**
     * メール通知の送信
     */
    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
                ->subject($this->title)
                ->markdown('mail.notification', ['title' => $this->title, 'content' => $this->content]);
    }

    /**
     * 通知をデータベースに保存
     *
     * @return array<string, mixed>
     */
    public function toArray(object $notifiable): array
    {
        return [
            'title' => $this->title,
            'content' => $this->content,
        ];
    }
}

対応するMarkdownテンプレートを指定し、Mailableを生成するには、make:notification Artisanコマンドを--markdownオプション付きで使用します。

メッセージカスタマイズ方法

resources/views/mail/notification.blade.php
<x-mail::message>
# {{ $title }}

{{ $content }}



Thanks,<br>
</x-mail::message>

スクリーンショット 2024-03-24 14.51.43.png

他にも

  • Buttonコンポーネント
  • Panelコンポーネント
  • Tableコンポーネント
    などでカスタマイズができます。

Notifiableトレイトの使用

Notifiableトレイトは、アプリケーションのApp\Models\Userモデルにデフォルトで含まれています。

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
}

通知作成

通知を送信したい相手にnotifyメソッドで引数を渡します。

$user->notify(new InformationNotification('title', 'content'));

通知表示と既読機能

ビューコンポーザークラスを作成

まず、ビューコンポーザーとして機能するクラスを作成します。このクラスでは、ビューに渡すデータを定義します。

php artisan make:provider ViewServiceProvider

このコマンドでApp\Providers\ViewServiceProvider.phpが作成されます。

ViewServiceProviderの編集

今回はヘッダーで通知を表示させたかったので*で全てのviewに適応されるようにしました。
認証ユーザーを取得し、unreadNotificationsは未読の通知。notificationsは既読・未読の区別なく、全ての通知が含まれます。

app\Providers\ViewServiceProvider.php
class ViewServiceProvider extends ServiceProvider
{

    /**
     * ログインユーザーの未読通知をビューに渡す
     *
     * @return void
     */
    public function boot(): void
    {
        View::composer('*', function ($view) {
            $unreadNotifications = 0;
            $notifications = collect();

            if (Auth::check()) {
                $unreadNotifications = Auth::user()->unreadNotifications->count();
                $notifications = Auth::user()->notifications;
            }

            $view->with(compact('notifications', 'unreadNotifications'));
        });
    }
}

config/app.phpの編集

config/app.phpのproviders配列に、作成したViewServiceProviderを追加します。

  'providers' => [
        // Other Service Providers

        App\Providers\ViewServiceProvider::class,
    ],

コントローラーを作成

php artisan make:controller NotificationController

NotificationControllerを作成し、markAsReadメソッドを作成します。

app/Http/Controllers/NotificationController.php
    <?php

    namespace App\Http\Controllers;

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;

    class NotificationController extends Controller
    {
         public function markAsRead()
		    {
		        $user = Auth::user();
		        $user->unreadNotifications->markAsRead();
		        return redirect()->back();
		    }
    }

ルーティング作成

routes/web.phpへ既読機能に対応するルートを追加します。

web.php
/**
 * 通知を既読にする
 */
Route::controller(NotificationController::class)->group(function () {
    Route::get('/notifications/mark-as-read', 'markAsRead')->name('mark-as-read');
});

Readとしての通知作成

Viewに表示

Alpine.jsでベールマークをクリックしたら、通知がドロップダウンで表示されるようになっています。
レイアウトはTailwindcssで実装しました。

header_blade.php
<header class="fixed top-0 left-0 w-full z-30 bg-white shadow">
    <div class="flex justify-end flex-1 gap-x-4 self-stretch lg:gap-x-6">
        <div class="flex items-center gap-x-4 lg:gap-x-6">
            <!-- Notification dropdown -->
            <div x-data="{ notificationsOpen: false }">
                <button @click="notificationsOpen = !notificationsOpen" type="button" class="relative">
                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" />
                    </svg>
                    <!-- 未読通知数を表示 -->
                    <span class="absolute top-0.5 -right-6 inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-red-100 bg-red-600 rounded-full">
                        {{$unreadNotifications}}
                    </span>
                </button>

                <!-- 通知ドロップダウンメニュー -->
                <div x-show="notificationsOpen" @click.away="notificationsOpen = false" class="absolute mt-2 w-64 bg-gray-200 rounded-md shadow-lg overflow-hidden z-50 right-0 mr-10">
                    @if($unreadNotifications === 0)
                        <div>
                            <p>何もありません</p>
                        </div>
                    @else
                        <a href="{{ route('mark-as-read') }}" class="inline-block bg-gray-500 text-white font-bold m-2 py-1 px-1 rounded hover:bg-gray-700 focus:outline-none focus:shadow-outline">
                            全て既読にする
                        </a>
                        <div class="max-h-64 overflow-y-auto">
                            @foreach ($notifications as $notification)
                                <div class="d-flex flex-row align-items-center justify-content-center border-b-2 border-gray-700">
                                    <p class="font-bold p-3">
                                        {{ $notification->data['title'] }}
                                    </p>
                                    <p class="p-3">
                                        {{ $notification->data['content'] }}
                                    </p>
                                </div>
                            @endforeach
                        </div>
                    @endif
                </div>

                 <!-- Profile dropdown -->
            <div class="relative" x-data="{ open: false }">
                <button @click="open = !open" type="button" class="-m-1.5 flex items-center p-1.5" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
                    <span class="sr-only">ユーザーメニューを開く</span>
                     <span class="hidden lg:flex lg:items-center">
                        <span class="ml-4 text-sm font-semibold leading-6" aria-hidden="true">テスト</span>
                        <svg class="ml-2 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
                            <path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
                        </svg>
                    </span>
                </button>
            </div>
        </div>
    </div>
</header>

スクリーンショット 2024-03-24 15.29.21.png
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?