CLIツールを簡単に作れる、"Console"

[Symfony Component Advent Calendar 2022](https://qiita.com/advent-calendar/2022/symfony-component)の7日目の記事です。

## 最初に
[Symfony](https://symfony.com)はPHPのフレームワークのひとつです。しかし、公式サイトの説明文には

> Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
  （SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。）

と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。

今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。

※記事内ではautoloadのインポートは省略します。


## CLIツールを簡単に作れる、"Console"

[Console](https://symfony.com/doc/current/components/console.html)は、ターミナル（黒い画面）で実行するコマンドラインツールを簡単に作れるコンポーネントです。Symfony以外でも動作します。

### インストール

```shell
composer require symfony/console
```

### 実行するコマンドラインを作る

まずは実行するためのコマンドラインを作ります。このコマンドラインを使って、別途作成するコマンドを実行します。

```php: app.php
#!/usr/bin/env php

<?php

require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Console\Application;

$application = new Application;

// コマンド登録
...

$application->run();
```

### コマンドを作る

次にコマンドを作ります。コマンドは `Command`クラスを拡張した子クラスを作っていきます。

```php:HelloCommand.php

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
    name: 'app:hello',
    description: 'あいさつします',
)]
class HelloCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->addArgument('name', InputArgument::REQUIRED, 'お名前') // 引数の設定
            ->addOption('with-date', 'd', InputOption::VALUE_NONE, '日にちを同時に表示します') // オプションの設定
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $name = $input->getArgument('name');

        $message = sprintf("こんにちは、%sさん。", $name);
        if ($input->getOption('with-date')) {
            $message .= sprintf("今日は、%sです。", (new \DateTime())->format('Y年m月d日'));
        }

        $io->success($message);

        return Command::SUCCESS;
    }
}
```

コマンドの設定については、Attributesで行います。`#[AsCommand]` Attributeを使い、コマンド名と概要を設定します。
引数やオプションについては`configure()`メソッド内で定義します。引数は`addArgument()`, オプションは`addOption()`メソッドを実行して設定します。

そして、実際の処理は`execute()`メソッド内に記述します。画面表示するメッセージは`SymfonyStyle`オブジェクトを使って表示します。
戻り値がintですが、実行結果によって、`Command::SUCCESS(= 0)`, `Command::Failure(= 1)`, `Command::Invalid(= 2)`を返します。

### コマンドを実行する

作ったコマンドを`add()`メソッドを使って、コマンドライン側に登録し実行できるようにします。

```php: app.php
<?php

require __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Console\Application;

$application = new Application;

// コマンド登録
$application->add(new HelloCommand()); // ここ

$application->run();
```

そして実行です。

```shell
php app.php app:hello すみだ

実行結果

 [OK] こんにちは、すみださん。

php app.php app:hello すみだ --with-date

実行結果

 [OK] こんにちは、すみださん。今日は、2022年12月07日です。                                                              
```

`php 実行コマンドファイル {コマンド名} --{オプション}[={オプションの値}] {引数}`、もしくは`php 実行コマンドファイル {コマンド名} {引数} --{オプション}[={オプションの値}]` の順で記述すると、指定されたコマンド名に引数・オプションを渡して実行できます。上記の例では`--with-date`に値を渡していませんが、これは`InputOption::VALUE_NONE`を指定したためで、これを指定するとオプション名がコマンドで指定された時点で、値としてtrueが入るようになっています。

### Symfonyの場合

Symfonyの場合、実行コマンドは作る必要がなく、Symfonyインストール時に用意された`bin/console`コマンドで実行できます。
また、各コマンドクラスはオートワイヤリングにより、コンストラクタに指定した引数が自動的にDIされます。すてき。

## まとめ

今回は`Console`を紹介しました。このコンポーネント自体も強力ですが、Symfony内で使うとさらに強力です。
Symfony利用有無を問わず、コマンドラインツールを作成する際はぜひ使って欲しいコンポーネントです。