فصل ۵: کار با کانال‌ها و Subscriptions

در این فصل، به بررسی انواع کانال‌های موجود در Laravel Reverb (عمومی، خصوصی، و حضور) و نحوه مدیریت subscriptions در سمت کلاینت با استفاده از Laravel Echo می‌پردازیم. همچنین، احراز هویت کانال‌ها با Laravel Sanctum یا Passport را توضیح داده و یک مثال عملی برای ایجاد یک سیستم نوتیفیکیشن ساده ارائه می‌کنیم. این فصل بر اساس مستندات رسمی لاراول نسخه ۱۲.x تنظیم شده و شامل کدهای عملی و نکات کاربردی است.

انواع کانال‌ها: Public, Private, Presence

لاراول سه نوع کانال برای broadcasting ارائه می‌دهد که هر کدام برای سناریوهای خاصی مناسب هستند:

  1. Public Channels:

  2. برای همه کلاینت‌های متصل قابل دسترسی هستند.

  3. نیازی به احراز هویت ندارند.
  4. مناسب برای داده‌های عمومی مانند اعلانات، به‌روزرسانی‌های خبری یا داشبوردهای عمومی.
  5. مثال: Channel('public-notifications')

  6. Private Channels:

  7. فقط کاربران احراز هویت‌شده می‌توانند به آن‌ها subscribe کنند.

  8. برای داده‌های حساس مانند پیام‌های خصوصی یا نوتیفیکیشن‌های شخصی مناسب هستند.
  9. مثال: PrivateChannel('user.1')

  10. Presence Channels:

  11. علاوه بر احراز هویت، اطلاعات کاربران حاضر در کانال (مانند لیست کاربران آنلاین) را ارائه می‌دهند.

  12. برای اپلیکیشن‌هایی مانند چت گروهی یا همکاری real-time مناسب هستند.
  13. مثال: PresenceChannel('chat-room')

تفاوت‌ها در عمل

  • Public: بدون نیاز به تنظیمات اضافی، هر کلاینتی می‌تواند به کانال متصل شود.
  • Private: نیاز به تعریف منطق احراز هویت در فایل routes/channels.php دارد.
  • Presence: علاوه بر احراز هویت، اطلاعاتی مانند user_id و user_info را برای کلاینت‌های متصل فراهم می‌کند.

احراز هویت کانال‌ها با Laravel Sanctum یا Passport

برای کانال‌های خصوصی و حضور، لاراول از مکانیزم احراز هویت استفاده می‌کند تا اطمینان حاصل شود که فقط کاربران مجاز به کانال دسترسی دارند. این احراز هویت معمولاً با Laravel Sanctum (برای برنامه‌های SPA) یا Laravel Passport (برای APIهای پیچیده‌تر) انجام می‌شود.

استفاده از Laravel Sanctum

Sanctum یک راه‌حل سبک برای احراز هویت API و SPA است که به‌خوبی با Reverb کار می‌کند.

  1. نصب Sanctum:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
  1. تنظیم middleware:

فایل app/Http/Kernel.php را به‌روزرسانی کنید تا middleware مربوط به Sanctum اضافه شود:

protected $middlewareGroups = [
    'web' => [
        // ...
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
    ],
];
  1. تنظیم احراز هویت کانال‌ها:

فایل routes/channels.php را برای احراز هویت کانال‌های خصوصی و حضور ویرایش کنید:

<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('user.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

Broadcast::channel('chat-room', function ($user) {
    return ['id' => $user->id, 'name' => $user->name];
});
  • user.{id}: کانال خصوصی که فقط کاربر با id مشخص‌شده می‌تواند به آن subscribe کند.
  • chat-room: کانال حضور که اطلاعات کاربر (مانند id و name) را برای کلاینت‌های دیگر در کانال فراهم می‌کند.

استفاده از Laravel Passport

برای پروژه‌های پیچیده‌تر که نیاز به OAuth2 دارند، می‌توانید از Passport استفاده کنید: 1. نصب Passport:

composer require laravel/passport
php artisan migrate
php artisan passport:install
  1. تنظیمات مشابه Sanctum را در routes/channels.php اعمال کنید، اما از توکن‌های Passport برای احراز هویت استفاده کنید.

نکته: برای اکثر برنامه‌های SPA، Sanctum به دلیل سادگی و ادغام آسان‌تر با Reverb توصیه می‌شود.

مدیریت Subscriptions در سمت کلاینت (با Laravel Echo)

Laravel Echo یک کتابخانه جاوااسکریپت است که مدیریت اتصالات WebSocket و subscriptions به کانال‌ها را در سمت کلاینت ساده می‌کند. Echo با Reverb ادغام شده و به شما امکان می‌دهد به‌راحتی به کانال‌های عمومی، خصوصی، و حضور subscribe کنید.

نصب و تنظیم Laravel Echo

  1. نصب پکیج‌های موردنیاز:
npm install --save laravel-echo @reverbjs/laravel-echo
  1. تنظیم Echo در فایل resources/js/bootstrap.js:
import Echo from 'laravel-echo';
import * as Reverb from '@reverbjs/laravel-echo';

window.Echo = new Echo({
    broadcaster: Reverb.Reverb,
    host: 'localhost:8080',
    authEndpoint: '/broadcasting/auth',
    appId: 'my-reverb-app',
    key: 'your-app-key',
    auth: {
        headers: {
            Authorization: `Bearer ${localStorage.getItem('token')}`
        }
    }
});
  • authEndpoint: آدرس endpoint برای احراز هویت کانال‌های خصوصی (به‌صورت پیش‌فرض /broadcasting/auth).
  • auth.headers: برای ارسال توکن‌های احراز هویت (در صورت استفاده از Sanctum یا Passport).

مدیریت Subscription در کلاینت

Echo سه متد اصلی برای subscription به کانال‌ها ارائه می‌دهد:

  • channel('name'): برای کانال‌های عمومی.
  • private('name'): برای کانال‌های خصوصی.
  • join('name'): برای کانال‌های حضور.

مثال: Subscription به کانال‌ها

// کانال عمومی
window.Echo.channel('public-notifications')
    .listen('.notification.sent', (e) => {
        console.log('Public notification:', e.message);
    });

// کانال خصوصی
window.Echo.private('user.1')
    .listen('.private.notification', (e) => {
        console.log('Private notification:', e.message);
    });

// کانال حضور
window.Echo.join('chat-room')
    .here((users) => {
        console.log('Users in room:', users);
    })
    .joining((user) => {
        console.log('User joined:', user);
    })
    .leaving((user) => {
        console.log('User left:', user);
    })
    .listen('.chat.message', (e) => {
        console.log('Chat message:', e.message);
    });
  • here: لیست کاربران حاضر در کانال حضور هنگام اتصال.
  • joining/leaving: اطلاع‌رسانی هنگام ورود یا خروج کاربران.
  • listen: گوش دادن به رویدادهای خاص در کانال.

مثال عملی: ایجاد یک سیستم نوتیفیکیشن ساده

در این مثال، یک سیستم نوتیفیکیشن ساده پیاده‌سازی می‌کنیم که شامل کانال‌های عمومی، خصوصی، و حضور است. کاربران می‌توانند نوتیفیکیشن‌های عمومی (مانند اعلانات سیستم) و خصوصی (مانند پیام‌های شخصی) دریافت کنند و در یک کانال حضور، لیست کاربران آنلاین را مشاهده کنند.

مرحله ۱: تنظیم مدل و احراز هویت

  1. یک مدل Notification ایجاد کنید:
php artisan make:model Notification -m
  1. فایل migration (database/migrations/*_create_notifications_table.php) را ویرایش کنید:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('notifications', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->string('message');
            $table->string('type')->default('public');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });
    }

    public function down()
    {
        Schema::dropIfExists('notifications');
    }
};
  1. Migration را اجرا کنید:
php artisan migrate

مرحله ۲: ایجاد Events

سه event برای کانال‌های مختلف ایجاد کنید:

php artisan make:event PublicNotification
php artisan make:event PrivateNotification
php artisan make:event PresenceNotification
  1. PublicNotification:
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PublicNotification implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;

    public function __construct($message)
    {
        $this->message = $message;
    }

    public function broadcastOn()
    {
        return new Channel('public-notifications');
    }

    public function broadcastAs()
    {
        return 'notification.sent';
    }
}
  1. PrivateNotification:
<?php

namespace App\Events;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PrivateNotification implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;
    public $userId;

    public function __construct($message, $userId)
    {
        $this->message = $message;
        $this->userId = $userId;
    }

    public function broadcastOn()
    {
        return new PrivateChannel('user.' . $this->userId);
    }

    public function broadcastAs()
    {
        return 'private.notification';
    }
}
  1. PresenceNotification:
<?php

namespace App\Events;

use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PresenceNotification implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;

    public function __construct($message)
    {
        $this->message = $message;
    }

    public function broadcastOn()
    {
        return new PresenceChannel('chat-room');
    }

    public function broadcastAs()
    {
        return 'chat.message';
    }
}

مرحله ۳: ایجاد Route‌ها

فایل routes/web.php را برای ارسال نوتیفیکیشن‌ها به‌روزرسانی کنید:

<?php

use App\Events\PublicNotification;
use App\Events\PrivateNotification;
use App\Events\PresenceNotification;
use App\Models\Notification;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::post('/send-public', function () {
    $message = request('message', 'New public notification!');
    Notification::create(['user_id' => 0, 'message' => $message, 'type' => 'public']);
    event(new PublicNotification($message));
    return 'Public notification sent!';
});

Route::middleware('auth')->post('/send-private', function () {
    $message = request('message', 'New private notification!');
    Notification::create(['user_id' => auth()->id(), 'message' => $message, 'type' => 'private']);
    event(new PrivateNotification($message, auth()->id()));
    return 'Private notification sent!';
});

Route::middleware('auth')->post('/send-presence', function () {
    $message = request('message', 'New chat message!');
    Notification::create(['user_id' => auth()->id(), 'message' => $message, 'type' => 'presence']);
    event(new PresenceNotification($message));
    return 'Presence notification sent!';
});

مرحله ۴: تنظیم Frontend

فایل resources/views/welcome.blade.php را برای نمایش و ارسال نوتیفیکیشن‌ها تنظیم کنید:

<!DOCTYPE html>
<html>
<head>
    <title>Reverb Notification System</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    @vite(['resources/js/app.js'])
</head>
<body>
    <h1>Notification System</h1>

    <h2>Send Notifications</h2>
    <form id="public-form">
        <input type="text" name="message" placeholder="Public message">
        <button type="submit">Send Public</button>
    </form>
    @auth
        <form id="private-form">
            <input type="text" name="message" placeholder="Private message">
            <button type="submit">Send Private</button>
        </form>
        <form id="presence-form">
            <input type="text" name="message" placeholder="Chat message">
            <button type="submit">Send Chat</button>
        </form>
    @endauth

    <h2>Notifications</h2>
    <ul id="notifications"></ul>

    <h2>Online Users</h2>
    <ul id="online-users"></ul>

    <script>
        // Public channel
        window.Echo.channel('public-notifications')
            .listen('.notification.sent', (e) => {
                $('#notifications').append(`<li>Public: ${e.message}</li>`);
            });

        // Private channel
        @auth
            window.Echo.private('user.{{ auth()->id() }}')
                .listen('.private.notification', (e) => {
                    $('#notifications').append(`<li>Private: ${e.message}</li>`);
                });
        @endauth

        // Presence channel
        @auth
            window.Echo.join('chat-room')
                .here((users) => {
                    $('#online-users').empty();
                    users.forEach(user => {
                        $('#online-users').append(`<li>${user.name} (ID: ${user.id})</li>`);
                    });
                })
                .joining((user) => {
                    $('#notifications').append(`<li>${user.name} joined the chat!</li>`);
                    $('#online-users').append(`<li>${user.name} (ID: ${user.id})</li>`);
                })
                .leaving((user) => {
                    $('#notifications').append(`<li>${user.name} left the chat!</li>`);
                    $('#online-users').find(`li:contains('(ID: ${user.id})')`).remove();
                })
                .listen('.chat.message', (e) => {
                    $('#notifications').append(`<li>Chat: ${e.message}</li>`);
                });
        @endauth

        // Form submissions
        $('#public-form').submit(function(e) {
            e.preventDefault();
            $.post('/send-public', $(this).serialize());
        });

        $('#private-form').submit(function(e) {
            e.preventDefault();
            $.post('/send-private', $(this).serialize());
        });

        $('#presence-form').submit(function(e) {
            e.preventDefault();
            $.post('/send-presence', $(this).serialize());
        });
    </script>
</body>
</html>

مرحله ۵: تنظیم احراز هویت

  1. اطمینان حاصل کنید که Sanctum نصب و تنظیم شده است (مراحل در بخش احراز هویت توضیح داده شد).
  2. یک کاربر تست ایجاد کنید:
php artisan tinker
\App\Models\User::create(['name' => 'Test User', 'email' => 'test@example.com', 'password' => bcrypt('password')]);
  1. برای ورود کاربران، یک سیستم لاگین ساده اضافه کنید (یا از Laravel Breeze استفاده کنید).

مرحله ۶: اجرای پروژه

  1. سرور Reverb را اجرا کنید:
php artisan reverb:start
  1. سرور لاراول را اجرا کنید:
php artisan serve
  1. فایل‌های جاوااسکریپت را کامپایل کنید:
npm run dev
  1. به http://localhost:8000 بروید، وارد سیستم شوید، و فرم‌ها را برای ارسال نوتیفیکیشن‌های عمومی، خصوصی، و حضور تست کنید.

خروجی مورد انتظار

  • Public Notifications: پیام‌های عمومی در لیست <ul id="notifications"> برای همه کاربران نمایش داده می‌شوند.
  • Private Notifications: فقط کاربر احراز هویت‌شده پیام‌های خصوصی خود را می‌بیند.
  • Presence Notifications: لیست کاربران آنلاین در <ul id="online-users"> نمایش داده می‌شود و ورود/خروج کاربران به‌صورت real-time نشان داده می‌شود.

نکات و بهترین شیوه‌ها

  • امنیت: همیشه از کانال‌های خصوصی یا حضور برای داده‌های حساس استفاده کنید.
  • مدیریت خطا: در سمت کلاینت، خطاهای اتصال WebSocket را با ws.onerror مدیریت کنید.
  • بهینه‌سازی: برای اپلیکیشن‌های با تعداد کاربران بالا، از queue (مانند Redis) برای broadcasting استفاده کنید:
BROADCAST_QUEUE_CONNECTION=redis

تمرین پیشنهادی

  • یک دکمه برای پاک کردن لیست نوتیفیکیشن‌ها اضافه کنید.
  • یک فیلتر به صفحه اضافه کنید تا کاربران بتوانند فقط نوتیفیکیشن‌های عمومی یا خصوصی را مشاهده کنند.
  • یک سیستم امتیازدهی به نوتیفیکیشن‌ها اضافه کنید که در دیتابیس ذخیره شود.

منابع

  • مستندات رسمی لاراول: laravel.com/docs/12.x/broadcasting
  • مستندات Laravel Echo: laravel.com/docs/12.x/echo
  • مستندات Sanctum: laravel.com/docs/12.x/sanctum

در فصل بعدی، به ادغام عمیق‌تر با frontend و استفاده پیشرفته‌تر از Laravel Echo خواهیم پرداخت.