جدولة المهام (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 حدّ المهمة في أيام الإربعاء

;()‎->thursdays

حدّ المهمة في أيام الخميس
;()‎->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

مصادر