Task های زمان بندی شده در لاراول (هاست های اشتراکی)
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 10 دقیقه

Task های زمان بندی شده در لاراول (هاست های اشتراکی)

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

دستور بالا در همان لحظه بررسی می‌کند چه تسک‌هایی باید اجرا شوند و آن‌ها را اجرا می‌کند.

تصویر مفهومی اجرای کرون جاب در هاست اشتراکی برای راه‌اندازی Task Scheduler لاراول

اجرای دستورات 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() تغییر بدهی و نتیجه را سریع ببینی.

تنظیم کرون جاب در cPanel برای اجرای Task Scheduling لاراول در هاست اشتراکی

اجرای زمان‌بندی در هاست اشتراکی (cPanel/DirectAdmin)

در هاست اشتراکی معمولاً دسترسی Root یا سرویس‌هایی مثل Supervisor نداری. اما Task Scheduling لاراول همچنان با یک کرون جاب ساده کار می‌کند:

  1. به کنترل‌پنل (مثلاً cPanel) وارد شو و Cron Jobs را باز کن.
  2. بازهٔ اجرا را روی «هر دقیقه» بگذار (برای بررسی زمان‌بندی‌ها بهترین حالت است).
  3. دستور نمونه (مسیرها را مطابق هاست خودت تنظیم کن):
/usr/local/bin/php /home/USERNAME/public_html/artisan schedule:run 1>> /dev/null 2>&1

نکات مهم:

  • مسیر PHP ممکن است متفاوت باشد (مثلاً /opt/alt/php81/usr/bin/php). از پشتیبانی هاست بپرس.
  • اگر پروژه در زیرپوشه است، مسیر artisan را درست بده (مثلاً /home/USERNAME/domains/example.com/public_html/project/artisan).
  • در صورت نیاز، از نسخهٔ PHP دقیق‌تری استفاده کن (مثلاً PHP 8.1).
  • اگر خروجی خطا لازم داری، بخش 1>> /dev/null 2>&1 را حذف کن تا خروجی ایمیل شود یا از email notifications کرون استفاده کن.

جایگزین‌ها وقتی Cron محدود است

  • سرویس‌های خارجی cron: مانند easycron یا cron-job.org که هر دقیقه/۵ دقیقه یک درخواست به URL مشخص شما می‌زنند؛ شما در آن URL Artisan::call('schedule:run') را اجرا می‌کنید (با احراز هویت/توکن امن).
  • راه‌اندازی اسکریپت میانجی: یک Route امن داخلی که با کلید مخفی اجرا شود و فقط در محیط Production فعال باشد.
  • SSH + اسکریپت محلی: اگر دسترسی SSH دارید، می‌توان یک اسکریپت در لوکال تنظیم کرد که با ssh وارد هاست شود و php artisan schedule:run را تریگر کند.

ترکیب Schedule با Queue در هاست اشتراکی

خیلی از تسک‌ها بهتر است به‌جای انجام مستقیم، به صف (Queue) سپرده شوند: هم خطاها بهتر مدیریت می‌شود، هم UI بلاک نمی‌شود، هم می‌توان کارهای سنگین را قطعه‌قطعه کرد. در سرورهای اختصاصی از Supervisor برای اجرای دائمی workerها استفاده می‌کنیم؛ اما در هاست اشتراکی معمولاً Supervisor نداریم. دو رویکرد کاربردی:

  1. تریگر دوره‌ای worker: با کرون جاب هر ۱–۵ دقیقه این دستور را اجرا کن:
    php artisan queue:work --stop-when-empty

    این کارگر صف را تا زمانی که Job هست اجرا می‌کند و سپس متوقف می‌شود. اگر Job جدیدی بیاید، دفعهٔ بعد که کرون اجرا شد دوباره worker بالا می‌آید.

  2. پردازش صف داخل خود 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 را با دقت تنظیم کن.

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

?

۱) «هاست لاراول» یعنی چه و چرا مهم است؟

هاست لاراول یعنی هاستی که نسخهٔ مناسب PHP (معمولاً ۸.۰+)، اکستنشن‌های لازم، و تنظیمات معمول لاراول را پشتیبانی کند. اگر هاست قدیمی باشد یا دسترسی به مسیر PHP مناسب ندهد، اجرای Cron و Artisan به مشکل می‌خورد.

?

۲) چرا گاهی پیام no scheduled commands are ready to run می‌بینم؟

این پیام خطا نیست؛ یعنی الان هیچ تسکی موعد اجرا ندارد. اگر فکر می‌کنی باید اجرا می‌شد، زمان/منطقهٔ زمانی و شرط‌ها را بررسی کن و برای تست موقتاً everyMinute() بگذار.

?

۳) می‌توانم چند کرون جاب متفاوت تنظیم کنم؟

از نظر فنی بله، اما بهترین روش این است که فقط یک کرون schedule:run داشته باشی و همهٔ زمان‌بندی‌ها را به لاراول بسپری. این روش مدیریت را ساده و خطا را کم می‌کند.

?

۴) در هاست اشتراکی Supervisor ندارم؛ صف‌ها را چطور اجرا کنم؟

از کرون جاب دوره‌ای با queue:work --stop-when-empty استفاده کن. این دستور کارگر صف را راه می‌اندازد، تا خالی‌شدن صف کار می‌کند و بعد متوقف می‌شود.

?

۵) چطور از اجرای هم‌زمان دو نسخهٔ یک تسک جلوگیری کنم؟

withoutOverlapping() را به تسک اضافه کن. در محیط چندسروری، اگر کش مشترک داری، از onOneServer() هم استفاده کن.

?

۶) اجرای تسک خیلی طولانی است و به Timeout می‌خورد؛ چه کنم؟

کار را خرد کن و در Jobهای صف اجرا کن، از runInBackground() استفاده کن، یا بازهٔ کرون را بلندتر بگیر. همچنین ممکن است لازم باشد memory_limit/ max_execution_time را بررسی کنی.

?

۷) می‌توانم به‌جای کرون داخلی هاست از سرویس خارجی استفاده کنم؟

بله؛ سرویس‌هایی مثل easycron یا cron-job.org می‌توانند هر دقیقه به URL امن شما درخواست بفرستند و شما در آن URL schedule:run را تریگر می‌کنی. مواظب امنیت و نرخ درخواست باش.

?

۸) تفاوت call() و command() در Schedule چیست؟

call() یک Closure/PHP callable را اجرا می‌کند؛ سریع و ساده برای کارهای کوچک. command() یک دستور Artisan را اجرا می‌کند؛ بهتر برای کارهای بزرگ، تست‌پذیر و قابل استفاده در جاهای دیگر.

?

۹) آیا می‌توانم خروجی تسک را ذخیره یا ایمیل کنم؟

بله؛ از sendOutputTo() یا appendOutputTo() برای لاگ فایل، و از emailOutputTo() برای ارسال خروجی به ایمیل استفاده کن.

?

۱۰) بهترین ساختار پوشه/کلاس برای نگه‌داری تسک‌های زیاد چیست؟

منطق اصلی را در Commandها یا Service‌های مجزا نگه‌دار و در Kernel فقط زمان‌بندی را بنویس. برای هر حوزه (گزارش‌ها، همگام‌سازی، پاکسازی) Commandهای جدا و قابل تست بساز.

 

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
4.17 از 12 رای

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید