August 10, 2021 • ☕️ 5 min read
【 環境 】
Laravel のバージョン: 8.16.1
PHP のバージョン: 7.4.7
MySQL のバージョン: 5.7
Artisan::call でコマンドを実行できますが、これらがどんな挙動をするのか確認してみました。
class SampleCommand extends Command
{
//(中略)
public function handle()
{
Artisan::call(Batch01Command::class);
Artisan::call(Batch02Command::class);
Artisan::call(Batch03Command::class);
}
全て共通。
10秒おきにログを出力します。
public function handle()
{
$this->info(__METHOD__);
\Log::info(__METHOD__);
// 10 秒間遅延させる
sleep(10);
$this->info(__METHOD__ . ': after 10 second');
\Log::info(__METHOD__. ': after 10 second');
// 10 秒間遅延させる
sleep(10);
$this->info(__METHOD__ . ': after 20 second');
\Log::info(__METHOD__. ': after 20 second');
// 10 秒間遅延させる
sleep(10);
$this->info(__METHOD__ . ': after 30 second');
\Log::info(__METHOD__. ': after 30 second');
}
[2021-08-14 06:00:57] local.INFO: App\Console\Commands\Batch01Command::handle
[2021-08-14 06:01:07] local.INFO: App\Console\Commands\Batch01Command::handle: after 10 second
[2021-08-14 06:01:17] local.INFO: App\Console\Commands\Batch01Command::handle: after 20 second
[2021-08-14 06:01:27] local.INFO: App\Console\Commands\Batch01Command::handle: after 30 second
[2021-08-14 06:01:27] local.INFO: App\Console\Commands\Batch02Command::handle
[2021-08-14 06:01:37] local.INFO: App\Console\Commands\Batch02Command::handle: after 10 second
[2021-08-14 06:01:47] local.INFO: App\Console\Commands\Batch02Command::handle: after 20 second
[2021-08-14 06:01:57] local.INFO: App\Console\Commands\Batch02Command::handle: after 30 second
[2021-08-14 06:01:57] local.INFO: App\Console\Commands\Batch03Command::handle
[2021-08-14 06:02:07] local.INFO: App\Console\Commands\Batch03Command::handle: after 10 second
[2021-08-14 06:02:17] local.INFO: App\Console\Commands\Batch03Command::handle: after 20 second
[2021-08-14 06:02:27] local.INFO: App\Console\Commands\Batch03Command::handle: after 30 second
Batch01Command 開始 → Batch01Command 終了 → Batch02Command 開始 → Batch02Command 終了 → …
と、順番に実行するようです。
Schedule から起動する時は、withoutOverlapping や runInBackground といった豊富なオプションが指定できますが、Artisan::call では出来ないようです。
理由としては、「$schedule->command」の戻り値は Event クラスだけど、Artisan::call の戻り値は $exitCode となっているのが原因。
public function command($command, array $parameters = [])
{
if (class_exists($command)) {
$command = Container::getInstance()->make($command)->getName();
}
return $this->exec(
Application::formatCommandString($command), $parameters
);
}
public function exec($command, array $parameters = [])
{
if (count($parameters)) {
$command .= ' '.$this->compileParameters($parameters);
}
$this->events[] = $event = new Event($this->eventMutex, $command, $this->timezone);
return $event;
}
public function call($command, array $parameters = [], $outputBuffer = null)
{
[$command, $input] = $this->parseCommand($command, $parameters);
if (! $this->has($command)) {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $command));
}
return $this->run(
$input, $this->lastOutput = $outputBuffer ?: new BufferedOutput
);
}
public function run(InputInterface $input = null, OutputInterface $output = null)
{
$commandName = $this->getCommandName(
$input = $input ?: new ArgvInput
);
$this->events->dispatch(
new CommandStarting(
$commandName, $input, $output = $output ?: new BufferedConsoleOutput
)
);
$exitCode = parent::run($input, $output);
$this->events->dispatch(
new CommandFinished($commandName, $input, $output, $exitCode)
);
return $exitCode;
}
まずはこれで無事に実行できました。
event(Artisan::call(Batch01Command::class));
が、戻り値が Event 型ではないため、withoutOverlapping 等のメソッドを使用できません。
というか、そういう制御をしたいなら、ShouldQueue が居るんじゃねーのか。
「new Event()」見たいな感じで Event クラスのインスタンスを生成して・・・という事をやろうとしたが、要求される第一引数が EventMutex 型(排他制御)と、何やら無暗に触らない方が良さそうな物だったので、変な迂回路を探すのは止めといたよさそう。
という事で、こういう事をしたいなら、イベントとリスナーを使う。
場合によってはスケジューラで回した方が簡単か。