فصل ۶: ادغام با Frontend و Laravel Echo

در این فصل، به بررسی نحوه ادغام Laravel Reverb با frontend از طریق Laravel Echo می‌پردازیم. ابتدا مراحل نصب و تنظیم Laravel Echo برای جاوااسکریپت (با پشتیبانی از Vue.js، React یا vanilla JS) را توضیح می‌دهیم، سپس نحوه اتصال به سرور Reverb از طریق WebSocket و مدیریت به‌روزرسانی‌های real-time (مانند live search یا dashboard updates) را بررسی می‌کنیم. در نهایت، یک پروژه عملی برای ساخت یک اپلیکیشن چت real-time ارائه می‌دهیم. تمام کدها و مثال‌ها بر اساس مستندات رسمی لاراول نسخه ۱۲.x تنظیم شده‌اند.

نصب و تنظیم Laravel Echo برای JavaScript

Laravel Echo یک کتابخانه جاوااسکریپت است که مدیریت اتصالات WebSocket و subscription به کانال‌های Reverb را ساده می‌کند. این کتابخانه با فریم‌ورک‌هایی مانند Vue.js، React یا vanilla JavaScript سازگار است.

نصب Laravel Echo

  1. پکیج‌های موردنیاز را نصب کنید:
   npm install --save laravel-echo @reverbjs/laravel-echo
  • laravel-echo: کتابخانه اصلی برای مدیریت WebSocket.
  • @reverbjs/laravel-echo: درایور خاص برای اتصال به سرور Reverb.

  • اگر از Vue.js یا React استفاده می‌کنید، پکیج‌های مربوطه را نصب کنید:

   npm install --save vue   # برای Vue.js
   npm install --save react react-dom   # برای React
  1. فایل resources/js/bootstrap.js را برای تنظیم Echo به‌روزرسانی کنید:
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')}` // برای احراز هویت
        }
    }
});
  • host: آدرس سرور Reverb (در تولید، به دامنه اصلی تغییر دهید، مانند example.com).
  • authEndpoint: endpoint برای احراز هویت کانال‌های خصوصی و حضور.
  • auth.headers: برای ارسال توکن‌های احراز هویت (در صورت استفاده از Sanctum یا Passport).

  • فایل‌های جاوااسکریپت را کامپایل کنید:

   npm run dev

تنظیم برای Vue.js یا React

  • Vue.js: اگر از Vue استفاده می‌کنید، Echo را در فایل resources/js/app.js به اپلیکیشن Vue اضافه کنید:
import { createApp } from 'vue';
import App from './components/App.vue';

const app = createApp(App);
app.mount('#app');
  • React: برای React، Echo را در فایل اصلی (مانند resources/js/app.jsx) وارد کنید:
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './components/App';

const root = createRoot(document.getElementById('app'));
root.render(<App />);

اتصال به Reverb از طریق WebSocket در Frontend

برای اتصال به سرور Reverb، Laravel Echo به‌صورت خودکار از پروتکل WebSocket استفاده می‌کند. شما می‌توانید با استفاده از متدهای channel, private, یا join به کانال‌های عمومی، خصوصی، یا حضور subscribe کنید.

مثال: اتصال به یک کانال عمومی

در فایل resources/js/app.js:

window.Echo.channel('public-channel')
    .listen('.message.sent', (e) => {
        console.log('Received:', e.message);
    });

اتصال به کانال خصوصی یا حضور

برای کانال‌های خصوصی یا حضور، باید احراز هویت تنظیم شده باشد (مانند Laravel Sanctum). مثال:

window.Echo.private('user.1')
    .listen('.private.message', (e) => {
        console.log('Private message:', e.message);
    });

window.Echo.join('chat-room')
    .here((users) => {
        console.log('Online users:', users);
    })
    .joining((user) => {
        console.log('User joined:', user);
    })
    .leaving((user) => {
        console.log('User left:', user);
    });

مدیریت Real-Time Updates

به‌روزرسانی‌های real-time، مانند live search یا dashboard updates، با استفاده از Reverb و Echo به‌سادگی پیاده‌سازی می‌شوند. در این بخش، یک مثال ساده از یک live search پیاده‌سازی می‌کنیم که نتایج را به‌صورت real-time نمایش می‌دهد.

  1. ایجاد Event:
   php artisan make:event SearchResult

فایل app/Events/SearchResult.php:

<?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 SearchResult implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $results;

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

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

    public function broadcastAs()
    {
        return 'search.result';
    }
}
  1. ایجاد Route: فایل routes/web.php:
<?php

use App\Events\SearchResult;
use App\Models\User;
use Illuminate\Support\Facades\Route;

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

Route::post('/search', function () {
    $query = request('query');
    $results = User::where('name', 'like', "%{$query}%")->get(['id', 'name'])->toArray();
    event(new SearchResult($results));
    return response()->json($results);
});
  1. تنظیم Frontend (Vanilla JS): فایل resources/views/welcome.blade.php:
<!DOCTYPE html>
<html>
<head>
    <title>Live Search</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>Live Search with Reverb</h1>
    <input type="text" id="search" placeholder="Search users...">
    <ul id="results"></ul>

    <script>
        $('#search').on('input', function() {
            $.post('/search', { query: $(this).val() });
        });

        window.Echo.channel('search-results')
            .listen('.search.result', (e) => {
                $('#results').empty();
                e.results.forEach(result => {
                    $('#results').append(`<li>${result.name} (ID: ${result.id})</li>`);
                });
            });
    </script>
</body>
</html>
  1. اجرا:
  2. سرور Reverb را اجرا کنید: php artisan reverb:start
  3. سرور لاراول را اجرا کنید: php artisan serve
  4. فایل‌های جاوااسکریپت را کامپایل کنید: npm run dev
  5. به http://localhost:8000 بروید و در ورودی جستجو تایپ کنید تا نتایج به‌صورت real-time نمایش داده شوند.

مثال پروژه: ساخت یک اپ چت Real-Time

در این پروژه، یک اپلیکیشن چت real-time با استفاده از کانال‌های حضور (presence channels) پیاده‌سازی می‌کنیم. این اپلیکیشن امکان ارسال پیام، نمایش کاربران آنلاین، و اطلاع‌رسانی ورود/خروج کاربران را فراهم می‌کند.

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

  1. یک مدل Message ایجاد کنید:
   php artisan make:model Message -m
  1. فایل migration (database/migrations/*_create_messages_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('messages', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->text('content');
            $table->timestamps();

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

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

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

php artisan make:event ChatMessage

فایل app/Events/ChatMessage.php:

<?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 ChatMessage implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;
    public $user;

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

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

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

    public function broadcastWith()
    {
        return [
            'message' => $this->message,
            'user' => ['id' => $this->user->id, 'name' => $this->user->name],
            'timestamp' => now()->toDateTimeString()
        ];
    }
}

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

فایل routes/channels.php برای کانال حضور:

<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('chat-room', function ($user) {
    return ['id' => $user->id, 'name' => $user->name];
});

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

فایل routes/web.php:

<?php

use App\Events\ChatMessage;
use App\Models\Message;
use Illuminate\Support\Facades\Route;

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

Route::middleware('auth')->post('/send-message', function () {
    $message = request('message');
    Message::create([
        'user_id' => auth()->id(),
        'content' => $message
    ]);
    event(new ChatMessage($message, auth()->user()));
    return response()->json(['status' => 'Message sent']);
});

مرحله ۵: تنظیم Frontend (Vue.js)

  1. یک کامپوننت Vue ایجاد کنید (resources/js/components/Chat.vue):
<template>
    <div>
        <h1>Real-Time Chat</h1>
        <div v-if="!isAuthenticated">
            <p>Please log in to join the chat.</p>
        </div>
        <div v-else>
            <form @submit.prevent="sendMessage">
                <input v-model="newMessage" placeholder="Type a message..." required>
                <button type="submit">Send</button>
            </form>
            <h2>Messages</h2>
            <ul>
                <li v-for="msg in messages" :key="msg.timestamp">
                    {{ msg.user.name }}: {{ msg.message }} ({{ msg.timestamp }})
                </li>
            </ul>
            <h2>Online Users</h2>
            <ul>
                <li v-for="user in onlineUsers" :key="user.id">
                    {{ user.name }} (ID: {{ user.id }})
                </li>
            </ul>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            newMessage: '',
            messages: [],
            onlineUsers: [],
            isAuthenticated: !!localStorage.getItem('token')
        };
    },
    mounted() {
        if (this.isAuthenticated) {
            this.joinChat();
        }
    },
    methods: {
        joinChat() {
            window.Echo.join('chat-room')
                .here((users) => {
                    this.onlineUsers = users;
                })
                .joining((user) => {
                    this.onlineUsers.push(user);
                    this.messages.push({
                        message: `${user.name} joined the chat`,
                        user: { name: 'System' },
                        timestamp: new Date().toLocaleString()
                    });
                })
                .leaving((user) => {
                    this.onlineUsers = this.onlineUsers.filter(u => u.id !== user.id);
                    this.messages.push({
                        message: `${user.name} left the chat`,
                        user: { name: 'System' },
                        timestamp: new Date().toLocaleString()
                    });
                })
                .listen('.chat.message', (e) => {
                    this.messages.push(e);
                });
        },
        sendMessage() {
            fetch('/send-message', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${localStorage.getItem('token')}`
                },
                body: JSON.stringify({ message: this.newMessage })
            });
            this.newMessage = '';
        }
    }
};
</script>
  1. فایل resources/js/app.js:
import { createApp } from 'vue';
import Chat from './components/Chat.vue';

const app = createApp(Chat);
app.mount('#app');
  1. فایل resources/views/chat.blade.php:
<!DOCTYPE html>
<html>
<head>
    <title>Real-Time Chat</title>
    @vite(['resources/js/app.js'])
</head>
<body>
    <div id="app"></div>
</body>
</html>

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

  1. نصب Laravel Sanctum:
   composer require laravel/sanctum
   php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
   php artisan migrate
  1. فایل app/Http/Kernel.php را به‌روزرسانی کنید:
   protected $middlewareGroups = [
       'web' => [
           // ...
           \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
       ],
   ];
  1. یک سیستم لاگین ساده (مانند Laravel Breeze) اضافه کنید یا یک کاربر تست ایجاد کنید:
   php artisan tinker
   \App\Models\User::create(['name' => 'Test User', 'email' => 'test@example.com', 'password' => bcrypt('password')]);

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

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

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

  • کاربران آنلاین در لیست نمایش داده می‌شوند.
  • پیام‌های ارسالی به‌صورت real-time در چت ظاهر می‌شوند.
  • ورود و خروج کاربران به‌صورت اعلان در چت نشان داده می‌شود.

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

  • مدیریت خطا: خطاهای WebSocket را با window.Echo.connector.socket.on('error') مدیریت کنید.
  • بهینه‌سازی: برای اپلیکیشن‌های بزرگ، از Redis برای queue استفاده کنید: env BROADCAST_QUEUE_CONNECTION=redis
  • امنیت: اطمینان حاصل کنید که توکن‌های احراز هویت به‌درستی مدیریت می‌شوند.

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

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

منابع

  • مستندات رسمی لاراول: laravel.com/docs/12.x/echo
  • مستندات Reverb: laravel.com/docs/12.x/reverb
  • مستندات Vue.js: vuejs.org

در فصل بعدی، به امنیت و بهترین شیوه‌ها برای محافظت از اپلیکیشن‌های Reverb خواهیم پرداخت.