📍 الدليل المرجعي: Laravel 11 وPHP 8.4
كلّ عملية طويلة — إرسال بريد، تغيير حجم صورة، استدعاء API خارجي، توليد PDF — يجب ألّا تحجب ردّ HTTP. إجبار المستخدم على الانتظار 10 ثوانٍ بينما يُرسَل بريد تجربة سيّئة؛ جعل الخادم ينتظر ليبقى دورة الطلب/الردّ سريعة معمارية جيّدة. queues في Laravel تحلّ هذه المشكلة: تُؤجِّل العمل إلى workers غير متزامنة تُعالج المهامّ في الخلفية، دون أثر على زمن الاستجابة. يبني هذا الدليل نظام معالجة غير متزامنة كاملًا، من تهيئة driver حتى الإشراف في الإنتاج بـ Supervisor.
المتطلّبات
- مشروع Laravel 11.
- قاعدة بيانات مُهَيَّأة لـ driver
database(بديل: Redis لـ driverredis). - الوقت: 45 إلى 60 دقيقة.
الخطوة 1 — اختيار وتهيئة driver queue
Laravel 11 يدعم ستّة drivers: database، redis، sqs (Amazon SQS)، beanstalkd، sync، وnull. لانطلاق دون تبعيّات، database الأكثر يسرًا. للإنتاج بحجم عالٍ، redis موصى به.
# Driver database (التطوير والإنتاج الخفيف)
QUEUE_CONNECTION=database
# Driver Redis (إنتاج بحجم عالٍ — يتطلّب php8.4-redis وخادم Redis)
# QUEUE_CONNECTION=redis
# REDIS_HOST=127.0.0.1
# REDIS_PORT=6379
# أنشئ migration لجدول jobs
php artisan make:queue-table
# نَفِّذ migrations
php artisan migrate
المتوقّع: create_jobs_table .... DONE وcreate_failed_jobs_table .... DONE. جدول jobs يُخزّن jobs في الانتظار. جدول failed_jobs يحفظ jobs المُستنفِدة لمحاولاتها.
الخطوة 2 — إنشاء أوّل Job
php artisan make:job SendWelcomeEmail
<?php
namespace App\Jobs;
use App\Models\User;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail implements ShouldQueue
{
use Queueable;
// الحدّ الأقصى للمحاولات
public int $tries = 3;
// timeout بالثواني
public int $timeout = 30;
// انتظار أسّي بين المحاولات (1د، 5د، 10د)
public function backoff(): array
{
return [60, 300, 600];
}
public function __construct(
public readonly User $user,
) {}
public function handle(): void
{
Mail::to($this->user->email)->send(new WelcomeEmail($this->user));
}
// يُستدعى حين تُستنفَد كلّ المحاولات
public function failed(Throwable $exception): void
{
Log::error("SendWelcomeEmail a échoué pour {$this->user->email}", [
'exception' => $exception->getMessage(),
]);
}
}
ثلاث خصائص مهمّة. $tries = 3 يُشير إلى أنّ Laravel يُعيد محاولة الـ job 3 مرّات عند الإخفاق. $timeout = 30 يُعَرِّف زمن التنفيذ الأقصى. backoff() يُعَرِّف انتظارًا أسّيًّا بين المحاولات. failed() handler ملاذ أخير: يُستدعى مرّة واحدة بعد كلّ الإخفاقات.
الخطوة 3 — Dispatch لـ Job
<?php
// في controller، بعد التسجيل
public function register(StoreUserRequest $request): JsonResponse
{
$user = User::create($request->validated());
// Dispatch قياسي
SendWelcomeEmail::dispatch($user);
// Dispatch مؤجَّل — يُنَفَّذ بعد 5 دقائق
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));
// Dispatch مشروط
SendWelcomeEmail::dispatchIf($user->hasVerifiedEmail(), $user);
// Dispatch على queue محدّدة (أولوية عالية)
SendWelcomeEmail::dispatch($user)->onQueue('high');
// Dispatch متزامن — للاختبارات الوحدوية
SendWelcomeEmail::dispatchSync($user);
return response()->json(['message' => 'Inscription réussie.'], 201);
}
ردّ HTTP يُرجَع فورًا بعد dispatch() — المستخدم لا ينتظر إرسال البريد. في الخلفية، الـ worker يلتقط الـ job من جدول jobs وينفّذ handle().
الخطوة 4 — تشغيل Queue Worker
Queue Worker عملية PHP دائمة تُراقب الـ queue وتُنَفِّذ jobs فور توفّرها. في التطوير، تُشَغِّله يدويًّا في terminal منفصل:
# تشغيل worker
php artisan queue:work
# Worker بأولوية: يُعالج "high" قبل "default"
php artisan queue:work --queue=high,default
# Worker محدود (مفيد لـ VPS بذاكرة قليلة)
php artisan queue:work --max-jobs=500 --max-time=3600
# عرض jobs المُخفِقة
php artisan queue:failed
# إعادة محاولة كلّ jobs المُخفِقة
php artisan queue:retry all
# إعادة محاولة job محدّد (بـ UUID)
php artisan queue:retry 5e3d8c2a-1234-5678-abcd-ef0123456789
إشارة عمل worker: ترى رسائل [2026-05-15 10:00:00] Processing: App\Jobs\SendWelcomeEmail ثم Processed: App\Jobs\SendWelcomeEmail.
الخطوة 5 — الإشراف على workers في الإنتاج بـ Supervisor
في الإنتاج، عملية queue:work يجب أن تبقى نشطة دائمًا، حتى بعد crash أو إعادة تشغيل الخادم. Supervisor أداة إدارة عمليات Linux مُصَمَّمة لهذا.
sudo apt-get install -y supervisor
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/mon-projet/artisan queue:work database --queue=high,default --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/mon-projet/storage/logs/worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
# تحقّق من الحالة
sudo supervisorctl status
numprocs=2 يُشَغِّل اثنين بالتوازي. autorestart=true يُعيد التشغيل عند التوقّف. --max-time=3600 يُعيد تشغيل worker كلّ ساعة لتحرير الذاكرة. بعد كلّ نشر: php artisan queue:restart.
الخطوة 6 — Job Batching: تجميع المهامّ وتتبّع التقدّم
# أنشئ جدول batches
php artisan make:queue-batches-table
php artisan migrate
<?php
use Illuminate\Support\Facades\Bus;
$batch = Bus::batch([
new ProcessInvoice($invoices[0]),
new ProcessInvoice($invoices[1]),
new ProcessInvoice($invoices[2]),
])->then(function (Illuminate\Bus\Batch $batch) {
// كلّ الفواتير عُولِجت بنجاح
Log::info("Batch {$batch->id} terminé : {$batch->totalJobs} factures traitées.");
})->catch(function (Illuminate\Bus\Batch $batch, Throwable $e) {
// job واحد على الأقلّ أخفق
Log::error("Erreur dans le batch {$batch->id} : {$e->getMessage()}");
})->finally(function (Illuminate\Bus\Batch $batch) {
// يُنفَّذ دائمًا، نجاحًا أم إخفاقًا
Log::info("Batch {$batch->id} finalisé. Annulé: {$batch->cancelled()}");
})->dispatch();
أخطاء شائعة
| الخطأ | السبب | الحلّ |
|---|---|---|
Jobs لا تُنفَّذ (QUEUE_CONNECTION=sync) |
driver sync يُنَفِّذ فورًا متزامنًا | انتقل إلى database أو redis |
| Job محجوب ولم يُعالَج | queue:work غير مُشَغَّل |
افحص supervisorctl status |
| Model غير قابل للتسلسل | الـ Model يحوي closures | مَرِّر فقط ID وأعد التحميل في handle() |
| Jobs مكرّرة في queue | Dispatch مرّتين | نَفِّذ ShouldBeUnique مع uniqueId() |
FAQ
الفرق بين queue:work وqueue:listen؟ queue:work يُحَمِّل الـ framework مرّة واحدة — أسرع، لكن يلزم queue:restart بعد كلّ نشر. queue:listen يُعيد التحميل مع كلّ job — أبطأ لكن يعكس التغييرات فورًا.
Redis أم database في الإنتاج؟ Redis موصى به منذ عشرات jobs/دقيقة. Redis يحفظ jobs في الذاكرة (مايكروثوانٍ) بينما database يقرأ/يكتب على القرص.
كيف نُراقب queues دون Supervisor؟ Laravel Horizon (Redis فقط) يعرض dashboard مرئي للـ workers وjobs والإخفاقات. ثبِّت: composer require laravel/horizon ثم /horizon.