Consoleコンポーネント¶
Consoleコンポーネントを使うと、美しくてテスト可能なコマンドラインインターフェイスを簡単に作ることができます。
Symfony2 はコマンドラインツールとしてのコマンドを作成することのできるコンソールコンポーネントが付いてきます。コンソールコマンドは、cronジョブやインポートなどのバッチジョブなどの自動更新タスクに使用されます。
インストール¶
Consoleコンポーネントをインストールする方法は何通りもあります。
- 公式Gitレポジトリ (https://github.com/symfony/Console);
- PEARコマンドでインストール ( pear.symfony.com/Console );
- Composerを使ってインストール (Packagistの symfony/console ).
ベーシックなコマンドの作成¶
Symfony2 において、コンソールコマンドを自動的に使用可能にするには、 Command ディレクトリをバンドル内に作成し、その中に提供したいコマンドを実装した Command.php という接尾辞を追加した PHP ファイルを作成してください。例えば、 Symfony Standard Edition に含まれている AcmeDemoBundle を拡張し、コマンドラインから挨拶をしようとするならば、次のように GreetCommand.php ファイルを作成してください。
// src/Acme/DemoBundle/Command/GreetCommand.php
namespace Acme\DemoBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('demo:greet')
->setDescription('Greet someone')
->addArgument('name', InputArgument::OPTIONAL, 'Who do you want to greet?')
->addOption('yell', null, InputOption::VALUE_NONE, 'If set, the task will yell in uppercase letters')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
if ($name) {
$text = 'Hello '.$name;
} else {
$text = 'Hello';
}
if ($input->getOption('yell')) {
$text = strtoupper($text);
}
$output->writeln($text);
}
}
次のように新しく作成したコンソールコマンドを実行してみましょう。
app/console demo:greet Fabien
結果として次のコマンドラインが表示されます。
Hello Fabien
また --yell オプションを使用し、大文字で表示することができます。
app/console demo:greet Fabien --yell
結果は以下の通りです。:
HELLO FABIEN
出力に色を付ける¶
テキストを出力する際に、タグの付いたテキストに色を付けることができます。例えば
// green text
$output->writeln('<info>foo</info>');
// yellow text
$output->writeln('<comment>foo</comment>');
// black text on a cyan background
$output->writeln('<question>foo</question>');
// white text on a red background
$output->writeln('<error>foo</error>');
コマンドラインの引数の使用¶
コマンドの最も興味深い部分は、指定可能な引数とオプションです。引数はスペースで区切られた文字列で、コマンドラインに続いて指定します。これは順序があり、オプションや必須項目であるという指定ができます。例えば、コマンドにオプションの last_name 引数、、必須項目の name 引数を追加してみます。
$this
// ...
->addArgument('name', InputArgument::REQUIRED, 'Who do you want to greet?')
->addArgument('last_name', InputArgument::OPTIONAL, 'Your last name?')
// ...
これで、次のようにコマンドの last_name 引数を受け取ることができるようになりました。
if ($lastName = $input->getArgument('last_name')) {
$text .= ' '.$lastName;
}
結果、コマンドは、次のように使用できるようになりました。
app/console demo:greet Fabien
app/console demo:greet Fabien Potencier
コマンドのオプションの使用方法¶
引数とは違い、オプションは指定する順番は関係がありません。そして、 --yell のようにハイフンを2つ使用し、指定します。実際は、ショートカットとして -y のようにハイフン1つ使用し、1文字で指定することもできます。オプションは 必ず 指定しなくても問題ありません。また、 dir=src のような値も有効ですし、 yell のように単純に値無しの真偽値としても有効です。
Tip
It is also possible to make an option optionally accept a value (so that --yell or yell=loud work). Options can also be configured to accept an array of values.
例として、コマンドに新しいオプションを追加してみましょう。このオプションは、表示するメッセージの回数を指定することにします。
$this
// ...
->addOption('iterations', null, InputOption::VALUE_REQUIRED, 'How many times should the message be printed?', 1)
次に、複数回このメッセージを表示するように、このコマンド内でオプションである iterations を使用します。
for ($i = 0; $i < $input->getOption('iterations'); $i++) {
$output->writeln($text);
}
これでタスクを実行すれば、 --iterations のフラグをオプションとして指定できるようになりました。
app/console demo:greet Fabien
app/console demo:greet Fabien --iterations=5
最初の例では、 iterations を渡していないので、一度だけ表示します。これは、 addOption メソッドの最後の引数でデフォルト値に 1 を指定しているからです。そして2つ目の例では、5回表示します。
オプションには、順番は関係ないので、次の例のどちらも同じように動作します。
app/console demo:greet Fabien --iterations=5 --yell
app/console demo:greet Fabien --yell --iterations=5
4つのオプションが使用できます。:
Option | Value |
---|---|
InputOption::VALUE_IS_ARRAY | このオプションは複数の値を受け取ります |
InputOption::VALUE_NONE | このオプションへの入力を受け取りません (e.g. --yell) |
InputOption::VALUE_REQUIRED | 値は必須です (e.g. iterations=5) |
InputOption::VALUE_OPTIONAL | 値はオプショナルです |
次のように VALUE_REQUIRED と VALUE_OPTIONAL を組み合わせた VALUE_IS_ARRAY も可能です。
$this
// ...
->addOption('iterations', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'How many times should the message be printed?', 1)
ユーザに情報を尋ねる¶
コマンドを作成する際に、ユーザに質問を尋ねて情報を集めることもできます。例えば、あるアクションに対して実行前にユーザに確認させるようにしたいとしましょう。次のようにしてください。
$dialog = $this->getHelperSet()->get('dialog');
if (!$dialog->askConfirmation($output, '<question>Continue with this action?</question>', false)) {
return;
}
このケースでは、ユーザに “Continue with the action” と尋ねています。そして、ユーザが y を返さなければこのタスクは実行しないようにします。 askConfirmation の3つ目の引数は、ユーザが何も入力しなかった際のデフォルト値です。
また、単なる yes/no の答え以外にも質問を尋ねることができます。例えば、何かの名前を知りたいとしましょう。その際には、次のようにします。
$dialog = $this->getHelperSet()->get('dialog');
$name = $dialog->ask($output, 'Please enter the name of the widget', 'foo');
コマンドのテスト¶
Symfony2 はコマンドを容易にテストできるようになるツールをいくつか用意しています。最も便利なものは、 Symfony\Component\Console\Tester\CommandTester クラスです。このクラスは、実際のコンソール無しでテストができるように、特別な入力と出力のクラスを使用します。
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Acme\DemoBundle\Command\GreetCommand;
class ListCommandTest extends \PHPUnit_Framework_TestCase
{
public function testExecute()
{
// mock the Kernel or create one depending on your needs
$application = new Application($kernel);
$application->add(new GreetCommand());
$command = $application->find('demo:greet');
$commandTester = new CommandTester($command);
$commandTester->execute(array('command' => $command->getName()));
$this->assertRegExp('/.../', $commandTester->getDisplay());
// ...
}
}
:method:`Symfony\\Component\\Console\\Tester\\CommandTester::getDisplay` メソッドは、コンソールからのコマンド実行で、表示されるはずの結果を返します。
Tip
Symfony\Component\Console\Tester\ApplicationTester クラスを使用すれば、全てのコンソールアプリケーションのテストもできます。
サービスコンテナからサービスを取得する¶
コマンドのベースクラスに Symfony\Component\Console\Command\Command ではなく、 Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand を使用すれば、サービスコンテナへのアクセスもできるようになります。 つまり、設定された全てのサービスにアクセスができるのです。例えば次のように、簡単にタスクを拡張して、翻訳可能にもできます。
protected function execute(InputInterface $input, OutputInterface $output)
{
$name = $input->getArgument('name');
$translator = $this->getContainer()->get('translator');
if ($name) {
$output->writeln($translator->trans('Hello %name%!', array('%name%' => $name)));
} else {
$output->writeln($translator->trans('Hello!'));
}
}
すでにあるコマンドの呼び出し¶
あるコマンドを実行する前に、他のコマンドを既に実行し終わっていないと、いけないという順番の管理が必要なときもあるでしょう。実行の順番をユーザに覚えてもらうのではなく、あなた自身で直接管理することができます。たくさんのコマンドをまとめて実行する “meta” コマンドを作成する際に便利です。 “meta” コマンドとは、例えば本番サーバのプロジェクトのコードを変更した際に、実行すべき全てのコードをまとめたものです。キャッシュのクリア、 Doctrine2 のプロクシの生成、 Assetic アセットのダンプなどなど。
コマンドから他のコマンドを呼ぶのは簡単で、次のようできます。
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find('demo:greet');
$arguments = array(
'command' => 'demo:greet',
'name' => 'Fabien',
'--yell' => true,
);
$input = new ArrayInput($arguments);
$returnCode = $command->run($input, $output);
// ...
}
まず、 :method:`Symfony\\Component\\Console\\Command\\Command::find` メソッドにコマンド名を渡し、実行したいコマンドを探します。
そして、指定したい引数とオプションを渡し Symfony\Component\Console\Input\ArrayInput クラスを新しく作成します。
最後に、 run() メソッドを呼んで、実際にコマンドを実行し、そのコマンドの戻り値を戻します。全てうまく行けば 0 が返ってきますし、何か問題があれば、他の整数値が返ってきます。
Note
ほとんどの場合、コマンドライン上で実行されないコードからコマンドを呼び出すのは、次の理由から良いアイデアではありません。まず、コマンドの出力は、コンソールのために最適化されています。しかし、より大事なこととして、コマンドをコントローラのように考えることができます。コントローラは、モデルを使用し処理を行い、ユーザにフィードバックを表示します。ウェブからコマンドを呼ぶのではなく、コードをリファクタリングして、ロジックを新しいクラスに移すべきです。