> ## 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のArtisanコンソールを使ってカスタムCLIコマンドを作成する方法を解説します。

## Artisanとは

ArtisanはLaravelに同梱されているコマンドラインインターフェース(CLI)ツールです。
プロジェクトルートの `artisan` スクリプトとして存在し、開発・運用を効率化する多数のコマンドを提供しています。

利用可能なコマンド一覧を確認するには `list` コマンドを使います。

```shell theme={null}
php artisan list
```

コマンドの使い方を確認するには `help` を先頭に付けます。

```shell theme={null}
php artisan help migrate
```

<Info>
  Laravel Sailを使っている場合は、`php artisan` の代わりに `sail artisan` を使ってください。
  コマンドはDockerコンテナ内で実行されます。

  ```shell theme={null}
  ./vendor/bin/sail artisan list
  ```
</Info>

## Tinker

[Laravel Tinker](https://github.com/laravel/tinker) は、EloquentモデルやJob、イベントなど、アプリケーション全体をコマンドライン上でインタラクティブに操作できるREPL環境です。

```shell theme={null}
php artisan tinker
```

起動後はPHPコードをそのまま実行できます。

```php theme={null}
// ユーザーを取得して確認
>>> App\Models\User::find(1)
// ファクトリでレコードを作成
>>> App\Models\User::factory()->create()
// サービスクラスを直接呼び出す
>>> app(App\Services\OrderService::class)->process(1)
```

<Tip>
  Tinkerはデータを実際に変更します。本番環境での実行は十分注意してください。
  ローカルやステージング環境での動作確認・デバッグ用途に適しています。
</Tip>

## カスタムコマンドの作成

### コマンドを生成する

`make:command` でコマンドクラスのひな型を生成します。

```shell theme={null}
php artisan make:command ImportProducts
```

`app/Console/Commands/ImportProducts.php` が生成されます。

### コマンドの基本構造

生成されたコマンドクラスには、`$signature`、`$description`、`handle()` という3つの主要要素があります。

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

namespace App\Console\Commands;

use Illuminate\Console\Command;

class ImportProducts extends Command
{
    /**
     * コマンド名と引数・オプションの定義
     */
    protected $signature = 'import:products';

    /**
     * コマンドの説明(php artisan list に表示される)
     */
    protected $description = '商品データをCSVからインポートする';

    /**
     * コマンドの実行処理
     */
    public function handle(): void
    {
        $this->info('インポートを開始します...');
        // 処理を記述する
        $this->info('完了しました。');
    }
}
```

<Info>
  Laravel 13ではPHPアトリビュートを使った書き方も利用できます。

  ```php theme={null}
  use Illuminate\Console\Attributes\Description;
  use Illuminate\Console\Attributes\Signature;

  #[Signature('import:products')]
  #[Description('商品データをCSVからインポートする')]
  class ImportProducts extends Command
  {
      public function handle(): void
      {
          // ...
      }
  }
  ```
</Info>

### 引数とオプションの定義

`$signature` にコマンド名・引数・オプションをまとめて定義します。

```php theme={null}
protected $signature = 'import:products
                        {file : インポートするCSVファイルのパス}
                        {--limit= : インポートする最大件数}
                        {--dry-run : 実際にはDBに保存しない(確認用)}';
```

| 記法                    | 種別           | 説明                     |
| --------------------- | ------------ | ---------------------- |
| `{file}`              | 必須引数         | 省略するとエラーになる            |
| `{file?}`             | 省略可能な引数      | 省略した場合は `null`         |
| `{file=products.csv}` | デフォルト付き引数    | 省略した場合はデフォルト値を使う       |
| `{--queue}`           | フラグオプション     | 指定で `true`、省略で `false` |
| `{--limit=}`          | 値付きオプション     | `--limit=100` のように値を渡す |
| `{--limit=50}`        | デフォルト付きオプション | 省略時は `50`              |
| `{--Q\|queue}`        | ショートカット付き    | `-Q` でも指定可能            |

### 引数とオプションの取得

`handle()` メソッド内で `argument()` と `option()` を使って値を取得します。

```php theme={null}
public function handle(): void
{
    $file    = $this->argument('file');       // 引数の取得
    $limit   = $this->option('limit');        // オプションの取得
    $dryRun  = $this->option('dry-run');      // フラグオプション(bool)

    $this->info("ファイル: {$file}");

    if ($dryRun) {
        $this->warn('ドライランモード: DBへの保存はスキップします');
    }
}
```

## ユーザーとのインタラクション

### メッセージの出力

コンソールに色付きのメッセージを出力するメソッドが用意されています。

```php theme={null}
$this->info('処理が正常に完了しました');    // 緑
$this->warn('この操作は元に戻せません');    // 黄
$this->error('エラーが発生しました');       // 赤
$this->line('通常のテキスト');             // 色なし
$this->comment('補足情報');               // 薄いグレー
```

### ユーザーへの問い合わせ

対話的にユーザーから入力を受け付けることができます。

```php theme={null}
// テキスト入力
$name = $this->ask('担当者名を入力してください');

// デフォルト値付き
$env = $this->ask('実行環境を選択', 'production');

// パスワードなど秘匿情報(入力が画面に表示されない)
$password = $this->secret('APIキーを入力してください');

// Yes/No確認
if (! $this->confirm('本番DBにインポートしますか？')) {
    $this->info('キャンセルしました。');
    return;
}

// 選択肢から選ぶ
$format = $this->choice('出力形式を選んでください', ['csv', 'json', 'xml'], 0);
```

### プログレスバー

大量データの処理中に進捗をわかりやすく表示できます。

```php theme={null}
use App\Models\Product;

// コレクションを渡すだけで自動的にプログレスバーを表示する
$this->withProgressBar(Product::cursor(), function (Product $product) {
    $this->processProduct($product);
});
```

手動で細かく制御したい場合は次の方法を使います。

```php theme={null}
$total = Product::count();
$bar = $this->output->createProgressBar($total);
$bar->start();

Product::cursor()->each(function (Product $product) use ($bar) {
    $this->processProduct($product);
    $bar->advance();
});

$bar->finish();
$this->newLine();
```

## 実用的なユースケース

### データインポートコマンド

CSVファイルから商品データをインポートする実践的なコマンドの例です。

<Steps>
  <Step title="コマンドを生成する">
    ```shell theme={null}
    php artisan make:command ImportProducts
    ```
  </Step>

  <Step title="コマンドを実装する">
    ```php theme={null}
    <?php

    namespace App\Console\Commands;

    use App\Models\Product;
    use Illuminate\Console\Command;

    class ImportProducts extends Command
    {
        protected $signature = 'import:products
                                {file : CSVファイルのパス}
                                {--limit= : インポートする最大件数}
                                {--dry-run : 確認のみ(DBに保存しない)}';

        protected $description = '商品データをCSVファイルからインポートする';

        public function handle(): void
        {
            $filePath = $this->argument('file');
            $limit    = $this->option('limit') ? (int) $this->option('limit') : null;
            $dryRun   = $this->option('dry-run');

            if (! file_exists($filePath)) {
                $this->error("ファイルが見つかりません: {$filePath}");
                return;
            }

            // PHPの組み込み関数でCSVを読み込む
            $handle = fopen($filePath, 'r');
            $headers = fgetcsv($handle); // 1行目をヘッダーとして取得
            $rows = [];
            while (($row = fgetcsv($handle)) !== false) {
                $rows[] = array_combine($headers, $row);
                if ($limit !== null && count($rows) >= $limit) {
                    break;
                }
            }
            fclose($handle);

            $count = 0;

            $this->withProgressBar($rows, function (array $row) use ($dryRun, &$count) {
                if (! $dryRun) {
                    Product::updateOrCreate(
                        ['sku' => $row['sku']],
                        [
                            'name'  => $row['name'],
                            'price' => $row['price'],
                        ]
                    );
                }
                $count++;
            });

            $this->newLine();

            if ($dryRun) {
                $this->warn("{$count} 件が対象です(ドライラン: 保存はスキップしました)");
            } else {
                $this->info("{$count} 件のインポートが完了しました。");
            }
        }
    }
    ```
  </Step>

  <Step title="コマンドを実行する">
    ```shell theme={null}
    # 通常のインポート
    php artisan import:products storage/products.csv

    # 件数を制限する
    php artisan import:products storage/products.csv --limit=100

    # ドライランで確認
    php artisan import:products storage/products.csv --dry-run
    ```
  </Step>
</Steps>

### 定期メンテナンスコマンド

古いデータの削除など、定期的に実行するメンテナンスコマンドの例です。

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

namespace App\Console\Commands;

use App\Models\Order;
use Illuminate\Console\Command;

class PruneOldOrders extends Command
{
    protected $signature = 'orders:prune {--days=90 : 何日前より古いデータを削除するか}';

    protected $description = '指定日数より古い完了済み注文を削除する';

    public function handle(): void
    {
        $days = (int) $this->option('days');

        if (! $this->confirm("{$days}日以上前の完了済み注文を削除しますか？")) {
            $this->info('キャンセルしました。');
            return;
        }

        $deleted = Order::where('status', 'completed')
            ->where('created_at', '<', now()->subDays($days))
            ->delete();

        $this->info("{$deleted} 件の注文データを削除しました。");
    }
}
```

### スケジュール実行との組み合わせ

作成したコマンドは `routes/console.php` でスケジュール実行できます。
詳細は[タスクスケジューリング](/jp/scheduling)を参照してください。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

// 毎日深夜2時に実行
Schedule::command('orders:prune --days=90')->dailyAt('2:00');

// 毎週月曜9時に実行
Schedule::command('import:products storage/weekly.csv')->weeklyOn(1, '9:00');
```

## コマンドのテスト

Laravelのテスト機能を使って、Artisanコマンドの動作を検証できます。

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

namespace Tests\Feature\Commands;

use App\Models\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PruneOldOrdersTest extends TestCase
{
    use RefreshDatabase;

    public function test_it_deletes_old_completed_orders(): void
    {
        // 90日以上前の完了済み注文を作成
        Order::factory()->create([
            'status'     => 'completed',
            'created_at' => now()->subDays(100),
        ]);

        // 直近の完了済み注文(削除されないはず)
        Order::factory()->create([
            'status'     => 'completed',
            'created_at' => now()->subDays(10),
        ]);

        $this->artisan('orders:prune', ['--days' => 90])
            ->expectsConfirmation('90日以上前の完了済み注文を削除しますか？', 'yes')
            ->expectsOutput('1 件の注文データを削除しました。')
            ->assertExitCode(0);

        $this->assertDatabaseCount('orders', 1);
    }

    public function test_it_cancels_when_user_declines(): void
    {
        Order::factory()->create(['status' => 'completed', 'created_at' => now()->subDays(100)]);

        $this->artisan('orders:prune', ['--days' => 90])
            ->expectsConfirmation('90日以上前の完了済み注文を削除しますか？', 'no')
            ->expectsOutput('キャンセルしました。')
            ->assertExitCode(0);

        $this->assertDatabaseCount('orders', 1);
    }
}
```

<Info>
  `artisan()` メソッドで使えるアサーションメソッドの例:

  | メソッド                              | 説明                     |
  | --------------------------------- | ---------------------- |
  | `expectsOutput('...')`            | 指定したテキストが出力されることを検証    |
  | `expectsQuestion('?', '回答')`      | `ask()` の入力をシミュレート     |
  | `expectsConfirmation('?', 'yes')` | `confirm()` の応答をシミュレート |
  | `expectsChoice('?', '選択肢')`       | `choice()` の選択をシミュレート  |
  | `assertExitCode(0)`               | 終了コードを検証(0 = 成功)       |
  | `assertFailed()`                  | 終了コードが 0 以外であることを検証    |
</Info>

## よく使うコマンドまとめ

```shell theme={null}
# コマンドを新規作成
php artisan make:command CommandName

# 利用可能なコマンド一覧
php artisan list

# コマンドの使い方を確認
php artisan help command:name

# Tinkerを起動
php artisan tinker
```
