Laravelでコマンドラインアプリケーションを作成する


最近仕事でLaravelを使い始めました。
で、バッチ用にコマンドライン処理を作成する機会がありました。
公式ドキュメント見れば大体分かるのですが、忘れっぽいので備忘としてやったことをまとめておきます。

## 環境
Laravel Framework 5.4.0
PHP 7.0.13

## Commandクラスの雛形生成
CLIで実行するクラスは、LaravelのCommandクラスを継承して作成します。
これをartisanコマンドとして追加し、それを実行させることになります。

このクラスの雛形は、artisanを使用して生成できます。

```sh:雛型生成
$ php artisan make:command SampleCommand
```

上記の場合はSampleCommandというクラスを作成しています。
これを叩くと、app/Console/Commands配下に、以下のようにクラスが生成されます。

```php:SampleCommand.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class SampleCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:name';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        //
    }
}

```

コメント見れば大体わかりますが、

* $signature：artisanコマンドとして登録するときの識別子
* $description：コマンドの説明
* コンストラクタ
* handle：実際の処理を記述する

という感じです。

ちなみにこんな感じで叩けば

```sh:雛型生成
$ php artisan make:command sample/SampleCommand
```
app/Console/Commands/sample配下にクラスが生成され、名前空間もそれに応じた形に設定されます。

```php:sample/SampleCommand.php

<?php

namespace App\Console\Commands\sample;

use Illuminate\Console\Command;

class SampleCommand extends Command

...以下略
```

## Commandクラスをartisanコマンドとして登録する

先程作成したCommandクラスをartisanコマンドとして登録します。
app/Console/Kernel.phpの$commands配列に、作成したクラスを追加します。

```php:Kernel.php
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        Commands\SampleCommand::class
    ];
    
...以下略
```

追加できたら、artisan listでコマンド登録がされているかを確認。

```
$ php artisan list

Laravel Framework 5.4.0

...中略

 command
  command:name         Command description
```

登録したクラスのsignature、descriptionに基づいて登録したコマンドが表示されます。
これで、

```
$ php artisan command:name
```

と叩くことで、先程のCommandクラスが実行できる状態になっています。

次にCommandクラスの必要な箇所を変更してみます。

## signatureの変更
artisanコマンドとして登録するときの識別子になります。

```php:生成時
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:name';
```

### 名前空間
コロンで区切ることで、名前空間としてコマンドのグルーピングができます。
上記の例であれば「command」という名前空間になるので、artisan listでは

```
 command
  command:name         Command description
```

「command」でグルーピングして表示されます。
もちろん指定しなくても問題なく、

```php:名前空間指定無し
    protected $signature = 'name';
```

その場合は、Available Commandsとして表示されます。

```
Available commands:
...中略
  name                 Command description
```

### 実行時引数の指定
signatureには、実行時引数の定義をすることもできます。

#### 必須引数の指定

```php:必須引数の指定
    protected $signature = 'sample:sample {name}';
```

第1引数必須、引数に指定された値は、プログラム中では「name」というキーで取得できるようになります。
引数は複数指定することが可能です。

```php:必須引数の指定
    protected $signature = 'sample:sample {name} {age}';
```

引数指定無しで実行すると、こんな感じでエラーとなります。

```sh:必須引数指定なし
$ php artisan sample:sample


  [Symfony\Component\Console\Exception\RuntimeException]
  Not enough arguments (missing: "name").

```

引数の数が合わない場合もエラーとなります。

```sh:引数の数が違う
$ php artisan sample:sample laravel hoge fuga

  [Symfony\Component\Console\Exception\RuntimeException]
  Too many arguments, expected arguments "command" "name" "age".
```

引数の取得はこんな感じ。

```php:実行時引数取得
    public function handle()
    {
        $name = $this->argument("name");
        $this->info("Hello $name");
    }
```

引数指定して実行するとこんな感じになります。

```sh:必須引数指定あり
$ php artisan sample:sample laravel

Hello laravel
```

#### 任意引数の指定

```php:任意引数の指定
    protected $signature = 'sample:sample {name?}';
```

第1引数は任意になります。
値の取得方法は先程と同じです。
任意指定かつ、指定されなかった場合のデフォルト値を指定することもできます。

```php:デフォルト値の指定
    protected $signature = 'sample:sample {name=laravel}';
```

#### 引数に説明をつける

Help用に説明をつけることができます。
引数定義に、コロン区切りで記述します。

```php:引数説明の指定
    protected $signature = 'sample:sample {name=laravel : 名前を指定} {age? : 年齢を指定}';
```

指定すると、Help表示時に説明も表示されるようになります。

```sh:Help表示
$ php artisan sample:sample -h
Usage:
  command:sample [<name>] [<age>]

Arguments:
  name                  名前を指定 [default: "laravel"]
  age                   年齢を指定

Options:

...以下略
```

### オプションの指定
signatureには、実行時オプションの定義をすることもできます。

#### 基本的な指定

```php:オプションの指定
    protected $signature = 'sample:sample {--dry-run}';
```

```sh:オプション指定して実行
$ php artisan sample:sample --dry-run
```

オプションの取得はこんな感じ。

```php:オプション取得
    public function handle()
    {
        $dry_run = $this->option("dry-run");
    }
```

該当オプションの指定があった場合はtrue、なければfalseが取得されます。

#### 値を指定するオプションの指定

```php:オプションの指定
    protected $signature = 'sample:sample {--greeting=}';
```

```sh:オプション指定して実行
$ php artisan sample:sample --greeting=Hello
```

値の取得方法は先程と同じです。
オプションを省略しても、実行時エラーにはなりません。

#### 値を指定してデフォルト値も設定するオプションの指定

```php:オプションの指定
    protected $signature = 'sample:sample {--greeting=Hello}';
```

この指定の場合、実行時にオプションを設定しなくても値が取得できます。

```sh:「Good&nbsp;morning」が取得できる
$ php artisan sample:sample '--greeting=Good morning'
```

```sh:「Hello」が取得できる
$ php artisan sample:sample --greeting=
$ php artisan sample:sample --greeting
$ php artisan sample:sample
```

#### オプションに説明をつける

引数と同じく、Help用に説明をつけることができます。
オプション定義に、コロン区切りで記述します。

```php:オプション説明の指定
    protected $signature = 'sample:sample {--greeting=Hello : 挨拶を指定}';
```

指定すると、Help表示時に説明も表示されるようになります。

```sh:Help表示
$ php artisan sample:sample -h
Usage:
  command:sample [options]

Options:
      --greeting[=GREETING]  挨拶を指定 [default: "Hello"]
...以下略
```

#### オプションのショートカットを設定する

オプションの完全名の前に、パイプ区切りでショートカット名を指定できます。

```php:オプションのショートカットの指定
    protected $signature = 'sample:sample {--g|greeting=Hello : 挨拶を指定}';
```

```sh:オプションのショートカット指定で実行
$ php artisan sample:sample '-gGood evening'
```

## descriptionの変更
artisanコマンドの説明になります。

```php:descriptionを変更
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'サンプルコマンドアプリケーションです';
```

artisan list表示時、Help表示時に説明も表示されるようになります。

```sh:description表示
$ php artisan list
Laravel Framework 5.4.0

...中略

 command
  command:sample       サンプルコマンドアプリケーションです
  
...中略
  
$ php artisan command:sample -h
Usage:
  command:sample [options]

...中略
  
Help:
  サンプルコマンドアプリケーションです
```
## コンストラクタ
インジェクションしたい場合は、コンストラクタで行っておけばいいですね。

```php
    private $messageCreator;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct(MessageCreator $messageCreator)
    {
        parent::__construct();
        $this->messageCreator = $messageCreator;
    }

```

コンストラクタの段階では、実行時引数やオプションの値は取得できません。

## 処理の記述
具体的な処理はhandleに記述していきます。
実際に使ってみた機能についてまとめてみます。

#### 対話的処理
コンソールで色々できます。

```php:SampleInteractiveCommand.php
<?php
class SampleInteractiveCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'sample:interactive';


...中略
    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->info('start');

        $name = $this->ask('名前を入力してください');
        $age = $this->ask('年齢を入力してください');

        $this->info("名前 : $name");
        $this->info("年齢 : $age");
        if ($this->confirm('この内容で実行してよろしいですか?')) {
            $this->info("$name $age years old");
        } else {
            $this->info('cancel');
        }

        $this->info('end');
    }
}
```

```sh:実行イメージ
$ php artisan sample:interactive
start

 名前を入力してください:
 > laravel

 年齢を入力してください:
 > 5

名前 : laravel
年齢 : 5

 この内容で実行してよろしいですか? (yes/no) [no]:
 > yes

laravel 5 years old
end
$ php artisan sample:interactive
start

 名前を入力してください:
 > Taro

 年齢を入力してください:
 > 10

名前 : Taro
年齢 : 10

 この内容で実行してよろしいですか? (yes/no) [no]:
 > no

cancel
end
```

#### コンソールへのメッセージ出力

```php:SampleConsoleOutputCommand.php
class SampleConsoleOutputCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'sample:console-output';

... 中略

    public function handle()
    {
        $this->info('info');
        $this->line('line');
        $this->comment('comment');
        $this->question('question');
        $this->error('error');

        $this->table(
            ['名前', '年齢'],
            [
                ['Taro', 10],
                ['Laravel', 5],
            ]
        );
    }
}
```

これ実行すると

![コンソール出力結果](https://qiita-image-store.s3.amazonaws.com/0/51995/584089da-ce2e-714e-6a6a-faff2624f632.png "コンソール出力結果")

色つけたり、テーブル形式で整形した結果を画面に表示してくれます。

#### 実行終了時の戻り値
シェルで実行して、Commandの戻り値をみて云々したい、みたいな場合は、handleから整数値を戻すことで対応できます。

```php:SampleExitCodeCommand.php
class SampleExitCodeCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'sample:exit-code {--force-error : 強制的にエラー扱いにする}';

... 中略

    public function handle()
    {
        $this->info('start');

        if ($this->option('force-error')) {
            $this->error('error!');
            return config('command.exit_code.ERROR');
        }

        $this->info('end');
        return config('command.exit_code.SUCCESS');
    }
}
```

```php:config/command.php
<?php

return [
    'exit_code' => [
        'SUCCESS' => 0,
        'ERROR' => 1
    ],
];
```

```sh:実行イメージ
$ php artisan sample:exit-code
start
end
$ echo $?
0
$ php artisan sample:exit-code --force-error
start
error!
$ echo $?
1
```

#### 実行時引数のvalidation
どうやるのが望ましいものなのかよく分からなかったので、Validatorファサード使いました。

```php
class SampleValidateCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'sample:validate {name : 名前を指定} {age? : 年齢を指定}';

... 中略

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $this->info('start');

        try {

            $this->validate();

        } catch (ValidationException $e) {
            $this->error('validation error!');
            foreach ($e->validator->getMessageBag()->all() as $error) {
                $this->error($error);
            }
            return config('command.exit_code.ERROR');
        }

        $this->info('end');
        return config('command.exit_code.SUCCESS');
    }

    /**
     * Validation
     */
    private function validate()
    {
        \Validator::validate(
            array_filter($this->arguments()),
            [
                'name' => 'max:10',
                'age' => 'numeric|min:20',
            ],
            [
                'name.max' => '名前は10文字以内で入力してください',
                'age.numeric' => '年齢は数値を入力してください',
                'age.min' => '年齢は20才以上で入力してください'
            ]
        );
    }
}
```

```sh:実行イメージ
$ php artisan sample:validate laravel 20
start
end
$ php artisan sample:validate laravel123! 19
start
validation error!
名前は10文字以内で入力してください
年齢は20才以上で入力してください
```

[こういうの](https://github.com/cerbero90/command-validator)もありました。
使ってみてもいいかもしれないですね。

#### プログレスバー

```php:SampleProgressBarCommand.php
class SampleProgressBarCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'sample:progress-bar';

... 中略

    public function handle()
    {
        $count = 5;

        // 標準的なプログレスバー
        {
            $progressBar = $this->output->createProgressBar($count);
            $i = 0;
            while ($i++ < $count) {
                $this->processing();
                $progressBar->advance();
            }
            $progressBar->finish();
        }

        echo PHP_EOL;

        // プログレスバーのデザイン変更
        {
            $progressBar = $this->output->createProgressBar($count);
            $progressBar->setBarCharacter('>');
            $progressBar->setEmptyBarCharacter('-');
            $progressBar->setProgressCharacter('!');
            $i = 0;
            while ($i++ < $count) {
                $this->processing();
                $progressBar->advance();
            }
            $progressBar->finish();
        }

        echo PHP_EOL;

        // プレースホルダーでメッセージを定義して表示
        {
            $i = 0;
            $progressBar = $this->output->createProgressBar($count);
            $progressBar->setFormatDefinition('custom', ' %current%/%max% -- %message% (%currentNo%)');
            $progressBar->setFormat('custom');
            while ($i++ < $count) {
                $this->processing();
                $progressBar->setMessage('processing...');
                $progressBar->setMessage('No.' . $i, 'currentNo');
                $progressBar->advance();
            }
            $progressBar->finish();
        }

        echo PHP_EOL;
    }

    /**
     * 擬似的な処理 サンプルなのでバーの表示を見やすくするために遅延をさせる
     */
    private function processing()
    {
        sleep(1);
    }
}
```

これ実行すると、こんな感じで進捗状況が表示されます。

![laravel_command_progress_bar-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/51995/c0ca8b2e-d42d-f596-9b97-32e2399e46c9.gif "laravel_command_progress_bar-compressor.gif")


上述してきたように、既にLaravelで用意されている機能を組み合わせるだけで、簡単にコマンドラインアプリケーションが作成できます。


## サンプル

作成したサンプルソース群を以下に置いてあります。

https://github.com/nenokido2000/laravel-command-sample

## 参考ページ
以下を参考にさせていただきました。
ありがとうございました。  

https://laravel.com/docs/5.4/artisan