Symfony Console

SymfonyフレームワークのConsoleコンポーネント。美しく、テスト可能なコマンドラインインターフェースを簡単に作成できます。

phpclicommand-linesymfony

GitHub概要

symfony/console

Eases the creation of beautiful and testable command line interfaces

スター9,822
ウォッチ42
フォーク264
作成日:2011年2月22日
言語:PHP
ライセンス:MIT License

トピックス

clicommand-linecomponentconsolephpsymfonysymfony-componentterminal

スター履歴

symfony/console Star History
データ取得日時: 2025/7/25 02:06

フレームワーク

Symfony Console

概要

Symfony Consoleコンポーネントは、美しくテスト可能なコマンドラインインターフェースの作成を容易にするPHPライブラリです。コマンドの作成、入力引数とオプションの処理、書式設定されたテキストの出力、プロンプトやダイアログを通じたユーザーとの対話などの機能を提供します。PHPの事実上の標準となっており、Composer、PHPStan、Behatなど多くの主要なPHPツールで使用されています。最新の安定版は7.3.1、最新のLTSバージョンは6.4.23で、PHP 8.1.0以降をサポートしています。

詳細

Symfony Consoleは、コマンドライン引数の解析、ヘルプメッセージの生成、エラーハンドリング、出力の装飾など、CLIアプリケーション開発に必要な機能を包括的に提供します。単独のPHPアプリケーションでの使用はもちろん、Symfonyフレームワーク全体の一部としても利用できます。

最新動向(2024-2025年)

  • PHP 8.1.0以降サポート: 最新のPHP機能を活用可能
  • 継続的な機能拡張: 新しいヘルパーコンポーネントや出力機能の追加
  • Runtime Component統合: Symfony Runtimeとの統合により、よりシンプルなアプリケーション構成が可能
  • PolyfillPhp85: PHP 8.5の新機能を下位バージョンで利用可能にするポリフィル(2025年3月リリース)
  • コンソール補完機能: バージョン5.4以降でシェル補完スクリプトの自動生成に対応

主な特徴

  • コマンド定義: #[AsCommand]属性やクラス拡張によるコマンド定義
  • 引数とオプション: 型安全な引数・オプション処理
  • 自動ヘルプ生成: コマンドの説明から自動的にヘルプメッセージを生成
  • 出力装飾: カラーリングや書式設定による美しい出力
  • 対話型機能: ユーザーからの入力やダイアログの表示
  • プログレス表示: プログレスバーや進捗インジケーター
  • テーブル出力: 構造化されたデータの表形式表示
  • ツリー表示: 階層構造データの視覚的表示
  • 設定ファイル: アプリケーション設定の外部化
  • テスト支援: コマンドのユニットテスト機能

コアコンポーネント

  1. Application: コンソールアプリケーションのメインクラス
  2. Command: 個別コマンドの基底クラス
  3. Input: 引数・オプションの入力処理
  4. Output: 出力とフォーマットの管理
  5. Helper: テーブル、プログレスバー等のヘルパー機能
  6. Style: SymfonyStyleによる統一された出力スタイル

グローバルオプション

全てのコマンドで自動的に利用可能なオプション:

  • --verbose (-v, -vv, -vvv): 詳細レベルの設定
  • --silent: 全ての出力と対話を無効化(Symfony 7.2で追加)
  • --quiet: 出力と対話を無効化(エラーは表示)
  • --no-interaction: 対話を無効化
  • --version: バージョン番号の表示
  • --help: ヘルプの表示
  • --ansi/--no-ansi: カラー出力の制御

メリット・デメリット

メリット

  • 豊富な機能: CLI開発に必要な機能が全て揃っている
  • 高い採用実績: Composer、PHPStan、Behat等の主要ツールで使用
  • 優れたドキュメント: 充実した公式ドキュメントとチュートリアル
  • テスト容易性: コマンドのユニットテストが簡単
  • 拡張性: カスタムヘルパーやスタイルの作成が可能
  • 現代的なPHP: 最新のPHP機能を活用した設計
  • 長期サポート: LTSバージョンによる安定したサポート
  • Runtime統合: Symfony Runtimeによるシンプルな構成

デメリット

  • PHP依存: PHP専用のため他言語では使用不可
  • 学習コスト: 豊富な機能ゆえに全体を理解するのに時間がかかる
  • パフォーマンス: PHP起動時間が小さなコマンドでは影響する場合がある
  • 依存関係: 単独使用でも一定のライブラリ依存が発生

主要リンク

書き方の例

基本的なコマンド作成

<?php
// src/Command/GreetCommand.php

namespace App\Command;

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;

#[AsCommand(
    name: 'app:greet',
    description: '指定されたユーザーに挨拶します',
    hidden: false
)]
class GreetCommand extends Command
{
    protected function configure(): void
    {
        $this
            ->addArgument('name', InputArgument::REQUIRED, 'ユーザー名')
            ->addOption('yell', null, InputOption::VALUE_NONE, '大文字で表示')
            ->addOption('iterations', null, InputOption::VALUE_REQUIRED, '繰り返し回数', 1)
            ->setHelp('このコマンドは指定されたユーザーに挨拶を表示します...');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $name = $input->getArgument('name');
        $iterations = $input->getOption('iterations');
        $yell = $input->getOption('yell');

        $text = "こんにちは、{$name}さん!";
        
        if ($yell) {
            $text = strtoupper($text);
        }

        for ($i = 0; $i < $iterations; $i++) {
            $output->writeln($text);
        }

        return Command::SUCCESS;
    }
}

アプリケーションのセットアップ

#!/usr/bin/env php
<?php
// bin/console

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

use App\Command\GreetCommand;
use Symfony\Component\Console\Application;

$application = new Application('MyApp', '1.0.0');
$application->add(new GreetCommand());

$application->run();

インラインコマンドの定義

<?php

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

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

$application = new Application();

// インラインでコマンドを定義
$application->register('generate-admin')
    ->addArgument('username', InputArgument::REQUIRED, 'ユーザー名')
    ->addOption('email', null, InputOption::VALUE_REQUIRED, 'メールアドレス')
    ->setCode(function (InputInterface $input, OutputInterface $output): int {
        $username = $input->getArgument('username');
        $email = $input->getOption('email');
        
        $output->writeln("管理者ユーザーを作成中...");
        $output->writeln("ユーザー名: {$username}");
        
        if ($email) {
            $output->writeln("メール: {$email}");
        }
        
        return Command::SUCCESS;
    });

$application->run();

SymfonyStyleを使った美しい出力

<?php

namespace App\Command;

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

#[AsCommand(name: 'app:report')]
class ReportCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        // タイトル表示
        $io->title('システムレポート');

        // 成功メッセージ
        $io->success('システムは正常に動作しています');

        // 情報メッセージ
        $io->info([
            'データベース接続: 正常',
            'キャッシュ: 有効',
            'ログファイル: 書き込み可能'
        ]);

        // 警告メッセージ
        $io->warning('一部の機能がメンテナンス中です');

        // エラーメッセージ
        $io->error('重要なエラーが検出されました');

        // テーブル表示
        $io->table(
            ['ID', 'ユーザー名', 'ステータス'],
            [
                [1, '田中太郎', 'アクティブ'],
                [2, '佐藤花子', '停止中'],
                [3, '鈴木次郎', 'アクティブ']
            ]
        );

        // プログレスバー
        $io->progressStart(100);
        for ($i = 0; $i < 100; $i++) {
            usleep(50000); // 50ms待機
            $io->progressAdvance();
        }
        $io->progressFinish();

        return Command::SUCCESS;
    }
}

対話型コマンド

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(name: 'app:setup')]
class SetupCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $helper = $this->getHelper('question');

        $io->title('アプリケーションセットアップ');

        // テキスト入力
        $nameQuestion = new Question('アプリケーション名を入力してください: ', 'MyApp');
        $appName = $helper->ask($input, $output, $nameQuestion);

        // パスワード入力(非表示)
        $passwordQuestion = new Question('データベースパスワードを入力してください: ');
        $passwordQuestion->setHidden(true);
        $password = $helper->ask($input, $output, $passwordQuestion);

        // 選択肢
        $dbQuestion = new ChoiceQuestion(
            'データベースを選択してください',
            ['MySQL', 'PostgreSQL', 'SQLite'],
            'MySQL'
        );
        $database = $helper->ask($input, $output, $dbQuestion);

        // 確認質問
        $confirmQuestion = new ConfirmationQuestion(
            'セットアップを実行しますか? [y/N] ',
            false
        );

        if (!$helper->ask($input, $output, $confirmQuestion)) {
            $io->warning('セットアップがキャンセルされました');
            return Command::SUCCESS;
        }

        // セットアップ実行
        $io->section('設定内容');
        $io->definitionList(
            ['アプリケーション名' => $appName],
            ['データベース' => $database],
            ['パスワード' => str_repeat('*', strlen($password))]
        );

        $io->success('セットアップが完了しました!');

        return Command::SUCCESS;
    }
}

カスタムヘルパーの作成

<?php

namespace App\Helper;

use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Output\OutputInterface;

class DatabaseHelper extends Helper
{
    public function validateConnection(string $host, string $database, OutputInterface $output): bool
    {
        $output->writeln("データベース接続をテスト中...");
        
        // 実際の接続テストロジック
        try {
            // PDO接続テスト等
            $output->writeln("<info>データベース接続成功</info>");
            return true;
        } catch (\Exception $e) {
            $output->writeln("<error>データベース接続失敗: {$e->getMessage()}</error>");
            return false;
        }
    }

    public function getName(): string
    {
        return 'database';
    }
}

// コマンドでの使用
class DatabaseCommand extends Command
{
    protected function configure(): void
    {
        $this->getHelperSet()->set(new DatabaseHelper());
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $dbHelper = $this->getHelper('database');
        $success = $dbHelper->validateConnection('localhost', 'myapp', $output);
        
        return $success ? Command::SUCCESS : Command::FAILURE;
    }
}

高度な出力制御

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:advanced-output')]
class AdvancedOutputCommand extends Command
{
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // カスタムスタイルの定義
        $alertStyle = new OutputFormatterStyle('red', '#ff0', ['bold', 'blink']);
        $output->getFormatter()->setStyle('alert', $alertStyle);

        // カラー出力
        $output->writeln('<info>情報メッセージ</info>');
        $output->writeln('<comment>コメント</comment>');
        $output->writeln('<question>質問</question>');
        $output->writeln('<error>エラーメッセージ</error>');
        $output->writeln('<alert>カスタムアラート</alert>');

        // セクション出力
        if ($output instanceof ConsoleOutputInterface) {
            $section1 = $output->section();
            $section2 = $output->section();

            $section1->writeln('セクション1の内容');
            $section2->writeln('セクション2の内容');

            sleep(1);

            // セクション内容の上書き
            $section1->overwrite('セクション1が更新されました');
            
            sleep(1);

            // セクションのクリア
            $section2->clear();
        }

        // 詳細レベルによる出力制御
        $output->writeln('常に表示されるメッセージ');
        $output->writeln('詳細メッセージ', OutputInterface::VERBOSITY_VERBOSE);
        $output->writeln('非常に詳細なメッセージ', OutputInterface::VERBOSITY_VERY_VERBOSE);
        $output->writeln('デバッグメッセージ', OutputInterface::VERBOSITY_DEBUG);

        return Command::SUCCESS;
    }
}

テスト可能なコマンド

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:process-data')]
class ProcessDataCommand extends Command
{
    public function __construct(
        private readonly DataProcessor $dataProcessor
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('データ処理を開始します...');

        try {
            $result = $this->dataProcessor->process();
            $output->writeln("処理完了: {$result['count']} 件のデータを処理しました");
            return Command::SUCCESS;
        } catch (\Exception $e) {
            $output->writeln("<error>エラー: {$e->getMessage()}</error>");
            return Command::FAILURE;
        }
    }
}

// PHPUnitテスト
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

class ProcessDataCommandTest extends TestCase
{
    public function testExecute(): void
    {
        // モックの作成
        $dataProcessor = $this->createMock(DataProcessor::class);
        $dataProcessor->method('process')
            ->willReturn(['count' => 5]);

        // コマンドのテスト
        $command = new ProcessDataCommand($dataProcessor);
        $application = new Application();
        $application->add($command);

        $commandTester = new CommandTester($command);
        $commandTester->execute([]);

        $this->assertSame(Command::SUCCESS, $commandTester->getStatusCode());
        $this->assertStringContainsString('5 件のデータ', $commandTester->getDisplay());
    }
}

Symfony Runtimeとの統合

#!/usr/bin/env php
<?php
// bin/console

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context): Application {
    $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);

    // Applicationを返すことで、RuntimeがHTTPカーネルの代わりに
    // コンソールアプリケーションを実行する
    return new Application($kernel);
};

補完機能の実装

<?php

namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:user-info')]
class UserInfoCommand extends Command
{
    protected function configure(): void
    {
        $this->addArgument(
            'username',
            InputArgument::REQUIRED,
            'ユーザー名',
            null,
            // 補完コールバック
            function (CompletionInput $input): array {
                // ユーザーが入力中の値
                $currentValue = $input->getCompletionValue();

                // データベースやAPIからユーザー名のリストを取得
                $availableUsernames = $this->getUsernames($currentValue);

                return $availableUsernames;
            }
        );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $username = $input->getArgument('username');
        $output->writeln("ユーザー情報を表示: {$username}");

        return Command::SUCCESS;
    }

    private function getUsernames(string $currentValue): array
    {
        // 実際の実装では、データベースやAPIから取得
        $allUsernames = ['alice', 'bob', 'charlie', 'david'];
        
        // 部分一致でフィルタリング
        return array_filter($allUsernames, function($username) use ($currentValue) {
            return strpos($username, $currentValue) === 0;
        });
    }
}