July 23, 2021 • ☕️☕️ 8 min read
【 環境 】
Laravel のバージョン: 8.16.1
PHP のバージョン: 7.4.7
MySQL のバージョン: 5.7
以前、こんなの書きました。
Laravel:Command クラスの handle メソッドに記述されている return 0 って何?
class SampleCommand extends Command
{
// 中略
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
return 0; //← これは一体何?
}
}
概要をまとめると、こんな感じ。
コマンドラインから叩く場合、「 ; 」で繋げると良さげ。
php artisan command:name ; echo $?
結論としては、出来ませんでした。
(詳細は冒頭で紹介したブログを参照)
が、Schedule クラスには onSuccess メソッドと onFailure メソッドがあり、これを使えば成功したかどうかを検出できるのでは?
と思ったので実験。
Kernel クラスは、こんな感じ。
protected function schedule(Schedule $schedule)
{
$this->scheduleExecuteCommand01($schedule);
$this->scheduleExecuteCommand02($schedule);
$this->scheduleExecuteCommand03($schedule);
}
private function scheduleExecuteCommand01(Schedule $schedule)
{
$schedule->command(Batch01Command::class)
->everyMinute()
->runInBackground()
->withoutOverlapping()
->onSuccess(function () {
\Log::info('Batch01Command successful.');
})
->onFailure(function () {
\Log::info('Batch01Command failed.');
});
}
private function scheduleExecuteCommand02(Schedule $schedule)
{
$schedule->command(Batch02Command::class)
->everyMinute()
->runInBackground()
->withoutOverlapping()
->onSuccess(function () {
\Log::info('Batch02Command successful.');
})
->onFailure(function () {
\Log::info('Batch02Command failed.');
});
}
private function scheduleExecuteCommand03(Schedule $schedule)
{
$schedule->command(Batch03Command::class)
->everyMinute()
->runInBackground()
->withoutOverlapping()
->onSuccess(function () {
\Log::info('Batch03Command successful.');
})
->onFailure(function () {
\Log::info('Batch03Command failed.');
});
}
Batch01Command は 0 を返し、
Batch02Command は 0 以外の値を返すようにする。
Batch03Command は return を記述しない。
public function handle()
{
return 0;
}
public function handle()
{
return 1;
}
public function handle()
{
// 戻り値を指定しない
}
以下のような結果になりました。
[2021-07-22 13:30:17] Batch01Command successful.
[2021-07-22 13:30:17] Batch02Command failed.
[2021-07-22 13:30:21] Batch03Command successful.
Batch01Command、Batch02Command は、バッチリ意図通りです。
そして、return を省略すると 0 が返るので、Batch03Command も予想通り。
という訳で、コマンドのリターンコードを受け取って判別、という事はできませんが、onSuccess メソッドと onFailure メソッドを使って、コマンドが成功したか失敗したかを判別できそうです。
戻り値を指定しなかった場合は 0 が返るのですが、メソッド内で明らかなエラーが起こった時も 0 を返すの?
と思い、以下のようなコードで実験。
private function scheduleExecuteCommand04(Schedule $schedule)
{
$schedule->command(Batch04Command::class, [0])
->everyMinute()
->runInBackground()
->withoutOverlapping()
->onSuccess(function () {
\Log::info('Batch04Command successful.');
})
->onFailure(function () {
\Log::info('Batch04Command failed.');
});
}
public function handle()
{
$param1 = $this->argument('param1');
$val1 = 10 / $param1;
\Log::info($param1);
\Log::info($val1);
}
以下のような結果になりました。
[2021-07-22 13:44:43] local.ERROR: Division by zero {"exception":"[object] (ErrorException(code: 0): Division by zero at /var/www/html/my-laravel-app/app/Console/Commands/Batch01Command.php:42)
[stacktrace]
#0 /var/www/html/my-laravel-app/app/Console/Commands/Batch01Command.php(42): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, 'Division by zer...', '/var/www/html/m...', 42, Array)
#1 /var/www/html/my-laravel-app/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): App\\Console\\Commands\\Batch01Command->handle()
(中略)
[2021-07-22 13:44:49] local.INFO: Batch01Command failed.
ゼロ割によるエラーが出力されています。
そして、onFailure に分岐したようで、「Batch01Command failed」を出力し、しっかりとエラー扱いにしてくれています。
コマンドの場合はどうなるの?
省略した場合は 0 が返るから、正常終了と見なされるの?
と、気になったので調べてみた。
php artisan command:batch04 0 ; echo $?
結果、こんな感じ。
ErrorException
Division by zero
(中略)
15 artisan:37
Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
1
ノイズが混ざって見づらくなってるけど、「1」が返ってきています。
また、ゼロ割が発生しないようにコマンドを叩いたら、「0」が返ってきました。
> php artisan command:batch04 1 ; echo $?
> 0
省略時は 0 が返って来るようですが、明らかなエラーが発生した場合は 0以外の値が返って来るようです。
しかし、
「PHPの処理としては正常に終了しているけど、レコードがちゃんと更新されていない(本当はレコードが更新されているけど、更新件数が 0件)」
といった処理は異常終了として検出されないので、常にリターンコードを意識するようにした方がいんじゃないかと思います。
コマンドを以下のように変えて実行してみた。
public function handle()
{
try {
$param1 = $this->argument('param1');
$val1 = 10 / $param1;
\Log::info($param1);
\Log::info($val1);
return 0;
} catch (Exception $e) {
\Log::error($e->getMessage());
return 1;
}
}
こんなログが出力されました。
[2021-07-22 15:48:31] local.ERROR: Division by zero {"exception":"[object] (ErrorException(code: 0): Division by zero at /var/www/html/my-laravel-app/app/Console/Commands/Batch04Command.php:43)
[stacktrace]
#0 /var/www/html/my-laravel-app/app/Console/Commands/Batch04Command.php(43): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError(2, 'Division by zer...', '/var/www/html/m...', 43, Array)
#1 /var/www/html/my-laravel-app/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(36): App\\Console\\Commands\\Batch04Command->handle()
#2 /var/www/html/my-laravel-app/vendor/laravel/framework/src/Illuminate/Container/Util.php(40): Illuminate\\Container\\BoundMethod::Illuminate\\Container\\{closure}()
(中略)
[2021-07-22 15:48:36] local.INFO: Batch04Command failed.
どうやら、コマンド内においては、システム的なエラーが発生しても Try catch で補足できず、エラーを返すようです。
スケジューラからの起動でなく、コマンドからの起動でも同じ結果となりました。
> php artisan command:batch04 0 ; echo $?
> 1
という事で、システム的なエラーが発生した場合は、こっちが return の値を指定しなかったとしても、気を利かしてエラーコードを返し、onFailure に分岐してくれるようです。
Schedule クラスから command を実行する時、リターンコードは取れないけど、onSuccess メソッドと onFailure メソッドを使えばエラーを検知できるので、活用しよう!