ویرگول
ورودثبت نام
آرش فدائی
آرش فدائیتوسعه‌دهنده ارشد وب و متخصص PHP
آرش فدائی
آرش فدائی
خواندن ۴ دقیقه·۲ ماه پیش

چگونه در لاراول Job بنویسیم؟ راهنمای عملی Queue

چگونه در لاراول Job بنویسیم؟
چگونه در لاراول Job بنویسیم؟

در خیلی از اپلیکیشن‌های Laravel، بخشی از کارها نباید هم‌زمان با پاسخ HTTP انجام شوند؛ مثل ارسال ایمیل، پردازش تصویر، همگام‌سازی با API خارجی یا ساخت گزارش. اگر این کارها را داخل Controller نگه دارید، کاربر منتظر می‌ماند و احتمال Timeout بالا می‌رود.

Laravel برای همین سناریو، سیستم Queue و کلاس‌های Job را فراهم کرده است. در این مقاله قدم‌به‌قدم می‌بینیم Job چیست، چطور ساخته می‌شود، چطور Dispatch می‌شود و چه نکاتی را در Production باید رعایت کنید.

Job در Laravel چیست؟

یک Job واحد کاری است که می‌تواند بلافاصله یا با تأخیر، در پس‌زمینه اجرا شود. معمولاً منطق سنگین یا وابسته به سرویس خارجی را داخل Job می‌گذارید و از Request فقط آن را Dispatch می‌کنید.

مزیت اصلی این الگو:

  • پاسخ سریع‌تر به کاربر

  • امکان Retry هنگام خطا

  • جدا کردن منطق سنگین از لایه HTTP

  • کنترل بهتر Rate Limit و بار سرور

پیش‌نیازها

قبل از شروع مطمئن شوید:

  • یک پروژه Laravel (نسخه ۸ به بالا پیشنهاد می‌شود) دارید

  • در .env درایور Queue را مشخص کرده‌اید

  • برای محیط واقعی، یک Worker در حال اجرا دارید

انتخاب Queue Connection

در فایل .env معمولاً یکی از این گزینه‌ها را می‌بینید:

QUEUE_CONNECTION=database # یا QUEUE_CONNECTION=redis # برای تست سریع محلی: QUEUE_CONNECTION=sync

sync یعنی Job همان لحظه در همان Process اجرا می‌شود و برای Production مناسب نیست. برای شروع توسعه، database یا redis گزینه‌های رایج‌اند.

اگر از database استفاده می‌کنید، جدول jobs را بسازید:

php artisan queue:table php artisan migrate

ساخت اولین Job

با Artisan یک Job بسازید:

php artisan make:job SendWelcomeEmail

فایل معمولاً در مسیر app/Jobs/SendWelcomeEmail.php ساخته می‌شود. ساختار پایه چیزی شبیه این است:

<?php namespace App\Jobs; 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; use Illuminate\Support\Facades\Mail; use App\Mail\WelcomeMail; class SendWelcomeEmail implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function __construct( public User $user ) {} public function handle(): void { Mail::to($this->user->email)->send(new WelcomeMail($this->user)); } }

نکته مهم: ShouldQueue

اگر کلاس Job اینترفیس ShouldQueue را پیاده‌سازی کند، Laravel آن را به Queue می‌فرستد. بدون این اینترفیس، Job به‌صورت همزمان (Synchronous) اجرا می‌شود.

ارسال (Dispatch) کردن Job

از Controller، Listener یا Service می‌توانید Job را Dispatch کنید:

use App\Jobs\SendWelcomeEmail; public function store(Request $request) { $user = User::create($request->validated()); SendWelcomeEmail::dispatch($user); return response()->json([ 'message' => 'ثبت‌نام انجام شد', ], 201); }

تأخیر، صف مشخص و اولویت

SendWelcomeEmail::dispatch($user) ->delay(now()->addMinutes(5)); SendWelcomeEmail::dispatch($user) ->onQueue('emails'); SendWelcomeEmail::dispatch($user) ->onConnection('redis');

جدا کردن صف‌ها (مثلاً emails، reports، default) کمک می‌کند کارهای سنگین، کارهای سبک را بلوکه نکنند.

اجرای Worker

Job فقط وقتی اجرا می‌شود که Worker در حال گوش‌دادن به Queue باشد:

php artisan queue:work # صف مشخص: php artisan queue:work --queue=emails,default # با محدودیت تلاش مجدد: php artisan queue:work --tries=3

در Production معمولاً از Supervisor یا systemd استفاده می‌شود تا Worker بعد از Crash دوباره بالا بیاید و بعد از Deploy ری‌استارت شود.

کنترل Retry، Timeout و Failure

داخل خود Job می‌توانید رفتار خطا را دقیق‌تر تعریف کنید:

class SendWelcomeEmail implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public int $tries = 3; public int $timeout = 60; public function backoff(): array { return [10, 30, 60]; } public function handle(): void { // ... } public function failed(\Throwable $exception): void { // لاگ، اعلان به تیم، یا علامت‌گذاری کاربر logger()->error('SendWelcomeEmail failed', [ 'user_id' => $this->user->id, 'error' => $exception->getMessage(), ]); } }

برای دیدن Jobهای شکست‌خورده، جدول failed jobs را بسازید:

php artisan queue:failed-table php artisan migrate php artisan queue:failed php artisan queue:retry all

یک مثال واقعی‌تر: پردازش گزارش

<?php namespace App\Jobs; use App\Models\Report; use App\Services\ReportGenerator; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; class GenerateUserReport implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public int $tries = 2; public int $timeout = 120; public function __construct( public Report $report ) {} public function handle(ReportGenerator $generator): void { $path = $generator->generate($this->report); $this->report->update([ 'status' => 'ready', 'file_path' => $path, ]); } }

اینجا منطق ساخت گزارش داخل Service است و Job فقط orchestration می‌کند؛ این جداسازی تست‌پذیری و خوانایی را بهتر می‌کند.

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

  • Job را کوچک نگه دارید: یک مسئولیت مشخص داشته باشد.

  • Model را با SerializesModels پاس بدهید: Laravel فقط ID را در Queue ذخیره می‌کند و هنگام اجرا دوباره Model را می‌خواند.

  • داده‌های حساس را در Payload نگذارید: رمز عبور، توکن خام یا اطلاعات محرمانه را Serialize نکنید.

  • Idempotent بنویسید: اگر Job دوباره اجرا شد، نتیجه تکراری یا خراب نسازد.

  • صف‌ها را جدا کنید: ایمیل، گزارش و کارهای سنگین را در Queueهای مجزا بگذارید.

  • لاگ و Monitoring داشته باشید: Horizon (برای Redis)، failed jobs و لاگ اپلیکیشن را جدی بگیرید.

اشتباهات رایج

  • استفاده از QUEUE_CONNECTION=sync در Production و فکر کردن که Queue دارید

  • فراموش کردن اجرای queue:work یا Supervisor

  • گذاشتن منطق خیلی سنگین داخل Controller به‌جای Job

  • Pass کردن Objectهای غیرقابل Serialize

  • نداشتن استراتژی Retry و متد failed()

  • یک صف واحد برای همه کارها و ایجاد گلوگاه

جمع‌بندی

برای نوشتن Job در Laravel این مسیر کافی است: Job بسازید، ShouldQueue را پیاده‌سازی کنید، منطق را در handle() بنویسید، با dispatch() ارسال کنید و Worker را اجرا نگه دارید. اگر کارهای پس‌زمینه را از Request جدا کنید، اپلیکیشن سریع‌تر، پایدارتر و مقیاس‌پذیرتر می‌شود.

سوالات متداول

تفاوت Job و Listener چیست؟

Listener به Event واکنش نشان می‌دهد. اگر Listener اینترفیس ShouldQueue داشته باشد، خودش مثل یک Job صف می‌شود. Job را معمولاً مستقیم Dispatch می‌کنید؛ Listener را با Fire شدن Event.

کی از Bus::batch استفاده کنیم؟

وقتی چند Job مرتبط دارید و می‌خواهید پیشرفت، تکمیل یا شکست کل مجموعه را یکجا مدیریت کنید، Batch مناسب است.

آیا بدون Redis می‌توان Queue داشت؟

بله. درایور database برای شروع کاملاً قابل استفاده است. برای حجم بالاتر، Redis معمولاً عملکرد بهتری می‌دهد.

joblaravelphpاموزش php
۷
۰
آرش فدائی
آرش فدائی
توسعه‌دهنده ارشد وب و متخصص PHP
شاید از این پست‌ها خوشتان بیاید