PHP
Article

Managing Cronjobs with Laravel

By Reza Lavaryan

There are times when your application needs to run administrative tasks periodically on the server. Whether you want to send out emails to your users or clean up the database tables at the end of the day, you will need a task scheduling mechanism to take care of the tasks, when it’s time.

Cron is a task scheduler in unix-like systems, which runs shell commands at certain intervals. This article is not meant for introducing Cron, but since it is a key concept in our tutorial, we’ll will describe the basics of how it works.

A clock

Cron basics

Cron is a task scheduler daemon which runs scheduled tasks at certain intervals. Cron uses a configuration file called crontab, also known as cron table, to manage the scheduling process.

Crontab contains cron jobs, each related to a specific task. Cron jobs are composed of two parts, the cron expression, and a shell command to be run:

* * * * * command/to/run

Each field in the above expression (* * * * *) is an option for setting the schedule frequency. It is composed of minute, hour, day of month, month and day of week in order of the placement. The asterisk symbol refers to all possible values for the respective field. As a result, the above cron job will be run every minute in the day.

The following cron job is executed at 12:30 every day:

30 12 * * * command/to/run

This is just the tip of the Cron iceberg; to learn more about it, you might want to visit the wikipedia page.

In PHP powered applications, administrative tasks are normally standalone PHP scripts which are run in CLI mode. These scripts are written for performing different jobs at certain times.

However, we can’t do much without the power of other PHP libraries and frameworks. In this article, you will learn how to use the Laravel framework to create robust PHP scripts for the command line and schedule them right from the source code.

Creating Commands in Laravel

Creating a command in PHP is as simple as creating a PHP script, then running it in the command line, using the php command:

php somefile.php

As you can see, the file is passed as an argument to the command php.

Whether your application has been developed in Laravel, or you just want to use its facades and helpers to create your scripts, you will need to bootstrap Laravel before using it. However, there’s a better alternative to this: creating a Laravel Artisan command.

When using Artisan commands, we will have access to all features of Laravel, including helpers, facades, and other Artisan commands, just to name a few.

We’re not going to go into the details of Artisan commands here, as they’re beyond the scope of this tutorial, but let’s discuss the basics, just to get started. We’ll will be using the latest version of Laravel, which is 5.1 at the time of writing this article.

We use the make:console Artisan command to generate a command class skeleton to work with. As an example, we’re going to create a command which sends a happy birthday SMS message to our users on their birthday.

$ php artisan make:console HappyBirthday --command=sms:birthday

The above command will create a class named HappyBirthday in a file of the same name in the app/Console/Commands directory. We also picked a name for the command via the command option. This is the name that we’ll use when calling the command.

Now let’s open the file, and see what we have so far. There are several properties and methods inside the class that build the command’s backend:

<?php
namespace App\Console\Commands;

use Illuminate\Console\Command;

class HappyBirthday extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'sms:birthday';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        //
    }
}

For the $description, we can put a simple description of what the command does when executed.

protected $description = 'Sends a Happy birthday message to users via SMS';

In Laravel 5.1, whenever a command is executed in the terminal, the handle method of the command class is called. That’s where we need to put our logic.

In our case, to send a birthday message to users on their birthday, we can modify handle method like so:

<?php
// ...
public function handle()
{
  User::whereBirthDate(date('m/d'))->get(); 

  foreach( $users as $user ) {
    if($user->has('cellphone')) {
    SMS::to($user->cellphone)
       ->msg('Dear ' . $user->fname . ', I wish you a happy birthday!')
       ->send();
    }   
}  

$this->info('The happy birthday messages were sent successfully!');

}

Once the command is finished, we need to register it with Artisan, so that it will be available in the terminal. To do this, we just need to add the command’s classname to the commands array of theKernel class, which is located at app/Console/Kernel.php:

<?php
class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        // ...
        'App\Console\Command\HappyBirthday'
    ];

    // ...

If we run php artisan list in the terminal, we will see our command’s description:

$ php artisan list

...
sms
  sms:birthday        Sends a Happy birthday to users via SMS
...

It’s a good habit to namespace the command names. This will help us categorize them based on their usage.

As a result, whenever we run the command in the terminal, a happy birthday message would be sent to the matched group of users:

$ php artisan sms:birthday

The happy birthday messages were sent successfully!

This is just a hypothetical use case. For a more concrete example, see Younes Rafie’s post about a minification command.

Scheduling the Commands

Here comes the fun part. Let’s schedule a task for running the command we just built. To do this, we’ll use a feature you’ll undoubtedly come to love: Laravel Task Scheduler.

The tasks are defined inside the schedule method of the Kernel class. We can add as many Artisan commands as we need by using the command method.

Our task, according to its usage, is supposed to be run once a day. So we can use the daily() method like so:

<?php

// ...

protected function schedule(Schedule $schedule)
{   
        $schedule->command('sms:birthday')->daily();
}

We can schedule all the commands right from the schedule method. As the documentation states, there are a variety of schedule frequencies we may assign to the tasks. We’ll list a few of them here, but you may want to read the documentation to see the full list and decide which best suits your circumstances.

To run a task every hour every day:

<?php
$schedule->command('myTask')
         ->hourly(); 

To run a task every day at midnight:

<?php
$schedule->command('myTask')
         ->daily(); 

To run a task every day at 9:30:

<?php
$schedule->command('myTask')
         ->dailyAt('09:30'); 

To run a task every week:

<?php
$schedule->command('myTask')
         ->weekly(); 

To run it every month:

<?php
$schedule->command('myTask')
         ->monthly(); 

We can also use a custom cron schedule, like an ordinary cron job expression:

$schedule->command('myTask')
         ->cron('* * * * *');

The above task will be run every minute.

To see the full list of options, please refer to the documentation, section Schedule Frequency Options.

Laravel also provides a set of schedule constraints, which can be combined with the above methods. For instance, we can schedule a task to be run weekly, but limit it to a certain day and hour.

<?php
$schedule->command('theTask')
         ->weekly()
         ->mondays()
         ->at(12:30); 

or

<?php
$schedule->command('theTask')
         ->weekly()
         ->sundays() 

We can go further and limit the execution of the task to a certain condition, using the when method which accepts a closure. The task will be executed only if the closure returns true.

<?php
$schedule->command('table:clean')
          ->daily()
          ->when(function() {
             return SMS::isWorkingFine();
          });

Starting the Laravel Scheduler

To start the scheduler itself, we only need to add one cron job on the server (using the crontab -e command), which executes php /path/to/artisan schedule:run every minute in the day:

* * * * * php /path/to/artisan schedule:run 1>> /dev/null 2>&1

Please note that we need to provide the full path to the Artisan command of our Laravel installation.

To discard the cron output we put /dev/null 2>&1 at the end of the cronjob expression.

To read more about the Scheduler, see the Scheduling chapter in the documentation.

Conclusion

In this tutorial, we made use of Laravel Artisan commands to create terminal commands. Rather than add many cron job entries on the server per task, we only created one which is executed every minute and delegates responsibility to Laravel’s task scheduler.

By using custom Artisan commands alongside Laravel’s task scheduler, we can focus on creating the commands and Laravel will take care of the rest. In addition to this, the scheduling frequencies are manageable by other team members since it is now tracked by the version control system.

If you have an questions or comments, please post them below and we’ll do our best to reply in a timely manner.

  • Jon Tomlinson

    Very nice write-up and explanation.

  • Sam Wong

    Good article. Is there a way to set the command timed out value ? (php execution time specifically for the cronjob)

    • Radek Dvořák

      Maybe the “timeout” CLI command will suit your needs.

  • http://SalaryNet30.com Theresa Gay

    In today’s life everyone knows that money is very important, so I want to make everyone’s life easier and provide you financial freedom by telling you about a project that is paying me $10 k or more every month by doing simple tasks that anyone with basic computer skills can do and you need only good internet connection

    ~~check`my` ~~http website~~~listed~~~on~~~my~~~~{PrIvate}`~~~~~`page` ~~~~~~~!~

    ^&^757

  • Ignatius Teo

    What if you need to run a user-defined task on a user-defined schedule? For example, let’s say you have a task that sends out an email, but you want to allow the user to specify the message content and when this email is sent?

  • Ignatius Teo

    What if you need to run a task on a user-defined schedule? For example, let’s say you have a task that sends out an email, but you want to allow the user to specify the message content and when this email is sent?

    • Antonino Di Bella

      Ignatius, you need a table on database when you will save the user addressee , the message content, the datetime when you want to send email and probably a flag for set that you tried to send it and an other that you send it.
      So you can create a task that will be run every minute or better every 10m. In the task you can check the date and time in the table in the database and send email to each user that have the message datetime less or egual the current datetime and flags to false (to prevent server datetime issue and errors)
      Obviously will be useful to prevent multiple tasks on the same row at once…

    • Antonino Di Bella

      Ignatius, you need a table on database when you will save the user addressee , the message content, the datetime when you want to send email and probably a flag for set that you tried to send it and an other that you send it.
      So you can create a task that will be run every minute or better every 10m. In the task you can check the date and time in the table in the database and send email to each user that have the message datetime less or egual the current datetime and flags to false (to prevent server datetime issue and errors)
      Obviously will be useful to prevent multiple tasks on the same row at once…

      • Ignatius Teo

        I was hoping not to have to but it seems you would also have to do something similar using a normal crontask.

  • Antonino Di Bella

    Ignatius, you need a table on database when you will save the user addressee , the message content, the datetime when you want to send email and probably a flag for set that you tried to send it and an other that you send it.
    So you can create a task that will be run every minute or better every 10m. In the task you can check the date and time in the table in the database and send email to each user that have the message datetime less or egual the current datetime and flags to false (to prevent server datetime issue and errors)
    Obviously will be useful to prevent multiple tasks on the same row at once…

  • sreng khorn

    I got this error after I run command php artisan list => Class AppConsoleCommandHappyBirthday does not exist.

    • http://www.gildniy.ml Gildas Niyigena

      Remember to add this in Kernel:

      /**
      * The Artisan commands provided by your application.
      *
      * @var array
      */
      protected $commands = [
      // …
      ‘AppConsoleCommandHappyBirthday’
      ];

      and to put the namespace in HappyBirthday class.

      • http://www.dixens.net/ Dixens

        Shouldn’t it be

        protected $commands = [
        // …
        ‘AppConsoleCommandsHappyBirthday’
        ];

        (with an “S”) ?

        • http://www.gildniy.ml Gildas Niyigena

          You’re right thanks!

        • clivend

          I should read comments first :D

  • Anurag Khandelwal

    Normally, people get confused about what do we mean with “/path/to/artisan” in “* * * * * php /path/to/artisan schedule:run 1>> /dev/null 2>&1”.
    It is the path to your artisan file present in laravel project at root.

    So, people get confused in a way that whether they have quoted correct path in the live server or not. To test this, what we can do is:
    1. Enter in your server through ssh and cd into your project directory.
    2. Now, type “pwd”. You will get the path to your project. Just copy that to the place where it was mentioned “/path/to” in “* * * * * php /path/to/artisan schedule:run 1>> /dev/null 2>&1” and configure in your server’s crontab or through cPanel
    3. To test if the path which you have quoted is correct or not, just copy the /path/to/artisan and paste in the terminal after getting-in through ssh in your server,
    If it lists down several “Available Commands”, means your path to artisan is correct!

    Hope this helps to someone!

    • Soft Pdf

      Suppose my directory is in ‘/c/xampp/htdocs/blog’

      So should I run the following command in cmd?

      ‘/c/xampp/htdocs/blog php /path/to/artisan emails:send 1>> /dev/null 2>&1’

      • Anurag Khandelwal

        Not sure about windows but I guess NO.
        To check, just try to type the path to artisan and check if you are getting the list of artisan commands or not. If yes, then its correct else you need to test and come to conclusion!

  • Divo

    Thank you so much for this tutorial. Please how can I schedule a task to run weekly but limit it to two days in a week and twice a day. Would this work?

    $schedule->command(‘supervisor:reminder’)->weekly()->mondays()->thursdays()->twiceDaily(6, 13);

  • Satya Ranjan Jena

    How can i schedule a command using laravel queue

  • A Naman

    Hi, I got this error

    ➜ blog * * * * * php /applications/web/laravel/blog/artisan schedule:run

    zsh: command not found: app

    Actually it returns the name of first folder in blog directory,
    The artisan path is correct,

    Thanks :-)

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.