Symfony Console
SymfonyフレームワークのConsoleコンポーネント。美しく、テスト可能なコマンドラインインターフェースを簡単に作成できます。
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
スター履歴
データ取得日時: 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]
属性やクラス拡張によるコマンド定義 - 引数とオプション: 型安全な引数・オプション処理
- 自動ヘルプ生成: コマンドの説明から自動的にヘルプメッセージを生成
- 出力装飾: カラーリングや書式設定による美しい出力
- 対話型機能: ユーザーからの入力やダイアログの表示
- プログレス表示: プログレスバーや進捗インジケーター
- テーブル出力: 構造化されたデータの表形式表示
- ツリー表示: 階層構造データの視覚的表示
- 設定ファイル: アプリケーション設定の外部化
- テスト支援: コマンドのユニットテスト機能
コアコンポーネント
- Application: コンソールアプリケーションのメインクラス
- Command: 個別コマンドの基底クラス
- Input: 引数・オプションの入力処理
- Output: 出力とフォーマットの管理
- Helper: テーブル、プログレスバー等のヘルパー機能
- 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;
});
}
}