Artisan::callはartisanコマンドをプログラム内で呼び出すための機能です。
Artisan::call('seeder:hoge'); // php artisan hoge で実行されるArtisanコマンドの実行 Artisan::call('seeder:fuga'); // php artisan fuga で実行されるArtisanコマンドの実行
大変便利なのですが、この機能の挙動は”Laravelの中でコマンドクラスを呼び出して実行する”というもので”新たなLaravelを作って、新たなLaravelの中でコマンドを実行する”という挙動ではありません。ある一つのLaravelの中で動いているためコマンドを実行する度に新たなLaravelを立ち上げる php artisan [コマンド名] と Artisan::call(‘コマンド名’) の挙動は等価ではありません。そしてLaravelにはシングルトンを容易に作る機能があり、グローバル範囲の値は汚染されやすいです。このため Artisan::call でコマンドを呼び出すと、グローバル範囲の影響で思わぬ挙動を引き起こします( 特に foreach で Artisan::call を回すと同名のグローバルを参照しだしてバグりやすいです)。この記事ではこれの解決法を紹介します。
やることはいたってシンプルです。シェルスクリプトによって等価な実行をする様にすれば、グローバル参照による予想外の挙動を引き起こしません。例えば、次の様にします。
// hoge.batファイルの中身 php artisan make:model-batch make_model_batch.bat php artisan clean:model-batch
class DumpMakeModelBatch extends Command { protected $name = 'make:model-batch'; public function handle(): void { $dump = collect(); $dump = $dump->merge($this->makeModelCommands()) ->push('php artisan ide-helper:model -Wr'); file_put_contents(base_path().'/make_model_batch.bat', implode("\n", $dump->toArray())); } }
class CleanMakeModelBatch extends Command { protected $name = 'clean:model-batch'; public function handle(): void { unlink(base_path().'/make_model_batch.bat'); } }
こうして hoge.bat を実行すると、実行すべき artisan コマンドをコンソール上で実行するコマンドを書き込んだバッチファイルが吐き出され、そのバッチファイルを自動実行することによってグローバルの影響を受けずに artisan コマンドを複数回走らせられます。グローバルの影響を受けないため、冒頭で記述したグローバル汚染によるバグを起こしません。
ちなみにグローバル汚染によるバグが起きないと確信できるならば、Artisan::callを使った方がいいです。Laravelの立ち上げは気持ち時間がかかるので実行するartisanコマンドの数によっては無視できない遅延が起きます。
もっと言うとコンソールに結果を見せたいという要望がなければexec関数で十分ですし、このやり方はDBのトランザクション回りの制御が面倒になります。小ネタとある様にハック的な使い方にとどめて、なるべく納品するアプリケーションに入れる機能の作り方にすべきではないです。