جدولة المهام (Task scheduling) في Laravel
مقدمة
ربّما ولّدت في الماضي نقطة دخول Cron لكل مهمة أردت جدولتها في الخادم. لكن يمكن أن يصبح هذا متعبًا بسرعة لأن الجدولة أصبحت خارج تحكم المصدر وأصبح عليك أن تدخل ب SSH للخادم لإضافة نقاط دخول Cron.
يسمح مجدول الأوامر في Laravel بتعريف جدول المهام داخل Laravel بطريقة سلسلة وواضحة. عند استخدام جدولة المهام، تحتاج لنقطة دخول Cron واحدة على الخادم. يُعرّف جدول المهام في التابع schedule
من الملف app/Console/Kernel.php
. لمساعدتك على البداية، يوجد مثال بسيط معرّف في التابع.
بدء مجدول المهام
عند استعمال مُجدول المهام، تحتاج فقط لتعريف نقطة دخول Cron على الخادم. إن كنت لا تعرف كيفية العمل ب Cron، يمكنك استعمال خدمات مثل Laravel Forge التي ستغير نقطة دخول Cron:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
سينادي هذا الأمر مجدول المهام كل دقيقة. عند تنفيذ الأمر schedule:run
، سيقيّم Laravel المهام المجدولة وينفّذ المهام التي حان وقتها.
تعريف جدول المهام
يمكنك تعريف كل المهام المجدولة في التابع schedule
من الصنف App/Console/Kernel
. للبدء، لنلق نظرة على المثال المذكور في التابع. في هذا المثال، سنجدول نطاقًا مغلقًا Closure ليُنفَّذ في كل يوم عند منتصف الليل. في النطاق المغلق Closure، سنُنفِّذ استعلامًا لقاعدة البيانات لتفريغ جدول:
<?php
namespace App\Console;
use DB;
<?php
namespace App\Console;
use DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
*الأوامر التي يوفرها التطبيق
*
* @var array
*/
protected $commands = [
//
];
/**
* تعريف جدول المهام.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
}
بالإضافة لجدولة استعمال Closure، يمكنك استعمال كائنات قابلة للاستدعاء. الكائنات القابلة للاستدعاء هي أصناف PHP بسيطة تحتوي التابع invoke_
:
$schedule->call(new DeleteRecentUsers)->daily();
جدولة أوامر artisan
بالإضافة لجدولة نداءات النطاق المغلق Closure، يمكنك جدولة أوامر Artisan و أوامر نظام التشغيل. على سبيل المثال، يمكنك استعمال التابع command
لجدولة أمر Artisan بذكر إمّا اسمه وإمّا صنفه:
$schedule->command('emails:send --force')->daily();
$schedule->command(EmailsCommand::class, ['--force'])->daily();
جدولة الوظائف في الطوابير
يُستعمل التابع job
لجدولة الوظائف في الطوابير. توفّر هذه الطريقة جدولة سهلة دون استخدام التابع call
وإنشاء نطاق مغلق Closure لإضافة المهام للطابور يدويًّا:
$schedule->job(new Heartbeat)->everyFiveMinutes();
//إضافة المهمة لقائمة انتظار
$schedule->job(new Heartbeat, 'heartbeats')->everyFiveMinutes();
جدولة أوامر Shell
يُستعمل الأمر exec
لجدولة أوامر نظام التشغيل :
$schedule->exec('node /home/forge/script.js')->daily();
جدولة خيارات التواتر Frequency options
بالطبع توجد أكثر من جدولة يمكن تعيينها للمهمة:
التابع | التعريف |
---|---|
;('* * * * *')->cron
|
تنفيذ جدول cron مخصص |
;()->everyMinute
|
تنفيذ المهمة كل دقيقة |
;()->everyFiveMinutes
|
تنفيذ المهمة كل خمس دقائق |
;()->everyTenMinutes
|
تنفيذ المهمة كل عشر دقائق |
;()->everyFifteenMinutes
|
تنفيذ المهمة كل خمس عشرة دقيقة |
;()->everyThirtyMinutes
|
تنفيذ المهمة كل نصف ساعة |
;()->hourly
|
تنفيذ المهمة كل ساعة |
;(->(hourlyAt(17
|
تنفيذ المهمة في الدقيقة 17 من كل ساعة |
;()->daily
|
تنفيذ المهمة كل يوم منتصف الليل |
;('->dailyAt(‘13:00
|
تنفيذ المهمة كل يوم الساعة 13:00 |
;(->twiceDaily(1, 13
|
تنفيذ المهمة يوميا على الساعة 1 و 13:00 |
;()->weekly
|
تنفيذ المهمة أسبوعيا |
;('->weeklyOn(1, ‘8:00
|
تنفبذ المهمة أسبوعيا يوم الاثنين الساعة 8:00 |
;()->monthly
|
تنفيذ المهمة كل شهر |
;('->monthlyOn(4, ‘15:00
|
تنفيذ المهمة اليوم 4 الساعة 15:00 من كل شهر |
;()->quarterly
|
تنفيذ المهمة كل رباعية |
;()->yearly
|
تنفيذ المهمة سنويا |
;('->timezone(‘America/New_York
|
اختيار المنطقة الزمنية |
يمكن دمج هذه التوابع مع ضوابط إضافية لإنشاء جداول زمنية مضبوطة بدقّة تُنفّذ في أيام محدّدة كل أسبوع. على سبيل المثال، لضبط أمر لينفَّذ يوم الاثنين من كل أسبوع:
// التنفيذ يوم الاثنين الساعة 1
$schedule->call(function () {
//
})->weekly()->mondays()->at('13:00');
// التنفيذ كل ساعة من 8 إلى 17 كل أيام الأسبوع
$schedule->command('foo')
->weekdays()
->hourly()
->timezone('America/Chicago')
->between('8:00', '17:00');
في ما يلي بعض القيود الإضافية:
التابع | التعريف |
---|---|
;()->weekdays
|
حدّ المهمة في أيام الأسبوع |
;()->sundays
|
حدّ المهمة في أيام الأحد |
;()->mondays
|
حدّ المهمة في أيام الاثنين |
;()->tuesdays
|
حدّ المهمة في أيام الثلاثاء |
;()->wednesday
|
حدّ المهمة في أيام الإربعاء |
|
حدّ المهمة في أيام الخميس |
;()->fridays
|
حدّ المهمة في أيام الجمعة |
;()->saturdays
|
حدّ المهمة في أيام السبت |
;(between($start, $end-<
|
حدّ المهمة بين تاريخ بداية ونهاية |
;(->when(Closure
|
حدّ المهمة باختبارات الصّحة |
القيد الزمني Between
يمكن استعمال التابع between
لضبط تنفيذ مهمة حسب وقت معين من اليوم:
$schedule->command('reminders:send')
->hourly()
->between('7:00', '22:00');
بنفس الطريقة، يُستعمل التابع unlessbetween
لمنع تنفيذ مهمة في وقت معين من اليوم:
$schedule->command('reminders:send')
->hourly()
->unlessBetween('23:00', '4:00');
قيود اختبارات الصّحة
يُستعمل التابع when
لضبط تنفيذ مهمة بنتيجة اختبار صحّة. أي إن أعاد النطاق المغلق Closure القيمة true
، تُنفّذ المهمة المبرمَجة ما لم تكن هناك ضوابط أخرى تمنع التنفيذ:
$schedule->command('emails:send')->daily()->when(function () {
return true;
});
يمكن اعتبار التابع skip
عكس when
. إن أعاد التابع skip
النتيجة true
لا تُنفّذ المهمة:
$schedule->command('emails:send')->daily()->skip(function () {
return true;
});
عند استعمال توابع when
متسلسلة، تُنفّذ المهمة في حال أعادت جميعا القيمة true
.
المناطق الزمنية
باستعمال التابع timezone
، يمكنك تحديد أن المهمة المجدولة تنفَّذ بوقت المنطقة الزمنية المحددة:
$schedule->command('report:generate')
->timezone('America/New_York')
->at('02:00')
تنبيه: تذكر أنّ المناطق الزمنية تستعمل التوقيت الصيفي. عند حدوث تغيير للتوقيت الصيفي، قد تُنفّذ المهام المجدولة مرتين أو لا تُنفّذ إطلاقًا. لهذا يُنصح بعدم استخدام الجدولة باعتماد المناطق الزمنية إن أمكن.
منع تداخل المهام
في العادة، ستُنفّذ المهام المجدولة حتى إن لم يكتمل تنفيذ نسخة السابقة من المهمّة. لإيقاف هذا السلوك، يمكن استعمال التابع withoutOverlapping
:
$schedule->command('emails:send')->withoutOverlapping();
في هذا المثال، سيُنفّذ الأمر emails:send
كل دقيقة إن لم يكن يٌنفّذ في ذلك الحين. يكون التابع withoutOverlapping
مفيدًا خاصة إذا كان لديك مهام تختلف مدّة تنفيذها جذريًّا من نسخة لأخرى ممّا يمنعك من تحديد مدة التنفيذ بدقّة.
يمكنك تحديد عدد الدقائق التي يجب أن تنقضي قبل إلغاء القفل "without overlapping". افتراضيًّا، يلغى القفل بعد 24 ساعة:
$schedule->command('emails:send')->withoutOverlapping();
تنفيذ المهام على نفس الخادم
تنبيه: لاستخدام هذه الخاصية، على التطبيق أن يستعمل برنامج تشغيل التخزين المؤقت memcached أو redis كبرنامج تشغيل رئيسي. أيضًا، يجب أن تكون كل الخوادم على اتصال بنفس خادم التخزين المركزي.
إن كان التطبيق يعمل على عدّة خوادم، يمكنك حدّ تنفيذ مهمة على خادم معيّن. على سبيل المثال، إن كان لديك مهمة تنتج تقريرا كل ليلة جمعة، إن كان منفّذ المهام يعمل على ثلاث خوادم، سينفّذ المهمة ثلاث مرّات و ينتج ثلاث تقارير.
للإشارة إلى أنّ المهمة يجب أن تنفّذ على خادم واحد، استعمل التابع onOneServer
عند جدولة المهمة. سينشأ الخادم الأول الذي سيحصل على المهمة قفلًا ذريًّا لمنع بقية الخوادم من تنفيذ المهمة في نفس الوقت:
$schedule->command('report:generate')
->fridays()
->at('17:00')
->onOneServer();
وضع الصيانة
لن تعمل جدولة المهام في Laravel إن كان التطبيق في وضع الصيانة حيث لا نريد أن تتعامل المهام مع أي جزء غير مكتمل تحت الصيانة. لكن إن أردت فرض تنفيذ مهمة ما رغم وضع الصيانة، استعمل التابع evenIfMaintenanceMode
:
$schedule->command('emails:send')->evenInMaintenanceMode();
مخرجات المهام
يوفّر منظم المهام في Laravel طرقا سهلة للعمل مع المُخرجات الناتجة عن المهام المجدولة. أولًا، باستعمال التابع sendOutputTo
، يمكنك إرسال المخرجات لملف محدد لفحصها لاحقًا:
$schedule->command('emails:send')
->daily()
->sendOutputTo($filePath);
إذا أردت إضافة المخرجات لملف موجود، استعمل التابع appendOutputTo
:
$schedule->command('emails:send')
->daily()
->appendOutputTo($filePath);
باستعمال التابع emailOutputTo
يمكنك إرسال المخرجات ببريد إلكتروني لعنوان من اختيارك. قبل إرسال البريد، يجب ضبط خدمة إرسال البريد في Laravel:
$schedule->command('foo')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('[]');
تنبيه: التوابع emailOutputTo
و endOutputTo
و appendOutputTo
خاصة فقط بالتابعين exec
و command
.
خطاطيف المهام Task Hooks
باستعمال التابعين before
و after
، يمكنك تحديد شيفرة تنفّذ قبل أو بعد انتهاء المهمة المجدولة:
$schedule->command('emails:send')
->daily()
->before(function () {
// قبل بداية المهمة...
})
->after(function () {
// بعد المهمة ..
});
استعلام عناوين URL
باستعمال التابعين pingBefore
و thenPing
، بإمكان منظم المهام اختبار الاتصال بعنوان URL معين قبل أو بعد تنفيذ مهمة مجدولة. هذه التوابع مفيدة في حال أردت إعلام خدمات خارجية مثل Laravel Envoyer بأن مهمة ما ستبدأ أو انتهت:
$schedule->command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);
استعمال pingBefore
أو thenPing
يتطلب أولًا المكتبة Guzzle HTTP. يمكنك تثبيت Guzzle للتطبيق باستعمال منظم الحزمات Composer:
composer require guzzlehttp/guzzle