> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# パッケージのArtisanコマンド登録と依存解決

> Laravel 13のcommands()からArtisan起動までを読み解き、コマンドの生成と実行を分離する設計、依存注入、遅延プロバイダーと長期保守の注意点を解説します。

パッケージに診断・同期・インストール用のArtisanコマンドを追加したいときは、コマンドの登録、インスタンスの生成、処理の実行を分けて考えます。実行時だけ必要なサービスをコンストラクターで解決すると、無関係なArtisanコマンドの起動まで巻き込むことがあります。

このページではLaravel Framework `v13.35.0` の実装を確認し、[パッケージ開発の基礎](/jp/advanced/package-development)にある `commands()` の登録例を掘り下げます。ファイル生成の実装は[GeneratorCommand](/jp/advanced/generator-command)、最適化タスクへの参加は[optimizeへの統合](/jp/advanced/package-optimization)を参照してください。

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

パッケージ内のコマンドは、サービスプロバイダーの `boot()` でクラス名を登録します。アプリケーションの `app/Console/Commands` に対する自動検出と、パッケージが提供するクラスの登録は別の入口です。

```php theme={null}
<?php

namespace Acme\Courier;

use Acme\Courier\Console\StatusCommand;
use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if ($this->app->runningInConsole()) {
            $this->commands([
                StatusCommand::class,
            ]);
        }
    }
}
```

この例は、プロバイダーが[パッケージの自動検出](/jp/advanced/package-discovery)またはアプリケーション側の明示登録で読み込まれることを前提にしています。コマンドクラスを置くだけでは、パッケージのプロバイダーまで自動的に登録されるわけではありません。

`runningInConsole()` はコンソール環境の判定であり、特定のコマンドを実行しているかの判定ではありません。HTTPリクエスト内から `Artisan::call()` する用途も提供するなら、この条件により登録されない可能性を考慮し、無条件登録を含めて利用契約を決めます。プログラムからの実行方法は[Artisan](/jp/artisan)を参照してください。

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

`ServiceProvider::commands()` は、受け取ったクラス名を使うクロージャーを `Illuminate\Console\Application::starting()` に登録します。この時点では、コマンドの `handle()` を呼びません。

通常のConsole Kernelでは、後でArtisan Applicationが作られると、登録済みの起動コールバックが実行されます。そこで `resolveCommands()` が各クラスを `resolve()` に渡し、コマンドの登録を進めます。

```mermaid theme={null}
flowchart TD
    A["プロバイダーのboot()"] --> B["commands()で起動コールバックを登録"]
    B --> C["Console KernelがArtisan Applicationを生成"]
    C --> D["resolveCommands()でクラスを登録"]
    D --> E["通常のクラスはコンテナでインスタンス化"]
    E --> F["選択されたコマンドを実行"]
    F --> G["handle()の依存を解決して呼び出す"]
```

ここで重要なのは、**コマンドのインスタンス生成は、そのコマンドの実行と同じタイミングとは限らない**ことです。後述の専用の遅延登録経路を使わない通常のクラスでは、`resolve()` がコンテナの `make()` を呼ぶため、登録時にコンストラクターとその依存関係が解決されます。

したがって、`php artisan list` や別のコマンドを実行するだけでも、パッケージコマンドのコンストラクターが動くことがあります。`commands()` を `boot()` に置くこと自体は、実行時までインスタンス生成を遅らせる保証にはなりません。

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

例として、パッケージ設定の有効・無効だけを表示するコマンドを作ります。Laravel 13の公式Artisanドキュメントと同じく、名前と説明を `Signature`・`Description` アトリビュートで定義します。

```php theme={null}
<?php

namespace Acme\Courier\Console;

use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Contracts\Config\Repository;

#[Signature('courier:status')]
#[Description('Display whether the Courier integration is enabled')]
class StatusCommand extends Command
{
    public function handle(Repository $config): int
    {
        $enabled = (bool) $config->get('courier.enabled', false);

        $this->line($enabled ? 'Courier: enabled' : 'Courier: disabled');

        return self::SUCCESS;
    }
}
```

この例ではコンストラクターを追加せず、設定Repositoryを `handle()` の引数として受け取ります。コマンドの実行経路で `Command::execute()` がコンテナの `call()` を使うため、型指定した依存関係が注入されます。

外部APIクライアントや同期サービスを使う場合も、実行時だけ必要なら同じ位置に注入できます。ただし、サービス自身の生成や別プロバイダーの `boot()` に副作用があれば、それまで自動的に防げるわけではありません。

| 置き場所 | 通常のコマンドで評価されるタイミング | 向いている処理 |
| - | - | - |
| プロバイダーの `boot()` | プロバイダーの起動時 | コマンドクラスの登録 |
| コマンドのコンストラクター | インスタンス生成時。実行前とは限らない | 軽量な初期化 |
| `handle()` の型指定引数 | コマンドの実行時 | 実行に必要なサービスの解決 |
| `handle()` の本体 | コマンドの実行時 | 入力の検証、業務処理、結果表示 |

<Warning>
  コンストラクターでは、外部APIへの接続、データベースの更新、公開ファイルの上書きなどを行わないでください。パッケージの利用者は、インストール直後や接続設定が未完成の状態でも `list`・`help` を使える必要があります。
</Warning>

コンストラクターを独自に定義する場合は、`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がコマンドを見つける前の問題は解決しません。

コマンドの公開を確実にする構成として、即時読み込みのプロバイダーへ登録を置き、重いサービスのバインディングだけを別の遅延プロバイダーへ分離できます。具体的な要件は[遅延サービスプロバイダー](/jp/advanced/deferred-provider)を参照してください。

## 更新時に確認すること

コマンド名、引数、オプション、出力、終了コードは、利用者のシェルスクリプトやCIから使われる公開インターフェースです。クラス内部の変更と同じ感覚で変更せず、互換性と移行手順を確認します。

| ケース | 確認すること |
| - | - |
| クリーンなアプリケーションへ導入 | プロバイダーが読み込まれ、コマンド名が見つかる |
| `php artisan list` | パッケージの実行処理や外部接続が始まらない |
| `php artisan help courier:status` | 説明と入力仕様を確認できる |
| 実際のコマンド実行 | `handle()` の依存が解決され、意図した終了コードになる |
| 設定が未公開・未設定 | 一覧・ヘルプが壊れず、実行時の不足設定を適切に扱える |
| パッケージやLaravelの更新 | 既存のコマンド名・引数・オプションが引き続き使える |

上の `StatusCommand` なら、プロバイダーを読み込んだテスト環境で次のように出力と終了コードを検証できます。テスト基盤は[パッケージのテスト](/jp/advanced/package-testing)、assertionの詳細は[Console Tests](/jp/console-tests)を参照してください。

```php theme={null}
public function test_status_command_displays_enabled_state(): void
{
    config()->set('courier.enabled', true);

    $this->artisan('courier:status')
        ->expectsOutput('Courier: enabled')
        ->assertExitCode(0);
}
```

これに加え、重いサービスを使うコマンドでは、一覧やヘルプの実行時にサービスの業務メソッドが呼ばれないことも検証します。登録できることだけを確認しても、無関係なコマンドへの影響までは検出できません。

## 参照した一次情報

* [Laravel公式ドキュメント: パッケージのコマンド登録](https://github.com/laravel/docs/blob/13.x/packages.md#commands)
* [Laravel公式ドキュメント: コマンド構造と依存注入](https://github.com/laravel/docs/blob/13.x/artisan.md#command-structure)
* [ServiceProvider: commands()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php)
* [Console Application: starting()・bootstrap()・resolve()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Console/Application.php)
* [Console Kernel: getArtisan()とローダー登録](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Foundation/Console/Kernel.php)
* [Command: コンストラクターとexecute()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Console/Command.php)
* [公式テスト: コマンド解決とAsCommandによる別名登録](https://github.com/laravel/framework/blob/v13.35.0/tests/Console/ConsoleApplicationTest.php)


## Related topics

- [応用トピック](/jp/advanced/index.md)
- [パッケージのキャッシュとoptimizeへの統合](/jp/advanced/package-optimization.md)
- [Laravelパッケージ開発](/jp/advanced/package-development.md)
- [パッケージの診断情報をaboutに追加する](/jp/advanced/package-about.md)
- [遅延サービスプロバイダー](/jp/advanced/deferred-provider.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.