nico bistol.fi

Published on

How to Run Laravel Scheduled Jobs on Heroku

Authors

Running scheduled jobs on Laravel is a piece of cake, you only need to add one cron entry on your server calling the laravel scheduler to run every minute and you can manage the rest from your code in app/Console/Kernel.php.

This is very useful if you’ve your own server and you can set a job to run every minute, but maybe you can’t do that, you don’t want to, or you’re using Heroku.

The Laravel Schedulers expects to run every minute, and the frequency options that Laravel provides won’t work well if that’s not happening. So let’s get to setup the scheduler first and after that discuss about the frequency options and how to run them successfully on Heroku.

Setting up the Heroku Scheduler

First you need to add the Heroku Scheduler add-on to your app.

heroku addons:create scheduler:standard

Then you’ll need to open the scheduler dashboard

heroku addons:open scheduler

Then, using the web interface, add the php artisan schedule:run command to run every 10 minutes.

Setup the cron job

We’ll run a test command called command:test, so you need to add this line of code to your app/Console/Kernel.php file.

$schedule->command(‘command:test’)->everyMinute();

Since the Heroku Scheduler only runs every 10 minutes, every-time the Heroku Scheduler calls the Laravel Scheduler, it’ll run the command:test. This limitation disables the everyMinute or everyFiveMinutes frequency options.

** If you only want to run a job every 10 minutes, that’s it, you don’t need to do anything else **

Laravel Scheduler Issue with Heroku

If you want to schedule a job to run every hour, then you may think that using the hourly frequency option will do the job, but it won’t. The Laravel Scheduler needs to be called precisely every minute, so if you call the scheduler at 11:01 the task that was supposed to run at 11:00 won’t run and breaking news the Heroku Scheduler won’t run exactly every 10 minutes.

So in order to work properly you need to add some manual controls to the scheduler and use the runEveryMinute option for all your jobs. Precisely running a job every hour, every day or something more complex like the 3rd week of each month it’s impossible.

So what now?

clocks

The solution is simple, but requires some extra code and a new table on your database.

We will go trough each step, first setting up the new table, creating the model and functions to control that manually and scheduling the jobs to run.

Solution

1 — Add a migrations to create the Crons table

You can run php artisan make:migration CreateCronsTable and that will automatically create your migrations file. The table needs to have this structure:

Schema::create(‘crons’, function (Blueprint $table) {
    $table->string(‘command’);
    $table->integer(‘next_run’);
    $table->integer(‘last_run’);
    $table->timestamps(); $table->primary(‘command’);
    $table->index(‘next_run’);
});

Then, run php artisan migrate to create that table in your database.

2 — Create the model

Then you’ll need to create a model for that table called Cron.php

<?php
namespace App;
use Illuminate\Database\Eloquent\SoftDeletes;

class Cron extends Model
{
    protected $primaryKey = ‘command’;
    /\*\*
    \* The attributes that are mass assignable.
    \*
    \* [@var](http://twitter.com/var) array
    \*/
    protected $fillable = [‘command’, ‘next_run’, ‘last_run’];
}

It’s important to change the default primary key name for the model to command, we’ll use this to create or update the table row for each cron. The command name is the primary key, that means if you want to run the same command at different schedules, just name it differently or add some parameters to it.

3 — Using Truth Tests to run the Scheduled Job

The Truth Test Constraints allows you to add the manual control to decide if you want the scheduled job to run or not. The when method receives a function, returning true will allow the command to run.

$schedule->command(‘command:test’)
  ->everyMinute()
  ->when(function() {
    return TRUE;
  });

4 — Checking the Crons table

We’ll add the logic to check the Crons table and decide if the command is ready to run again or not. So I decided to create a shouldIRun static function on the Cron model.

This function will receive the command name and the frequency in minutes.

public static function shouldIRun($command, $minutes) {
        $cron = Cron::find($command);
        $now  = Carbon::now();
        if ($cron && $cron->next_run > $now->timestamp) {
            return false;
        }
        Cron::updateOrCreate(
            ['command'  => $command],
            ['next_run' => Carbon::now()->addMinutes($minutes)->timestamp,
             'last_run' => Carbon::now()->timestamp]
        );
        return true;
}

5 — Calling the control function from the Truth Test

$schedule->command('command:test')->everyMinute()->when(function() {
    return Cron::shouldIRun('command:test', 60);
    //returns true every hour
});

So the first time that this code runs, it’ll create the row in the crons table.

The next run will happen after the timestamp 1527104146. Now you can play with the control function and with the next_run times to setup the frequency you want. I personally recommend using Carbon to manage the dates, since it’s extremely easy to use and compare dates.

You can find the sample code at: https://github.com/nicolasbistolfi/laravel-scheduled-jobs-on-heroku

Hope you found this article useful and now you’ve more control over your scheduled jobs on Heroku with Laravel.

Here are some clocks for you to enjoy :)

clocks