فصل ۱۰: پروژههای پیشرفته و مطالعات موردی¶
در این فصل پایانی، به بررسی پروژههای پیشرفته و مطالعات موردی برای استفاده از Laravel Reverb در نسخه ۱۲ لاراول میپردازیم. ابتدا نمونههایی از پروژههای واقعی مانند داشبورد تحلیلی real-time، ویرایش مشارکتی (collaborative editing)، و مزایدههای زنده (live auctions) را ارائه میدهیم. سپس نحوه انجام تستهای end-to-end برای WebSocketها با استفاده از ابزارهایی مانند Pest یا Laravel Dusk را توضیح میدهیم. در ادامه، به عیبیابی مشکلات رایج مانند قطعی اتصالات و مسائل مقیاسپذیری پرداخته و بهروزرسانیهای مربوط به لاراول ۱۲ را بررسی میکنیم. در نهایت، نگاهی به آینده Reverb و ادغام آن با ابزارهای جدید لاراول و روندهای real-time خواهیم داشت. تمام کدها بر اساس مستندات رسمی لاراول ۱۲.x تنظیم شدهاند.
مثالهای واقعی: داشبورد تحلیلی Real-Time، Collaborative Editing، و Live Auctions¶
۱. داشبورد تحلیلی Real-Time¶
یک داشبورد تحلیلی real-time دادههایی مانند معیارهای فروش، رفتار کاربران، یا معیارهای سرور را بهصورت زنده نمایش میدهد. در این مثال، یک داشبورد ساده برای نمایش تعداد کاربران آنلاین و فعالیتهای اخیر آنها پیادهسازی میکنیم.
تنظیم Backend¶
ایجاد یک Event برای ارسال معیارهای کاربران:
php artisan make:event AnalyticsUpdate
فایل app/Events/AnalyticsUpdate.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 AnalyticsUpdate implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $data;
public function __construct($data)
{
$this->data = $data;
}
public function broadcastOn()
{
return new Channel('analytics');
}
public function broadcastAs()
{
return 'analytics.updated';
}
}
ایجاد یک Job برای جمعآوری دادههای تحلیلی:
php artisan make:job CollectAnalytics
فایل app/Jobs/CollectAnalytics.php:
<?php
namespace App\Jobs;
use App\Events\AnalyticsUpdate;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CollectAnalytics implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle()
{
$data = [
'online_users' => User::where('last_activity', '>=', now()->subMinutes(5))->count(),
'recent_actions' => User::whereNotNull('last_action')->take(10)->get(['id', 'name', 'last_action'])->toArray(),
];
event(new AnalyticsUpdate($data));
}
}
تنظیم یک Command برای اجرای دورهای Job:
php artisan make:command RunAnalytics
فایل app/Console/Commands/RunAnalytics.php:
<?php
namespace App\Console\Commands;
use App\Jobs\CollectAnalytics;
use Illuminate\Console\Command;
class RunAnalytics extends Command
{
protected $signature = 'analytics:run';
protected $description = 'Collect and broadcast analytics data';
public function handle()
{
CollectAnalytics::dispatch();
$this->info('Analytics data collected and broadcasted.');
}
}
اضافه کردن Command به Scheduler در app/Console/Kernel.php:
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule)
{
$schedule->command('analytics:run')->everyMinute();
}
}
تنظیم Frontend (React)¶
ایجاد یک کامپوننت React برای نمایش داشبورد:
import React, { useState, useEffect } from 'react';
const AnalyticsDashboard = () => {
const [analytics, setAnalytics] = useState({ online_users: 0, recent_actions: [] });
useEffect(() => {
window.Echo.channel('analytics')
.listen('.analytics.updated', (e) => {
setAnalytics(e.data);
});
return () => {
window.Echo.leave('analytics');
};
}, []);
return (
<div>
<h1>Real-Time Analytics Dashboard</h1>
<p>Online Users: {analytics.online_users}</p>
<h2>Recent Actions</h2>
<ul>
{analytics.recent_actions.map(action => (
<li key={action.id}>{action.name}: {action.last_action}</li>
))}
</ul>
</div>
);
};
export default AnalyticsDashboard;
فایل resources/js/app.jsx:
import React from 'react';
import { createRoot } from 'react-dom/client';
import AnalyticsDashboard from './components/AnalyticsDashboard';
const root = createRoot(document.getElementById('app'));
root.render(<AnalyticsDashboard />);
فایل resources/views/analytics.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Analytics Dashboard</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
@vite(['resources/js/app.jsx'])
</head>
<body>
<div id="app"></div>
</body>
</html>
اجرا¶
- سرور Reverb را اجرا کنید:
php artisan reverb:start - سرور لاراول را اجرا کنید:
php artisan serve - Scheduler را اجرا کنید:
php artisan schedule:work - فایلهای جاوااسکریپت را کامپایل کنید:
npm run dev - به
http://localhost:8000بروید و داشبورد را مشاهده کنید.
۲. Collaborative Editing¶
یک سیستم ویرایش مشارکتی مانند Google Docs که کاربران میتوانند بهصورت real-time یک سند را ویرایش کنند.
تنظیم Backend¶
ایجاد مدل و migration برای سند:
php artisan make:model Document -m
فایل database/migrations/*_create_documents_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('documents', function (Blueprint $table) {
$table->id();
$table->text('content');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('documents');
}
};
ایجاد Event برای بهروزرسانی سند:
php artisan make:event DocumentUpdated
فایل app/Events/DocumentUpdated.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 DocumentUpdated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $documentId;
public $content;
public function __construct($documentId, $content)
{
$this->documentId = $documentId;
$this->content = $content;
}
public function broadcastOn()
{
return new PresenceChannel('document.' . $this->documentId);
}
public function broadcastAs()
{
return 'document.updated';
}
}
ایجاد Routeها در routes/web.php:
<?php
use App\Events\DocumentUpdated;
use App\Models\Document;
use Illuminate\Support\Facades\Route;
Route::get('/document/{id}', function ($id) {
$document = Document::findOrFail($id);
return view('document', compact('document'));
});
Route::middleware('auth')->post('/document/{id}/update', function ($id) {
$document = Document::findOrFail($id);
$content = request('content');
$document->update(['content' => $content]);
event(new DocumentUpdated($id, $content));
return response()->json(['status' => 'Document updated']);
});
تنظیم Frontend (Vue.js)¶
ایجاد کامپوننت Vue در resources/js/components/DocumentEditor.vue:
<template>
<div>
<h1>Collaborative Document Editor</h1>
<textarea v-model="content" @input="updateDocument" rows="10" cols="50"></textarea>
</div>
</template>
<script>
export default {
props: ['initialContent', 'documentId'],
data() {
return {
content: this.initialContent,
};
},
mounted() {
window.Echo.join(`document.${this.documentId}`)
.listen('.document.updated', (e) => {
if (e.content !== this.content) {
this.content = e.content;
}
});
},
methods: {
updateDocument() {
fetch(`/document/${this.documentId}/update`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({ content: this.content })
});
}
}
};
</script>
فایل resources/views/document.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Document Editor</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
@vite(['resources/js/app.js'])
</head>
<body>
<div id="app">
<document-editor :initial-content="{{ json_encode($document->content) }}" :document-id="{{ $document->id }}"></document-editor>
</div>
</body>
</html>
اجرا¶
- Migration را اجرا کنید:
php artisan migrate - یک سند نمونه ایجاد کنید:
php artisan tinker
\App\Models\Document::create(['content' => 'Initial content']);
- سرور Reverb و لاراول را اجرا کنید و به
/document/1بروید.
۳. Live Auctions¶
یک سیستم مزایده زنده که کاربران میتوانند پیشنهادات خود را بهصورت real-time ثبت کنند.
تنظیم Backend¶
ایجاد مدل و migration برای مزایده:
php artisan make:model Auction -m
فایل database/migrations/*_create_auctions_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('auctions', function (Blueprint $table) {
$table->id();
$table->string('item');
$table->decimal('current_bid', 8, 2)->default(0);
$table->unsignedBigInteger('highest_bidder_id')->nullable();
$table->timestamps();
$table->foreign('highest_bidder_id')->references('id')->on('users')->onDelete('set null');
});
}
public function down()
{
Schema::dropIfExists('auctions');
}
};
ایجاد Event برای بهروزرسانی مزایده:
php artisan make:event AuctionBid
فایل app/Events/AuctionBid.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 AuctionBid implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $auctionId;
public $bid;
public $bidder;
public function __construct($auctionId, $bid, $bidder)
{
$this->auctionId = $auctionId;
$this->bid = $bid;
$this->bidder = $bidder;
}
public function broadcastOn()
{
return new Channel('auction.' . $this->auctionId);
}
public function broadcastAs()
{
return 'auction.bid';
}
}
ایجاد Routeها در routes/web.php:
<?php
use App\Events\AuctionBid;
use App\Models\Auction;
use Illuminate\Support\Facades\Route;
Route::get('/auction/{id}', function ($id) {
$auction = Auction::findOrFail($id);
return view('auction', compact('auction'));
});
Route::middleware('auth')->post('/auction/{id}/bid', function ($id) {
$auction = Auction::findOrFail($id);
$bid = request('bid');
if ($bid > $auction->current_bid) {
$auction->update([
'current_bid' => $bid,
'highest_bidder_id' => auth()->id()
]);
event(new AuctionBid($id, $bid, auth()->user()));
return response()->json(['status' => 'Bid placed']);
}
return response()->json(['error' => 'Bid too low'], 422);
});
تنظیم Frontend (Vanilla JS)¶
فایل resources/views/auction.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Live Auction</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
@vite(['resources/js/app.js'])
</head>
<body>
<h1>Auction: {{ $auction->item }}</h1>
<p>Current Bid: $<span id="current-bid">{{ $auction->current_bid }}</span></p>
@auth
<form id="bid-form">
<input type="number" name="bid" step="0.01" min="{{ $auction->current_bid + 0.01 }}" required>
<button type="submit">Place Bid</button>
</form>
@endauth
<ul id="bid-history"></ul>
<script>
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
$('#bid-form').submit(function(e) {
e.preventDefault();
$.post('/auction/{{ $auction->id }}/bid', $(this).serialize(), () => {
$(this)[0].reset();
}).fail((xhr) => {
alert(xhr.responseJSON.error);
});
});
window.Echo.channel('auction.{{ $auction->id }}')
.listen('.auction.bid', (e) => {
$('#current-bid').text(e.bid);
$('#bid-history').prepend(`<li>${e.bidder.name} bid $${e.bid}</li>`);
});
</script>
</body>
</html>
اجرا¶
- Migration را اجرا کنید:
php artisan migrate - یک مزایده نمونه ایجاد کنید:
php artisan tinker
\App\Models\Auction::create(['item' => 'Antique Vase', 'current_bid' => 100]);
- سرور Reverb و لاراول را اجرا کنید و به
/auction/1بروید.
تست End-to-End با Pest یا Dusk برای WebSocketها¶
برای اطمینان از عملکرد صحیح اپلیکیشنهای real-time، باید تستهای end-to-end (E2E) انجام دهید. ابزارهای Pest و Laravel Dusk برای این منظور مناسب هستند.
تست با Pest¶
Pest یک فریمورک تست ساده و مدرن برای لاراول است. برای تست WebSocketها، میتوانید از یک کلاینت WebSocket مانند ratchet/pawl استفاده کنید.
- نصب وابستگیها:
composer require --dev pestphp/pest ratchet/pawl
php artisan pest:install
- ایجاد تست برای مزایده:
<?php
use App\Models\Auction;
use App\Models\User;
use Ratchet\Client\WebSocket;
use function Pest\Laravel\actingAs;
it('broadcasts auction bids', function () {
$user = User::factory()->create();
$auction = Auction::factory()->create(['current_bid' => 100]);
$received = false;
$message = null;
\Ratchet\Client\connect('ws://localhost:8080/app/my-reverb-app?protocol=7&client=js')->then(function (WebSocket $conn) use ($auction, &$received, &$message) {
$conn->send(json_encode([
'command' => 'subscribe',
'identifier' => json_encode(['channel' => 'auction.' . $auction->id])
]));
$conn->on('message', function ($msg) use (&$received, &$message) {
$data = json_decode($msg, true);
if (isset($data['message']['data'])) {
$message = json_decode($data['message']['data'], true);
$received = true;
}
});
});
actingAs($user)->post('/auction/' . $auction->id . '/bid', ['bid' => 150]);
// انتظار برای دریافت پیام
$timeout = now()->addSeconds(5);
while (!$received && now()->lessThan($timeout)) {
usleep(100000); // 100ms صبر
}
expect($received)->toBeTrue();
expect($message['bid'])->toBe(150);
expect($message['bidder']['id'])->toBe($user->id);
});
- اجرای تست:
php artisan reverb:start
php artisan serve
php artisan test
تست با Laravel Dusk¶
Dusk برای تستهای مرورگر مناسب است و میتواند تعاملات WebSocket را در یک محیط واقعی تست کند.
- نصب Dusk:
composer require --dev laravel/dusk
php artisan dusk:install
- ایجاد تست Dusk:
<?php
namespace Tests\Browser;
use App\Models\Auction;
use App\Models\User;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class AuctionDuskTest extends DuskTestCase
{
public function testAuctionBidUpdates()
{
$user = User::factory()->create();
$auction = Auction::factory()->create(['current_bid' => 100]);
$this->browse(function (Browser $browser) use ($user, $auction) {
$browser->loginAs($user)
->visit('/auction/' . $auction->id)
->type('bid', '150')
->press('Place Bid')
->waitForText('$150', 5)
->assertSeeIn('#bid-history', 'bid $150');
});
}
}
- اجرای تست:
php artisan reverb:start
php artisan serve
php artisan dusk
عیبیابی رایج (Disconnections, Scaling Issues) و بهروزرسانیها در Laravel 12¶
مشکلات رایج و راهحلها¶
-
قطعی اتصالات (Disconnections):
-
علت: مشکلات شبکه، تنظیمات نادرست reverse proxy، یا محدودیتهای سرور.
-
راهحل:
- بررسی لاگهای Reverb با
--debug:sh php artisan reverb:start --debug - اطمینان از تنظیم صحیح هدرهای
UpgradeوConnectionدر reverse proxy. - استفاده از keep-alive برای WebSocketها در فایل
config/reverb.php:php 'ping_interval' => 30, // ارسال ping هر 30 ثانیه 'ping_timeout' => 120, // قطع اتصال پس از 120 ثانیه
- بررسی لاگهای Reverb با
-
مشکلات مقیاسپذیری:
-
علت: تعداد زیاد اتصالات یا پیامها که سرور را تحت فشار قرار میدهد.
-
راهحل:
- استفاده از Redis برای توزیع پیامها (فصل نهم).
- فعالسازی
ext-uvبرای event loop (فصل هشتم). - تنظیم
capacityدرconfig/reverb.phpبرای محدود کردن اتصالات.
-
مشکلات احراز هویت:
-
علت: خطا در تنظیمات Sanctum یا Passport.
- راهحل:
- بررسی endpoint
/broadcasting/authبا:sh curl -H "Authorization: Bearer <token>" http://localhost:8000/broadcasting/auth - اطمینان از فعال بودن middleware
EnsureFrontendRequestsAreStateful.
- بررسی endpoint
بهروزرسانیهای Laravel 12¶
- Reverb بهعنوان درایور پیشفرض: در لاراول ۱۲، Reverb بهعنوان درایور broadcasting پیشفرض معرفی شده و جایگزین Pusher برای بسیاری از پروژهها شده است.
- پشتیبانی از ext-uv: بهبود عملکرد event loop با استفاده از libuv.
- ادغام با Pulse: امکان نظارت پیشرفتهتر بر اتصالات و پیامها.
- بهبودهای امنیتی: اضافه شدن قابلیتهای rate limiting و رمزنگاری داخلی برای پیامها.
آینده Reverb: ادغام با ابزارهای جدید لاراول و روندهای Real-Time¶
ادغام با ابزارهای جدید لاراول¶
- Laravel Pulse: انتظار میرود در نسخههای آینده، Pulse کارتهای سفارشی بیشتری برای Reverb ارائه دهد، مانند تجزیهوتحلیل الگوهای پیام یا مصرف منابع.
- Laravel Octane: ادغام عمیقتر Reverb با Octane برای بهبود عملکرد در اپلیکیشنهای real-time با استفاده از سرورهای Swoole یا RoadRunner.
- Laravel Scout: امکان استفاده از Reverb برای بهروزرسانیهای real-time در نتایج جستجو.
روندهای Real-Time¶
- WebSocketهای مقیاسپذیر: با افزایش استفاده از اپلیکیشنهای real-time (مانند چت، گیمینگ، و IoT)، Reverb احتمالاً قابلیتهای بیشتری برای مقیاسپذیری افقی ارائه خواهد داد.
- پشتیبانی از پروتکلهای جدید: ممکن است Reverb در آینده از پروتکلهای پیشرفتهتر مانند WebTransport پشتیبانی کند.
- ادغام با AI: استفاده از Reverb برای اپلیکیشنهای مبتنی بر AI که نیاز به پاسخهای real-time دارند، مانند چتباتهای هوشمند یا تحلیل دادههای زنده.
نکات و بهترین شیوهها¶
- تست جامع: همیشه تستهای E2E را برای سناریوهای real-time اجرا کنید تا از عملکرد صحیح اطمینان حاصل کنید.
- مانیتورینگ: از Pulse و ابزارهای خارجی مانند Prometheus برای نظارت مداوم استفاده کنید.
- بهروزرسانی مداوم: مستندات لاراول را برای ویژگیهای جدید Reverb بررسی کنید.
تمرین پیشنهادی¶
- یک سیستم اعلان برای داشبورد تحلیلی اضافه کنید که تغییرات مهم (مانند افزایش ناگهانی کاربران آنلاین) را گزارش دهد.
- قابلیت undo/redo را به ویرایشگر مشارکتی اضافه کنید.
- یک تست Dusk برای مزایده زنده بنویسید که چندین پیشنهاد را بهصورت همزمان تست کند.
منابع¶
- مستندات رسمی لاراول:
laravel.com/docs/12.x/reverb - مستندات Pulse:
laravel.com/docs/12.x/pulse - مستندات Pest:
pestphp.com - مستندات Dusk:
laravel.com/docs/12.x/dusk
این فصل پایان مجموعه آموزشی Laravel Reverb است. شما اکنون ابزارها و دانش لازم برای ساخت، تست، و استقرار اپلیکیشنهای real-time پیشرفته را دارید!