Laravel8でも動作確認済みの内容です。

Laravel上のテーブルに保存されたデータを使って日々の売上の集計を行いたい、Laravelの関連ファイルのバックアップを行いたいなど、定期的に決められた処理を自動で実行したい場合タスクスケジュールを利用することで簡単に実装することができます。

通常Unix/Linux系のOSを使用している場合に定期的な処理を行いたい場合はCronを利用し、実行したいタスク毎にエントリー(ジョブの登録)を追加していきます。Windowsでもタスクスケジューラを利用して定期的な処理を行うことができます。

LaravelもCronを利用していますがLaravelのタスクスケジュールを設定する場合は、Cronへのエントリーを1つ加えるだけでタスク毎にCronへのエントリーを追加する必要はありません。各タスク処理の設定はLaravelの設定ファイルで行うため、タスクの追加・削除を効率よく行うことができます。

Cronの理解

Laravelのタスクスケジュールの実行にはCronが利用されているので、まずCronとはなにかとCronの設定方法を確認しておきます。

Cronとは

Linux/Unix系OSでは、ある特定の処理を定期的に実行したい場合にCronを利用します(MACでも利用可能.Windowsではいえばタスクスケジューラ)。Cronはバックグランドで常時稼働しているのでcrontab(Cron Table)に何をいつどれくらいの頻度、周期で行いたいのかというエントリーを記述することでエントリーの内容に沿って定期的に決められた処理を行うことができます。手元にLinux系OSがある場合はcrontab -lコマンドを実行してみてください。なにもエントリーがなければno crontab for ユーザ名が表示されますがすでにエントリーがある場合は直後に説明を行うエントリーが表示されます。

crontab(Cron Table)の書式

crontabのエントリーの記述方法にはルールがあり下記の書式にしたがって記述する必要があります。左の*(アスタリスク)は分、2つ目は時間、3つ目は日時、4つ目は月の設定、5つ目は曜日の設定を行います。


分 時 日 月 曜日 
*  *  *  *  * command

commandに実行したい処理を記述します。

各*(アスタリスク)には下記の数値を指定することができます。

  • 分(0-59)
  • 時(0-23)
  • 日(1-31)
  • 月(1-12)
  • 曜日(0-6) ※0は日曜日

cronのコマンド

cronで使用するコマンドは下記の通りです。


#crontabの編集を行い場合
$crontab -e

#crontabに追加されているエントリーを確認
$crontab -l

#crontabのエントリーを削除
$crontab -r
MACの場合crontab -eでエントリーの追加を行ってもcrontab: “/usr/bin/vi” exited with status 1等のメッセージが出力され保存することができない場合があります。その場合はexport EDITOR=/usr/bin/vimを設定してcrontab -eを実行してください。installing new crontabが表示されれば保存可能となります。

crontabへのエントリー追加

エントリーを追加/更新する場合はcrontab -eコマンドを実行します。追加したエントリーはcrontab -lコマンドで確認することができます。

【エントリーの例】

毎分特定の処理を行い場合はすべてを*にします。


* * * * * command

毎時10分に特定の処理を行いたい場合は下記のように記述します。0時10分、1時10分、2時10分の時刻で実行されます。


10 * * * * command

毎朝8:00に特定の処理を行いたい場合は下記のように記述します。


0 8 * * * command

月曜日の朝8:00に処理を行いたい場合は下記のように記述します。


0 8 * * 1 command

【実行例】

実際にcronにエントリーを追加して動作確認を行ってみましょう。


* * * * * ls >> /Users/reffect/cron.log

上記を設定するとreffectユーザのホームディレクトリである/Users/reffectの下にcron.logが作成され、cron.logファイルが作成されます。

このようにcronは書式さえ理解できれば簡単に定期処理を行うことができます。

cronの設定がわかったので、cronを利用するLaravelのタスクスケジュールの設定方法を確認していきます。

Laravelのタスクスケジュールの設定

Laravelのタスクスケジュールはapp/Console/Kernel.phpファイルの中に記述します。タスクを実行する方法にはCommand(コマンド)を利用した方法、Closure(クロージャー)を利用した方法、直接コマンドを利用した方法などいくつかあります。本文書では1つ1つ設定方法を確認しておきます。

app/Console/Kernel.phpファイルのデフォルトの中身は下記のようになっています。


namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        //
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // $schedule->command('inspire')
        //          ->hourly();
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }
}

Cronへの登録

Laravelのタスクスケジュールを設定するためには、cronに必ず下記の1行を追加しておく必要があります。crontab -eコマンドで追加してください。


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

path-to-your-projectはLaravelをインストールしたディレクトリを設定してください。各自の環境により異なる値になります。

cronへのエントリーがすべて*(アスタリスク)になっているので、毎分php artisan schedule:runが実行されます。Laravelでタスクスケジュールを追加している場合は、scheduleを経由して登録されたタスクが設定された時刻に実行されます。

Commandとは

app/Console/Kernel.phpファイル内の各所にCommandという単語がいくつも記述されています。

Commandって何?またOSのコマンドのことを言っているのだろうと思われた方も多いかと思いますがLaravelを使った経験がある人はCommandという単語と機能を意識することなく知らず知らずのうちに利用しています。

データベースを作成する際に実行するphp artisan migrateのmigrateもCommandの一つです。Commandはphp artisan listコマンドで確認することができます。


$ php artisan list
Laravel Framework 5.8.32

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  clear-compiled       Remove the compiled class file
  down                 Put the application into maintenance mode
  dump-server          Start the dump server to collect dump information.
  env                  Display the current framework environment
//略

複数のCommandが事前に登録されていますが自分でCommandを作成・登録することができます。作成したCommandはタスクスケジュールで利用することができます。

Commandの作成

Commandはphp artisan make:commandで作成することができます。

動作確認のためWriteLogというCommandを作成します。


$ php artisan make:command WriteLog
Console command created successfully.

作成されたWriteLogはapp¥Console¥Commandsの下に作成されます。中身を下記のようになっています。


namespace App\Console\Commands;

use Illuminate\Console\Command;

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

    /**
     * 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()
    {
        //
    }
}

$signature, $description, handle()を設定していきます。

$signatureはCommandの名前の入力します。Commandを実行する際に利用します。:(コロン)で分けることでグループ化することができます。グループ化等についてはCommandを登録した後の確認で説明します。

ここでは$signatureを下記のように設定を行います。


protected $signature = 'writelog:info';

$descriptionにはCommandの説明を記述します。


protected $description = 'write info messages in log file';

handle()には実行したい処理内容を記述します。loggerのヘルパー関数を仕様してinfoレベルのメッセージをログファイルに書き込むだけのシンプルな処理内容です。


public function handle()
{
    logger()->info('This is WriteLog Command.');
}

Commandの作成は完了しましたが、Commandを作成しただけでは実行することができません。Commandを実行するためには、Commandの登録が必要になります。

Commandの登録

Commandの登録はapp¥Console¥Kernel.phpで行います。デフォルトでは何も設定されていない状態ですが$commands変数に作成したCommandの情報を登録します。


protected $commands = [
    //
];

作成したWriteLog Commandを追加します。


protected $commands = [
    Commands\WriteLog::Class,
];

追加した後にphp artisan listコマンドを実行するとKernel.phpに登録したWritelogの情報が表示されます。$signatureで入力した名前と$descriptionで入力した説明もここに表示されます。


$ php artisan list
  ・
  ・
 view
  view:cache           Compile all of the application's Blade templates
  view:clear           Clear all compiled view files
 writelog
  writelog:info       write info messages in log file

もし$signatureで:(コロン)を使用せずに名前だけ($signature = ‘writelog’;)入力した場合にphp artisan listを実行してもCommandは表示されます。


$ php artisan list
・
・
Available commands:
  clear-compiled       Remove the compiled class file
  down                 Put the application into maintenance mode
  dump-server          Start the dump server to collect dump information.
  env                  Display the current framework environment
  help                 Displays help for a command
  inspire              Display an inspiring quote
  list                 Lists commands
  migrate              Run the database migrations
  optimize             Cache the framework bootstrap files
  preset               Swap the front-end scaffolding for the application
  serve                Serve the application on the PHP development server
  tinker               Interact with your application
  up                   Bring the application out of maintenance mode
  writelogo            write info messages in log file

$signatureの設定値がCommand:nameでもCommandでもphp artisan listコマンドを実行すると表示されますが、表示される位置が異なります。Commandでは先頭のほうに表示、Command:nameではグループの名前にソートされアルファベット順になるので、writelogのグループ名では最後に表示されます。

登録したCommandの実行

Commandの登録が確認できたので、php artisanを使用して実行してみましょう。


$ php artisan writelogo:info

実行するとstorage/logs/の下にあるログファイルに下記のメッセージが追加されます。


[2019-08-20 03:44:14] local.INFO: This is WriteLog Command.

Commandの引数の追加

登録したCommandに対して実行時に引数をつけて値を渡すことも可能です。その場合は$signatureにカリーブレースで引数の名前を設定します。初期値を与えることも可能です。testという初期値を与えていますが文字列でもクォーテーションをつける必要はありません。


protected $signature = 'writelogo:info {message}';
//初期値を与えたい場合
protected $signature = 'writelogo:info {message=test}';

初期値をつけない場合にコマンドを実行してみます。引数を設定していないのでエラーが発生します。


$ php artisan calendar:add

  Not enough arguments (missing: "message").

引数に初期値を設定するとエラーなしで実行することができます。


[2019-08-20 03:44:14] local.INFO: This is WriteLog Command.

?をつけると引数の場合でもエラーなしで実行することできます。


protected $signature = 'writelogo:info {message?}';

設定した引数をhandleメソッド内で取得したい場合はargumentメソッドを利用ることができます。


public function handle()
{
    $message = $this->argument('message');
    logger()->info($message);

引数が単語の場合はダブルクォーテーションをつける必要はありませんが、文章のように空白がある場合はダブルクォーテーションをつける必要があります。ない場合は複数の引数があると判断されます。


$ php artisan logger:info "This is a argument message"

複数の引数も*(アスタリスク)をつけることで対応することできます。handle側のargumentメソッドで取得した値は配列で渡されるので注意が必要です。


protected $signature = 'logger:info {message*}';

登録したCommandの確認

登録したCommandは-hオプションをつけて実行すると確認することができます。ここでは引数などの情報も確認可能です。


$ php artisan logger:info -h
Description:
  write info messages in log file

Usage:
  logger:info 

Arguments:
  message

タスクスケジュールへの登録

Commandが単独で動作することが確認できたので、定期的にCommandが実行されるようにスケジュールへの登録を行います。登録はKernel.phpファイルで行います。

動作確認なので毎分実行させるためにscheduleメソッドの中に下記のように記述します。everyMinuteをつけることで毎分実行することができます。


protected function schedule(Schedule $schedule)
{
    $schedule->command('writelog:info')
             ->everyMinute();
}

登録後に毎分ログファイルの中にinfoメッセージが登録されれたタスクスケジュールへの登録が正常に行われたことになります。ログを見ると1分毎にログに書き込まれています。


[2019-08-20 04:13:01] local.INFO: This is WriteLog Command.  
[2019-08-20 04:14:00] local.INFO: This is WriteLog Command.  
[2019-08-20 04:15:01] local.INFO: This is WriteLog Command.  
[2019-08-20 04:16:00] local.INFO: This is WriteLog Command.  
[2019-08-20 04:17:00] local.INFO: This is WriteLog Command.  
[2019-08-20 04:18:01] local.INFO: This is WriteLog Command.  
[2019-08-20 04:19:00] local.INFO: This is WriteLog Command.

クロージャーを利用した方法

ここまではCommandを利用しましたが、クロージャーを使っても同じことを実行することができます。Kernel.phpファイルにcallメソッドを使ってクロージャーを記述します。


$schedule->call(function(){

    logger()->info('This message was writtend by Closure');

});

毎分ログファイルに上記のinfoメッセージが追加されます。


[2019-08-20 04:42:00] local.INFO: This message was writtend by Closure  
[2019-08-20 04:43:00] local.INFO: This message was writtend by Closure

シェルコマンドを利用した方法

シェルコマンドを利用する場合は、execメソッドを使用します。

動作確認なので、touchコマンドを利用して空ファイルの作成を行うタスクを追加します。


$schedule->exec('touch /Users/reffect/cron/new_file.txt')
         ->everyMinute();

指定したディレクトリの中にnew_file.txtファイルが作成されます。

処理結果をファイルに保存

タスクスケジュールを実行した内容を保存するためにsendOutputToメソッドを利用します。簡単な例ですが、月末のディスク容量を確認したい場合等にも利用することができます。


$schedule->exec('df -k')
         ->everyMinute()
         ->sendOutputTo(storage_path('logs/disk.log'));

storage_pathを設定しているので、Laravelインストールディレクトリのstorage/logsの下にdisk.logファイルが作成され、そのファイルにdf -kコマンドの出力結果が保存されます。

またCommandにはinfoメソッドもあり、Commandの中でinfoメソッドを使用することで指定したファイルにログファイルとは別で情報を保存することができます。

ここではinfoメソッドをファイルに書き込むために使用していますが、infoメソッドはphp artisanでCommandを実行した時にコンソールにメッセージを表示させるメソッドです。info以外にもerrorやline, comment等のメソッドがありますが、ファイルに書き込む場合に使用した場合はメソッドの違いがありません。コンソールだと文字の背景色がメソッド毎に変わったりといった違いがあります。

Kernel.phpファイルでsendOutputToメソッドを追記します。


$schedule->command('writelog:info')
         ->everyMinute()
         ->sendOutputTo(storage_path('logs/write.log'));

infoメソッドを使って、ログファイルの書き込みの前後にメッセージを書き込みます。


public function handle()
{
    $this->info('start logging');
    
    logger()->info('This is WriteLog Command.');

    $this->info('end logging');
}

loggerのヘルパー関数のinfoはログファイルへの書き込み、$this->infoメソッドはsendOutputToメソッドで指定したファイルへの書き込みを行います。

sendOutputToメソッドはファイル名を指定しただけでとファイルの上書きを行うので、ファイルに追記していきたい場合はsendOutputToの第2引数をtrueに設定するかappendOutputToメソッドを利用してください。


->sendOutputTo(FILE_PATH,ture);

->appendOutputTo(FILE_PATH);        

保存したファイルをメールで送信

sendOutputToで作成したファイルをメールで送信することができます。

ここでのメールの中身はsendOutputToに保存した内容です。

メールで送信するためにはメール設定が必要となりますが、動作確認を行うためなので.envファイルのMAIL_DRIVERをsmtpからlogに変更してメールの内容をログファイルに書き出します。


MAIL_DRIVER=log        

Laravelでのメール設定については下記を参考にしてください。

メールで送信するためにemailOutputToメソッドを使用します。


$schedule->command('writelog:info')
         ->everyMinute()
         ->sendOutputTo(storage_path('logs/write.log'))
         ->emailOutputTo('foo@example.com');

タスクスケジュールに登録しているので時間が経過するとログファイルにメールの内容が書き出されます。


[2019-08-20 05:52:01] local.DEBUG: Message-ID: <cfb48e20bcf1106a214923843fc80a92@swift.generated>
Date: Tue, 20 Aug 2019 05:52:01 +0000
Subject: Scheduled Job Output For ['/usr/bin/php' 'artisan' writelog:info]
From: Example <hello@example.com>
To: foo@example.com
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

start logging
end logging    

メール設定を適切に行えばメール送信できることが確認できます。

ここまでの説明通りに設定を行えばLaravelでタスクスケジュールを使って定期的な処理を行うことができます。ぜひ活用して業務の効率化につなげてください。