Task های زمانبندیشده در لاراول
در پروژههای واقعی همیشه کارهایی داریم که باید بهصورت خودکار و منظم انجام شوند: ارسال ایمیل های دورهای، پاکسازی فایلهای موقت، همگامسازی داده با سرویسهای بیرونی، تولید گزارشهای مدیریتی و دهها سناریوی دیگر. Task Scheduling در لاراول این نیاز را بهصورت یکپارچه پوشش میدهد. در این مقاله، از مقدمات تا نکات پیشرفتهٔ زمانبندی در لاراول را یاد میگیریم و گامبهگام پیادهسازی روی هاستهای اشتراکی (Shared Hosting) را انجام میدهیم؛ بههمراه مثالهای عملی، رفع خطاهای رایج، مدیریت صفها (Queue) در کنار Schedule، و بهترین شیوهها برای پایداری و عملکرد بهتر.
چرا زمانبندی وظایف مهم است و دقیقاً چه میکند؟
زمانبندی وظایف (Task Scheduling) یعنی تعریف مجموعهای از عملیاتها که بر اساس یک برنامهٔ زمانی مشخص اجرا شوند—بدون دخالت انسان. این عملیاتها میتوانند بسیار ساده (ثبت یک لاگ) یا پیچیده (اجرای مجموعهای از دستورات، پردازش دادههای حجیم، یا هماهنگسازی با چند سرویس خارجی) باشند. اگر اجرای این کارها را به عامل انسانی بسپاریم، ریسک خطا و فراموشی بالا میرود. مزایای استفاده از زمانبندی در لاراول عبارتند از:
- اتومات کردن عملیات تکراری: یکبار تعریف میکنی و سیستم بهموقع اجرا میکند.
- پایداری: عملیات حساس مثل بکاپ یا پاکسازی دورهای هیچوقت فراموش نمیشوند.
- مدیریت متمرکز: همهٔ زمانبندیها در یک نقطه (Kernel) تعریف میشوند؛ فهمش ساده و نگهداریش راحتتر است.
- انعطاف بالا: از اجرای هر دقیقه تا سالانه؛ از شرطیسازی تا جلوگیری از اجرای همزمان—همه در دسترس است.
لاراول با لایهٔ Scheduler عملاً یک واسط سطح بالا روی ابزار سیستمعامل (مثل cron
در لینوکس) ارائه میدهد تا نیاز نباشد برای هر کار یک Cron جدا بسازی. یک Cron کلی میگذاری؛ خود لاراول تشخیص میدهد الان چه تسکهایی باید اجرا شوند.
معماری زمانبندی در لاراول بهصورت خلاصه
هستهٔ کار در کلاس App\Console\Kernel
و متد schedule()
انجام میشود. شما وظایف را تعریف میکنید و سپس در سرور (یا هاست اشتراکی) با یک کرون جاب، دستور php artisan schedule:run
را مثلاً هر دقیقه اجرا میکنید. از آن به بعد لاراول بررسی میکند که در این لحظه کدام وظایف موعد اجرا دارند، همانها را اجرا میکند و بقیه را نگه میدارد. حاصل این معماری:
- برای ۱۰ وظیفهٔ مختلف، یک Cron کافی است.
- اضافه/حذف/تغییر زمانبندی فقط در کد انجام میشود (Dev-friendly).
- نگهداری و دیباگ سادهتر است چون همهچیز نزدیک کد اپلیکیشن شماست.
اولین وظیفه زمانبندیشده (نمونهٔ ساده)
فایل App\Console\Kernel.php
را باز کنید و داخل schedule()
اینطور شروع کنید:
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
\Log::info('Daily report job ran at 09:00');
// اینجا هر کاری خواستی انجام بده: ارسال ایمیل، پاکسازی، بهروزرسانی داده و...
})->dailyAt('09:00');
}
این کار هر روز ساعت ۹ صبح اجرا میشود. اگر بخواهی تست کنی همان لحظه چه اتفاقی میافتد:
php artisan schedule:run
دستور بالا در همان لحظه بررسی میکند چه تسکهایی باید اجرا شوند و آنها را اجرا میکند.
اجرای دستورات Artisan در زمانبندی
خیلی وقتها وظیفهٔ شما یک دستور Artisan است (نه فقط یک Closure). مثلاً:
$schedule->command('emails:send')->everyFiveMinutes();
// یا
$schedule->command('orders:sync --force')->hourly();
مزیت اجرای Command این است که کد قابل تست و تکرار است، لاجیک در یک نقطه میماند و از هر جای دیگری هم قابل اجراست.
متدهای زمانبندی در لاراول
لاراول مجموعهٔ کامل و قابلدرکی از متدها برای انواع زمانبندی ارائه میدهد. در این فهرست متدها را همراه با کاربرد و مثال میبینید:
everyMinute()
— اجرای هر دقیقه. مناسب برای کارهای بسیار فوری یا تِرایگر صفها.$schedule->command('sync:orders')->everyMinute();
everyTwoMinutes()
،everyFiveMinutes()
،everyTenMinutes()
،everyFifteenMinutes()
،everyThirtyMinutes()
$schedule->command('emails:send')->everyFiveMinutes();
hourly()
— اجرای هر ساعت. برای بهروزرسانی آمار، کشهای ساعتی و…$schedule->call(fn() => app(ReportService::class)->updateHourlyStats())->hourly();
hourlyAt(15)
— هر ساعت در دقیقهٔ خاص.$schedule->command('inventory:recalculate')->hourlyAt(15);
daily()
— روزانه نیمهشب (00:00).$schedule->command('backup:run')->daily();
dailyAt('09:30')
— روزانه ساعت مشخص.$schedule->command('report:send')->dailyAt('09:30');
twiceDaily(1, 13)
— دو بار در روز، مثلاً ۱ بامداد و ۱۳ ظهر.$schedule->command('pricing:sync')->twiceDaily(1, 13);
weekly()
— هفتگی (یکبار در هفته).$schedule->command('newsletter:dispatch')->weekly();
weeklyOn(1, '8:00')
— روز خاص هفته (اینجا: دوشنبه ۸ صبح).$schedule->command('logs:prune')->weeklyOn(1, '08:00');
monthly()
— ماهانه (ابتدای ماه).$schedule->command('invoices:generate')->monthly();
monthlyOn(5, '15:00')
— روز خاص ماه (روز پنجم ساعت ۱۵).$schedule->command('finance:audit')->monthlyOn(5, '15:00');
quarterly()
— هر سه ماه یکبار؛ برای کارهای فصلی مناسب است.$schedule->command('security:review')->quarterly();
yearly()
— سالانه.$schedule->command('db:refresh:annual')->yearly();
cron('expression')
— الگوی کرون سفارشی (انعطاف نامحدود).$schedule->command('task')->cron('*/10 * * * *'); // هر ۱۰ دقیقه
گزینههای پیشرفته و کنترل اجرای تسکها
برای ادارهٔ بهتر اجرای تسکها، متدهای کنترلی مهمی وجود دارند:
timezone('Asia/Tehran')
— اجرای تسک با منطقهٔ زمانی مشخص (وقتی سرور UTC است اما کاربر نهایی ایران).$schedule->command('report:send')->dailyAt('09:00')->timezone('Asia/Tehran');
between('08:00', '18:00')
— فقط بین این ساعات اجرا شود (ساعات اداری).$schedule->command('sync:crm')->hourly()->between('08:00','18:00');
unlessBetween('23:00', '04:00')
— بهجز این ساعات اجرا شود (پرهیز از تداخل با بکاپ شبانه).$schedule->command('emails:send')->everyFiveMinutes()->unlessBetween('23:00','04:00');
weekdays()
/weekends()
/sundays()
/mondays()
و… — روزهای خاص هفته.$schedule->command('ops:health-check')->dailyAt('07:00')->weekdays();
when($callback)
— فقط وقتی شرط برقرار است اجرا کن.$schedule->command('notify:vip') ->dailyAt('10:00') ->when(fn() => app(PlanService::class)->hasVipUsers());
skip($callback)
— اگر شرط برقرار شد اجرا نکن.$schedule->command('heavy:job') ->daily() ->skip(fn() => app(ServerLoad::class)->isTooHigh());
withoutOverlapping()
— از اجرای همزمان نسخهٔ بعدی جلوگیری میکند (اگر هنوز قبلی درحال اجراست).$schedule->command('import:big')->hourly()->withoutOverlapping();
onOneServer()
— در محیط چندسروری، تسک فقط روی یکی از سرورها اجرا شود (نیازمند درایور کش مشترک).$schedule->command('billing:close')->daily()->onOneServer();
runInBackground()
— اجرای پسزمینهای (برای تسکهای طولانی).$schedule->command('video:transcode')->everyFiveMinutes()->runInBackground();
sendOutputTo()
،appendOutputTo()
— لاگکردن خروجی.$schedule->command('report:gen')->daily()->sendOutputTo(storage_path('logs/report.log'));
emailOutputTo('you@example.com')
— ارسال خروجی به ایمیل (در تسکهای مهم گزارشمحور).$schedule->command('report:gen')->daily()->emailOutputTo('ops@example.com');
سناریوهای واقعی: از ایده تا کد
۱) ارسال گزارش فروش روزانه به مدیران
هر روز ساعت ۹ صبح گزارشی از فروش روز قبل تهیه و ایمیل شود. نکته: اگر گزارشگیری سنگین است، بهتر است آن را به صف (Queue) بسپاریم و فقط شروع فرایند را در Schedule تریگر کنیم.
// Kernel.php
$schedule->command('sales:daily-report')->dailyAt('09:00')->withoutOverlapping();
// Console/Commands/SalesDailyReport.php (نمونه)
public function handle()
{
dispatch(new \App\Jobs\GenerateSalesReport(now()->subDay()));
$this->info('Daily report job dispatched.');
}
۲) پاکسازی فایلهای موقت و لاگهای قدیمی
یکبار در هفته (مثلاً دوشنبه ۸ صبح) همهٔ فایلهای موقتی که بیش از ۷ روز عمر دارند حذف شوند تا فضای سرور هدر نرود.
$schedule->command('cleanup:temp --days=7')->weeklyOn(1, '08:00')->withoutOverlapping();
۳) همگامسازی دورهای با API بیرونی
دو بار در روز (۱ بامداد و ۱ ظهر) قیمتها یا موجودیها با سرویس خارجی همگام شوند. در ساعات پیک (مثلاً ۲۳ تا ۴ صبح) اجرا نشود.
$schedule->command('inventory:sync')
->twiceDaily(1, 13)
->unlessBetween('23:00','04:00')
->onOneServer()
->withoutOverlapping();
۴) بروزرسانی نرخ ارز هر ساعت در دقیقهٔ ۱۵
$schedule->command('fx:update')->hourlyAt(15)->weekdays()->runInBackground();
۵) بکاپ دیتابیس روزانه + ایمیل خروجی
$schedule->command('backup:run')
->daily()
->sendOutputTo(storage_path('logs/backup.log'))
->emailOutputTo('ops@example.com');
اجرای دستی و تست وظایف
برای تست محلی یا بررسی فوری:
php artisan schedule:run
اگر وظیفهای باید در ساعت خاصی اجرا شود، زمان سیستم/کانتینر را در نظر بگیر. برای تست، میتوانی موقتاً متد زمانبندی را به چیزی مثل everyMinute()
تغییر بدهی و نتیجه را سریع ببینی.
ترکیب Schedule با Queue در هاست اشتراکی
خیلی از تسکها بهتر است بهجای انجام مستقیم، به صف (Queue) سپرده شوند: هم خطاها بهتر مدیریت میشود، هم UI بلاک نمیشود، هم میتوان کارهای سنگین را قطعهقطعه کرد. در سرورهای اختصاصی از Supervisor برای اجرای دائمی workerها استفاده میکنیم؛ اما در هاست اشتراکی معمولاً Supervisor نداریم. دو رویکرد کاربردی:
- تریگر دورهای worker: با کرون جاب هر ۱–۵ دقیقه این دستور را اجرا کن:
php artisan queue:work --stop-when-empty
این کارگر صف را تا زمانی که Job هست اجرا میکند و سپس متوقف میشود. اگر Job جدیدی بیاید، دفعهٔ بعد که کرون اجرا شد دوباره worker بالا میآید.
- پردازش صف داخل خود Schedule: برای تسکهای سبک/کمحجم:
$schedule->command('queue:work --stop-when-empty')->everyFiveMinutes();
برای صف در هاست اشتراکی از درایورهایی مثل Database استفاده کن تا وابستگی به سرویسهای خارجی کمتر شود. همچنین retry_until
و backoff را برای Jobهای حساس تنظیم کن.
رفع خطاها و مشکلات رایج (با راهحلهای عملی)
۱) no scheduled commands are ready to run
این پیام لزوماً خطا نیست؛ یعنی «الان وظیفهای موعد اجرا ندارد». اگر انتظار داری اجرا شود:
- زمان سیستم/سرور و
timezone()
تسک را بررسی کن. - شرطها (
when
،skip
،between
) ممکن است اجرا را مانع شده باشند. - برای تست، موقتاً زمانبندی را روی
everyMinute()
بگذار وschedule:run
را بزن.
۲) مسیر PHP اشتباه یا نسخهٔ پایین
در هاست اشتراکی مسیر php
ممکن است متفاوت باشد یا نسخهٔ ۸.۱ روی مسیر دیگری باشد. از پشتیبانی بپرس و همان را در Cron بگذار. مثال:
/opt/alt/php81/usr/bin/php /home/USERNAME/public_html/artisan schedule:run
۳) خطاهای Artisan یا Permission
لاگها را بررسی کن: storage/logs/laravel.log
و error_log
ریشهٔ هاست. اگر دسترسی فایلها ایراد دارد، مجوزها (Permissions) را تنظیم کن و مطمئن شو کاربر وبسرور به مسیر پروژه دسترسی دارد.
۴) اجرای همزمان (Race Condition)
برای تسکهای طولانی از withoutOverlapping()
استفاده کن تا حتی اگر کرون دوباره اجرا شد، تسک تکراری بالا نیاید. همچنین میتوانی برای تسکهای حساس از قفلهای سطح برنامه (Cache Lock) استفاده کنی.
۵) تسک طولانی و Timeout
اگر تسک طولانی است، یا آن را به صف بسپار، یا runInBackground()
را اضافه کن، یا دستورات را خردتر کن. همچنین زمان اجرای کرون را با توجه به زمانبندی واقعی تسک تنظیم کن.
۶) خروجی/لاگ برای دیباگ
برای عیبیابی خروجی تسک را جایی بنویس:
$schedule->command('report:gen')->daily()->sendOutputTo(storage_path('logs/report.log'));
یا ایمیل کن:
$schedule->command('report:gen')->daily()->emailOutputTo('ops@example.com');
۷) همگامسازی در محیط چندسروری
اگر چند سرور داری و Redis/Database کش مشترک، از onOneServer()
استفاده کن تا تسک دوبار اجرا نشود.
مرجع کوتاه الگوهای کرون (برای سفارشیسازی)
بازه | الگوی کرون | توضیح |
---|---|---|
هر دقیقه | * * * * * | هر دقیقه یکبار |
هر ۵ دقیقه | */5 * * * * | هر پنج دقیقه |
ساعت کامل | 0 * * * * | ابتدای هر ساعت |
روزانه ۰۰:۰۰ | 0 0 * * * | هر روز نیمهشب |
هفتگی | 0 0 * * 0 | هر یکشنبه ۰۰:۰۰ |
ماهانه | 0 0 1 * * | اول هر ماه ۰۰:۰۰ |
دوبار در روز | 0 1,13 * * * | ۱ بامداد و ۱۳ ظهر |
هر ۱۰ دقیقه | */10 * * * * | برای کارهای نسبتاً پرتکرار |
برای الگوهای پیچیدهتر از cron('...')
استفاده کن. مثلاً «روزهای کاری هر ۱۵ دقیقه بین ۸ تا ۱۸» را میتوان با چند تسک مجزا یا شرطهای between
/weekdays
پوشش داد.
Best Practices و نکات حرفهای
- تسکهای سنگین را به صف بسپار: Schedule فقط تریگر باشد؛ کار اصلی در Jobهای Queue.
- از withoutOverlapping استفاده کن: مخصوصاً وقتی کرون هر دقیقه اجرا میشود.
- لاگگذاری هدفمند: خروجیها را ذخیره کن تا عیبیابی سریع شود.
- وابستگی به ساعت سیستم: اگر منطقهٔ زمانی مهم است،
timezone()
را تنظیم کن. - کپسولهسازی منطق: لاجیک کسبوکار را داخل Command/Service نگهدار، Kernel فقط زمانبندی باشد.
- پایش سلامت: یک تسک سلامت روزانه/ساعتی داشته باش که مشکلات را زود گزارش کند.
- نسخهٔ PHP و مسیرها: در هاست اشتراکی همیشه مسیر PHP/ artisan را با دقت تنظیم کن.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید