آموزش مقدماتی لاراول - پیاده‌سازی Jobs و اجرای آن با کمک crontab

در آموزش قبل در مورد کار با صف ها و نحوه اجرای اونها در سرور و محصول نهایی نکاتی رو مطرح کردیم. همانطور که گفتیم queue برای افزایش راندمان سایت و قراردادن عملیاتی در جریان یک درخواست که به لحاظ زمانی هزینه بر هستند کاربرد دارند. اما اگر بخواهیم یک کار خاص بصورت مستقل اجرا بشه چیکار باید بکنیم. مثلا اگر بخواهیم خبرنامه سایتمون در یک روز و زمان مشخص اجرا بشه. یا اگر بخواهیم بصورت دوره ای چیزی در سایتمون بررسی بشه چیکار باید کرد. در اینجا لاراول ابزاری به نام job  رو معرفی می کنه که میتونه بصورت مستقل عملیاتی رو انجام بده. در ادامه و در این مقاله در مورد پیاده‌سازی این ابزار کاربردی نکاتی رو مطرح می کنیم.


پیش از شروع توصیه میکنم اگر آموزش کار queue رو مطالعه نکردید و با مفهوم صف در لاراول آشنایی ندارید حتما آموزش قبلی در این رابطه رو مطالعه کنید:

بیایید تصور کنیم برای این پروژه ای که داریم روش کار می‌کنیم میخوایم یک سرویس خبرنامه برای کاربرانی که در سیستم وجود دارند ایجاد کنیم. اول از همه یک ایمیل بسیار ساده برای خبرنامه در نظر می گیریم.

php artisan make:mail NewsletterMail –m emails.newsletter

حالا با استفاده از دستور زیر یک job تعریف می کنیم :

php artisan make:job NewsletterJob

کلاس‌های job ما در مسیر /app/Jobs قرار می‌گیرند، همانطور که مشاهده می‌کنید NewsletterJob همانند listenerها که در مقاله قبلی هم مشاهده کردیم یک تابع handle دارند که ما دستورات خودمون رو در ان وارد می‌کنیم و اینکه این کلاس خاصیت صف‌ شدن رو دارد پس بنابراین با اجرای ان فرایند در پس‌زمینه پروژه اجرا خواهد شد.

من دستور ارسال ایمیلی که ایجاد کردیم رو برای تمامی کاربرانی که در سیستم ثبت‌نام کردند بصورت زیر تعریف کردیم:

public function handle()
{
   foreach (User::all() as $user) {
       Mail::to($user->email)->send(new NewsletterMail());
   }
}

ما در اینجا بسیار ساده عمل کردیم ولی می‌توانید یک جدول برای کسانی که نمی‌خواهند خبرنامه دریافت کنند هم تعریف کنید و بجای انتخاب تمام کاربران، تنها کاربرانی که از خبرنامه انصراف نداند رو انتخاب کنید. 

اجرای یک job

برای اجرای یک job چندین روش وجود داره. یکی از اونها استفاده از کلاس Queue هست. شما می‌توانید با توجه به آموزش قبلی با supervisor یک worker تعریف کنید و با کمک استفاده از دستور زیر job رو در صف اجرا قرار بدید.

\Illuminate\Support\Facades\Queue::push(new \App\Jobs\NewsletterJob());

اگر یک صف با نام متفاوت مثلا صف اجرای jobها داشته باشید مانند زیر عمل کنید:

\Illuminate\Support\Facades\Queue::pushOn('jobs_queue', new \App\Jobs\NewsletterJob());

و یا حتی اگر می‌خواهید با کمی تاخیر این اتفاق بیافتد دستور زیر را وارد کنید:

\Illuminate\Support\Facades\Queue::later(30, new \App\Jobs\NewsletterJob());
\Illuminate\Support\Facades\Queue::laterOn('jobs_queue', 30 , 
new \App\Jobs\NewsletterJob());

همچنین کلاس queue این امکان رو هم دارد که شما چندین job رو همزمان تعریف کنید:

\Illuminate\Support\Facades\Queue::bulk([
   new \App\Jobs\NewsletterJob(),
   new \App\Jobs\InvoiceJob()
]);
\Illuminate\Support\Facades\Queue::bulk([
   new \App\Jobs\NewsletterJob(),
   new \App\Jobs\InvoiceJob()
], null , 'jobs_queue');

از طرفی همانطور که در کلاس NewsletterJob هم مشخص هست این کلاس از قابلیت‌ Dispatchable هم پشتیبانی میکنه. در اینصورت می‌تونید این job رو بصورت زیر هم اجرا کنید:

\App\Jobs\NewsletterJob::dispatch();

و یا 

(new \App\Jobs\NewsletterJob())->dispatch();

و همچنین امکان تعریف تاخیر در اجرا و یا ارسال به یک صف اجرای دیگر هم وجود دارد:

(new \App\Jobs\NewsletterJob())->dispatch()
	->onQueue('jobs_queue')
	->delay(30);

همچنین یک تابع کمکی به نام dispatch هم برای این کار در نظر گرفته شده:

dispatch(new \App\Jobs\NewsletterJob())->onQueue('jobs_queue')->delay(30);

حالا اجازه بدید دستور اجرای job رو برای تست یکبار اجرا کنیم. برای اینکار وارد route/web.php بشید و یک مسیر بصورت زیر تعریف کنید:

Route::get('/test', function () {
   \App\Jobs\NewsletterJob::dispatch();
   dd(‘done’);
});

اگر مسیر رو در مرورگر باز کنید در صورتیکه ایمیل mailtrap رو متصل داشته باشید، خواهید دید که job در پس‌زمینه اجرا و ایمیل‌ها ارسال خواهند شد.

اما همانطور که پیش‌تر گفتیم ما گاهی نیاز داریم یک سری عملیات بصورت دوره‌ای و اتوماتیک در پروژه اجرا بشه. برای نمونه همین خبرنامه، یا فاکتور‌های دوره‌ای برای ادمین.

زمانبندی کار‌ها (Task Scheduling)

برای زمانبندی وظایف در لاراول ابتدا وارد مسیر app/Console بشوید و فایل Kernel.php رو باز کنید. همانطور که مشاهده می‌کنید در اینجا ما یک تابع به نام schedule داریم که در آن دستورات خودمون رو وارد می‌کنیم. اجازه بدید برای تست من یک دستور بنویسیم که در هر دقیقه یکی از آهنگارو پاک کنه:

protected function schedule(Schedule $schedule)
{
   $schedule->call(function () {
       Song::first()->delete();
   })->everyMinute()->description('Remove song in every minute');
}

حالا اگر دستور زیر رو بزنید خواهید دید که هر یک دقیقه دستور اجرا میشه و یک آهنگ رو پاک می‌کنه!

php artisan schedule:work

برای توقف هم دستور زیر رو وارد کنید:

php artisan schedule:finish all 
php artisan schedule:finish 0

ما در ابنجا از تابع call استفاده کردیم که خیلی روش تمیزی نیست برای این منظور به دو روش میشه عملیاتی که در نظر دارید رو در Schedule  تعریف کنید. یکی مستقیما با استفاده از job و دیگری با کمک commandها (برای آشنایی با command ها می‌تونید مقاله آموزش که در این رابطه منتشر کردیم رو در زیر مطالعه کنید):

خب من می‌خواهم job که مربوط به خبرنامه بود رو بصورت زمان‌بندی شده هر شنبه ساعت ۸ صبح به کاربران عضو در سیستم ارسال کنم و در صورتی که خطا داد یک رخداد رو فراخوانی کنم و در صورت موفقیت هم رخداد دیگری رو ارسال کنم

protected function schedule(Schedule $schedule)
{
   $schedule->job(new NewsletterJob())
       ->saturdays()
       ->at('08:00')
       ->description('Newsletter job')
       ->onSuccess(function () {
           return event(new NewsletterSuccessEvent());
       })->onFailure(function () {
           return event(new NewsletterFailurEvent());
       });
}

حالا اگر دستور زیر رو بزنید خواهید دید که زمان اجرا برای چه زمانی تعیین شده است:

Image for post
Image for post

برای زمانبندی کارها توابع بسیاری وجود دارد که شما می‌توانید در اینجا لیست کامل این توابع رو مشاهده کنید.

همچنین توابع کمکی بسیاری هست که خروجی‌هارو میشه با اون‌ها مدیریت کرد، برای نمونه همین توابع onSuccess و onFailure که در مثال بالا مشاهده کردید و یا تابع sendOutputTo می‌توانید نتیجه عملیات رو به یک فایل ارسال کنید و یا با emailOutputTo می‌توانید نتیجه رو به یک ایمیل ارسال کنید همچنین تابع دیگر emailOutputOnFailure وجود دارد که می‌توانید نتیجه رو در صورت بروز خطا به آن ارسال کنید.

همانطور که گفته شد یکی دیگر از روشها برای تعریف عملیات در Schedule استفاده از Commandهاست. 

اگر آموزش‌ها را تا الان دنبال کرده باشید ما در این مقاله یک Command نوشتیم که در آن genreهایی که استفاده نداشتند پاک می‌کرد. حالا می‌خواهیم با کمک schedule این دستور را بصورت زمان‌بندی شده اجرا کنیم تا genreهای اضافی پاک شوند:

$schedule->command('song:genre-clean')
	->dailyAt('00:00')
	->description('clean unused genre')
	->emailOutputOnFailure('admin@admin.com');

حالا اگر دستور مشاهده لیست دستورات schedule را بزنید نتیجه بصورت زیر خواهد بود:

Image for post
Image for post

همانطور که مشاهده می کنید در ستون command دستور رو هم نمایش میده. خب حالا بیایید schedule رو اجرا کنیم. برای این منظور از دستور زیر استفاده می‌کنیم:

php artisan schedule:work

پس از این دستور خواهید دید که ترمینال شما بصورت live منتظر رسیدن زمان معین برای اجرای دستوری که در schedule تعریف کردیم مانده است. اما در صورت بستن ترمینال این برنامه هم متوقف خواهد شد و دیگر زمانبندی در اجرای دستورات را نخواهیم داشت در اینصورت کافیست با یک & در انتهای دستور از باز ماندن ترمینال جلوگیری کرد. این امکان نیز وجود دارد تا خروجی و لاگ دستور را بگیریم که باید دستور را بصورت زیر وارد کنیم:

php artisan schedule:work > storage/logs/schedule.log &

مشابه آنچه در مقاله صفها گفته شد در اینجا نیز برای اتوماسیون اجرای دستورات در یک زمان بندی معین در سرور از یک ابزار به نام crontab استفاده میشه. این قابلیت در سیستم عامل‌های مبتنی بر unix هست که وظیفه اجرای برنامه در زمان بندی‌های مشخص را بر عهده دارد (چیزی شبیه taskها در ویندوز). بر این اساس یک خط زمانبندی وجود دارد که هر کاراکتر نشان‌دهنده یک واحد زمانیست که شما می‌توانید در تصویر زیر نحوه تعریف هر یک رو مشاهده کنید:

Image for post
Image for post

حالا برای اینکه ما دستور scheduler لاراول رو به نوعی خودکار کنیم باید Cron رو تنظیم کنیم که بعد از هر دقیقه اجرا بشه. برای این منظور دستور زیر را وارد کنید:

Crontab -e

اگر اولین بار دستور رو در سیستم خودتون یا سرور اجرا می‌کنید احتمالا با خروجی زیر روبرو میشید:

Image for post
Image for post

که از شما می‌خواد ادیتور فایل تنظیمات در ترمینال رو انتخاب کنید و پس انتخاب ادیتور مورد نظر وارد تنظیمات میشد، در نهایت هم برای اجرای دستور scheduler لاراول باید دستور زیر را وارد می‌کنیم:

* * * * * php artisan /your-project-path/schedule:run >> /dev/null 2>&1

تفاوت دستور schedule:run و schedule:work در این است که دستور run تنها یکبار تابع schedule را در kernel اجرا میکند در حالی که دستور work یک worker همانند آنجه در queue شاهد آن بودیم ایجاد می‌کنه. نهایتا اگر فایل تنظیمات crontab رو ذخیره کنید، خواهید دید که دستورات با توجه به زمانبندی تعریف شده اجرا خواهند شد.

تبریک میگم حالا شما قادر هستید وظایفی رو که می‌خواهید در لاراول تعریف کنید و با ابزار زمانبندی در سیستم‌های بر پایه unix این وظایف رو اجرا کنید.


در این دوره مقدماتی سعی بر این بود تا با یک نگاه کلی و بسیار ساده و روان در مورد فریم‌ورک قدرتمند لاراول یک آشنایی  بدست بیاریم. در ادامه و در یک مجموعه آموزشی دیگر کمی تخصصی‌تر وعمیق‌تر در مورد قابلیت‌هایی که در لاراول وجود داره پیش خواهیم رفت. درصورتی که سوالی وجود داره حتما در کامنتها مطرح کنید و اگر  از این دوره آموزشی لذت بردید حتما اشتراک بگذارید و تشویق فراموش نشه 😌😏

البته میشه مباحث بیشتری هم به این دوره اضافه کرد، درآینده احتمالا مواردی بیشتری رو اضافه می‌کنم.

منتظر ماجراجویی‌های بیشتر در بک‌اند باشید!!!