Laravel Logo

Scheduled tasks will be a core part of our application’s life cycle. We need to be able to send invoices every month and reminder users of upcoming items. Laravel provides a clean interface for quickly setting up scheduled tasks.

Without Laravel’s Scheduler

Unix based operating systems have a system know as cron that provides us with the ability to schedule specific commands at specific times. This system works extremely well but has some downsides when we’re using it to schedule lots of tasks in our application.

As we’ve discussed in our article about why we should be building our cron tasks inside our application. Maintaining anything more than a few items becomes a maintenance nightmare inside the normal cron system.

For example, we might have the following list of tasks inside the /etc/crontab file (the file where system-wide tasks are stored).

0 4 * * * root /path/to/your/scripts/itemToRunAt4AM.php
0 5 * * * root /path/to/your/scripts/itemToRunAt5AM.php
0 6 * * * root /path/to/your/scripts/itemToRunAt6AM.php
0 7 * * * root /path/to/your/scripts/itemToRunAt7AM.php
0 8 * * * root /path/to/your/scripts/itemToRunAt8AM.php

As the number of items gets larger and larger it gets harder to maintain this file and some items may get removed accidentally as we make changes. Thankfully, Laravel provides a much easier way to maintain our scheduled tasks.

Running the Schedule

Laravel provides an artisan command that allows us to run the schedule we’ve defined in our application.

Generally, in other articles from this series, we list out the --help output for the artisan commands we’re talking about but this one is so basic there isn’t any need. To start, we’re just going to run the command.

vagrant@ubuntu-xenial:/var/www$ php artisan schedule:run
No scheduled commands are ready to run.

Because this is a new project without any scheduled tasks it displays “No scheduled commands are ready to run.”.

The tasks that should be run are stored in app/Console/Kernel.php in the schedule() function. Initially, it will look like this:

protected function schedule(Schedule $schedule)
{
    // $schedule->command('inspire')
    //          ->hourly();
}

The commented out section of the function will run the inspire command every hour. As a quick side when it says hourly() it means at the beginning of the hour.

For demonstrative purposes, let’s uncomment that command and set it to run every minute.

protected function schedule(Schedule $schedule)
{
    $schedule->command('inspire')
        ->everyMinute();
}

Now when we run schedule:run it will run the command.

vagrant@ubuntu-xenial:/var/www$ php artisan schedule:run
Running scheduled command: '/usr/bin/php7.3' 'artisan' inspire > '/dev/null' 2>&1

That’s great but how do we get this to work with our application? The best way to interact with the schedule is to have it run jobs (which we talked about in “What the F*ck Is With All the Artisan Commands: Queues”). Using Laravel’s jobs class, allows us to create small self-contained classes that do what we need and prevent bloat in the kernel.php file.

To do this we’ll pass an instance of our job as a parameter to the Schedule’s job() function.

protected function schedule(Schedule $schedule)
{
    $schedule
        ->job(new AddTaskToNotCompletedProjects("Send Weekly Update to Team"))
        ->everyMinute();
}

Timezone Worries

When running tasks Laravel uses the timezone in our php.ini which may not match up with your internal view of when it should run. To help alleviate this, Laravel’s schedule component allows us to specify the timezone the command should run at.

For example, if we had a job we wanted to run every Friday at 7 AM in the Eastern Time Zone we would run it like so.

protected function schedule(Schedule $schedule)
{
    $schedule
        ->job(new AddTaskToNotCompletedProjects("Send Weekly Update to Team"))
        ->timezone('America/Detroit')
        ->weeklyOn(5, "7:00");
}

As a general rule, we recommend against this unless it’s necessary. When the “daylight saving” portion of the year starts and ends, there is a duplication of or skipping of that hour.

As an example, in the eastern timezone daylight savings time starts at 2 AM and we immediately skip that hour and it becomes 3 AM. Then when daylight savings time ends we repeat 2 AM to 3 AM twice. If we schedule a job to run at 2:30 AM it might run twice or not at all those days.

When Can We Run Jobs?

The frequency of the job is controlled by the function we call at the end of the chain. The code below causes jobs to run every hour.

protected function schedule(Schedule $schedule)
{
    $schedule
        ->job(new AddTaskToNotCompletedProjects("Send A Status Update To Your Boss"))
        ->everyHour();
}

The Laravel documentation provides a complete list of options but due to Laravel’s conventions we can generally guess the names of the functions.

$job->daily() // runs at midnight
$job->dailyAt($hour) // runs as $hour

We can also have the job run based intervals of time which are helpful for when we need to run something very routinely.

$job->everyMinute();
$job->everyThirtyMinutes();
$job->everyHour();

You can also limit it to run during business hours.

$job->between(8, 17);

And on a specific day of the week or weekdays/weekends.

$job->wednesdays();
$job->weekdays();
$job->weekends();

If those don’t provide enough flexibility, Laravel provides a cron() function that will allow us to specify a crontab expression.

$job->cron('* * * * *');

Running the Schedule Automatically

We’ve been running the schedule manually so far but we’re going to want this to operate automatically. To do this we’re going to edit /etc/crontab/ and enter the following line (this assumes our application root is /var/www and we have write access to /etc/crontab).

* * * * * apache cd /var/www; php artisan schedule:run

In Closing

Laravel’s scheduler allows us a great deal of flexibility to schedule jobs when we need them to run. Using jobs allows us to easily maintain them and if they’re running using a background worker they can be fault-tolerant.