Skip to main content
パッケージに診断・同期・インストール用のArtisanコマンドを追加したいときは、コマンドの登録、インスタンスの生成、処理の実行を分けて考えます。実行時だけ必要なサービスをコンストラクターで解決すると、無関係なArtisanコマンドの起動まで巻き込むことがあります。 このページではLaravel Framework v13.35.0 の実装を確認し、パッケージ開発の基礎にある commands() の登録例を掘り下げます。ファイル生成の実装はGeneratorCommand、最適化タスクへの参加はoptimizeへの統合を参照してください。

プロバイダーから明示的に登録する

パッケージ内のコマンドは、サービスプロバイダーの boot() でクラス名を登録します。アプリケーションの app/Console/Commands に対する自動検出と、パッケージが提供するクラスの登録は別の入口です。
この例は、プロバイダーがパッケージの自動検出またはアプリケーション側の明示登録で読み込まれることを前提にしています。コマンドクラスを置くだけでは、パッケージのプロバイダーまで自動的に登録されるわけではありません。 runningInConsole() はコンソール環境の判定であり、特定のコマンドを実行しているかの判定ではありません。HTTPリクエスト内から Artisan::call() する用途も提供するなら、この条件により登録されない可能性を考慮し、無条件登録を含めて利用契約を決めます。プログラムからの実行方法はArtisanを参照してください。

commands()はその場でコマンドを実行しない

ServiceProvider::commands() は、受け取ったクラス名を使うクロージャーを Illuminate\Console\Application::starting() に登録します。この時点では、コマンドの handle() を呼びません。 通常のConsole Kernelでは、後でArtisan Applicationが作られると、登録済みの起動コールバックが実行されます。そこで resolveCommands() が各クラスを resolve() に渡し、コマンドの登録を進めます。 ここで重要なのは、コマンドのインスタンス生成は、そのコマンドの実行と同じタイミングとは限らないことです。後述の専用の遅延登録経路を使わない通常のクラスでは、resolve() がコンテナの make() を呼ぶため、登録時にコンストラクターとその依存関係が解決されます。 したがって、php artisan list や別のコマンドを実行するだけでも、パッケージコマンドのコンストラクターが動くことがあります。commands() を boot() に置くこと自体は、実行時までインスタンス生成を遅らせる保証にはなりません。

実行時の依存はhandle()に注入する

例として、パッケージ設定の有効・無効だけを表示するコマンドを作ります。Laravel 13の公式Artisanドキュメントと同じく、名前と説明を Signature・Description アトリビュートで定義します。
この例ではコンストラクターを追加せず、設定Repositoryを handle() の引数として受け取ります。コマンドの実行経路で Command::execute() がコンテナの call() を使うため、型指定した依存関係が注入されます。 外部APIクライアントや同期サービスを使う場合も、実行時だけ必要なら同じ位置に注入できます。ただし、サービス自身の生成や別プロバイダーの boot() に副作用があれば、それまで自動的に防げるわけではありません。
コンストラクターでは、外部APIへの接続、データベースの更新、公開ファイルの上書きなどを行わないでください。パッケージの利用者は、インストール直後や接続設定が未完成の状態でも list・help を使える必要があります。
コンストラクターを独自に定義する場合は、parent::__construct() も呼びます。Laravelの親コンストラクターがアトリビュート、signature、descriptionなどからコマンドを構成しているためです。

Signatureと遅延登録は別の仕組み

確認した Application::resolve() には、Symfonyの AsCommand アトリビュートの name を読み、名前とクラスの対応をcommand mapへ保存する分岐があります。この分岐では、その場でコンテナの make() を呼びません。Console Kernelは後で setContainerCommandLoader() を呼び、このmapを使うローダーを設定します。 一方、上の例に使ったLaravelの Signature アトリビュートは、この AsCommand 判定とは別です。Signature を付けただけで、そのクラスがこの遅延登録経路に入るわけではありません。 この違いは内部実装を読む際の注意点として扱い、まずは公式のコマンド定義と handle() への依存注入で、生成コストと副作用を小さくします。遅延ローダーがある場合も、一覧・ヘルプなどによってコマンド情報が必要になる可能性があるため、「選択したコマンドの実行時以外は絶対に生成されない」とは設計しません。

コマンドの登録まで遅延させない

サービスのバインディングを遅延読み込みすることと、Artisanへコマンドを知らせることは別です。プロバイダーが DeferrableProvider を実装していると、対象サービスが必要になるまで boot() 自体が実行されない場合があります。 コマンド登録だけをその boot() に置き、Artisan起動までにプロバイダーが読み込まれなければ、コマンドの存在を知らせる起動コールバックも登録されません。handle() への依存注入に移したサービスが遅延プロバイダーを読み込むとしても、Artisanがコマンドを見つける前の問題は解決しません。 コマンドの公開を確実にする構成として、即時読み込みのプロバイダーへ登録を置き、重いサービスのバインディングだけを別の遅延プロバイダーへ分離できます。具体的な要件は遅延サービスプロバイダーを参照してください。

更新時に確認すること

コマンド名、引数、オプション、出力、終了コードは、利用者のシェルスクリプトやCIから使われる公開インターフェースです。クラス内部の変更と同じ感覚で変更せず、互換性と移行手順を確認します。 上の StatusCommand なら、プロバイダーを読み込んだテスト環境で次のように出力と終了コードを検証できます。テスト基盤はパッケージのテスト、assertionの詳細はConsole Testsを参照してください。
これに加え、重いサービスを使うコマンドでは、一覧やヘルプの実行時にサービスの業務メソッドが呼ばれないことも検証します。登録できることだけを確認しても、無関係なコマンドへの影響までは検出できません。

参照した一次情報

最終更新日 2026年10月12日