.. index:: single: Console; CLI コンソール/コマンドラインツールとしてのコマンドの作成方法 ========================================================= Symfony2 はコマンドラインツールとしてのコマンドを作成することのできるコンソールコンポーネントが付いてきます。コンソールコマンドは、cronジョブやインポートなどのバッチジョブなどの自動更新タスクに使用されます。 ベーシックなコマンドの作成 -------------------------- 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); } } 次のように新しく作成したコンソールコマンドを実行してみましょう。 .. code-block:: bash app/console demo:greet Fabien 結果として次のコマンドラインが表示されます。 .. code-block:: text Hello Fabien また ``--yell`` オプションを使用し、大文字で表示することができます。 .. code-block:: bash app/console demo:greet Fabien --yell 結果は以下の通りです。:: HELLO FABIEN 出力に色を付ける ~~~~~~~~~~~~~~~~ テキストを出力する際に、タグの付いたテキストに色を付けることができます。例えば :: // green text $output->writeln('foo'); // yellow text $output->writeln('foo'); // black text on a cyan background $output->writeln('foo'); // white text on a red background $output->writeln('foo'); コマンドラインの引数の使用 -------------------------- コマンドの最も興味深い部分は、指定可能な引数とオプションです。引数はスペースで区切られた文字列で、コマンドラインに続いて指定します。これは順序があり、オプションや必須項目であるという指定ができます。例えば、コマンドにオプションの ``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; } 結果、コマンドは、次のように使用できるようになりました。 .. code-block:: bash app/console demo:greet Fabien app/console demo:greet Fabien Potencier コマンドのオプションの使用方法 ------------------------------ 引数とは違い、オプションは指定する順番は関係がありません。そして、 ``--yell`` のようにハイフンを2つ使用し、指定します。実際は、ショートカットとして ``-y`` のようにハイフン1つ使用し、1文字で指定することもできます。オプションは *必ず* 指定しなくても問題ありません。また、 ``dir=src`` のような値も有効ですし、 ``yell`` のように単純に値無しの真偽値としても有効です。 .. tip:: さらに、オプションに ``--yell`` や ``yell=loud`` のように、どちらでも使用できるような値を受け取らせることも *可能* です。 例として、コマンドに新しいオプションを追加してみましょう。このオプションは、表示するメッセージの回数を指定することにします。 :: $this // ... ->addOption('iterations', null, InputOption::VALUE_REQUIRED, 'How many times should the message be printed?', 1) 次に、複数回このメッセージを表示するように、このコマンド内でオプションである ``iterations`` を使用します。 .. code-block:: php for ($i = 0; $i < $input->getOption('iterations'); $i++) { $output->writeln($text); } これでタスクを実行すれば、 ``--iterations`` のフラグをオプションとして指定できるようになりました。 .. code-block:: bash app/console demo:greet Fabien app/console demo:greet Fabien --iterations=5 最初の例では、 ``iterations`` を渡していないので、一度だけ表示します。これは、 ``addOption`` メソッドの最後の引数でデフォルト値に 1 を指定しているからです。そして2つ目の例では、5回表示します。 オプションには、順番は関係ないので、次の例のどちらも同じように動作します。 .. code-block:: bash 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 も可能です。 .. code-block:: php $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, 'Continue with this action?', 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 はコマンドを容易にテストできるようになるツールをいくつか用意しています。最も便利なものは、 :class:`Symfony\\Component\\Console\\Tester\\CommandTester` クラスです。このクラスは、実際のコンソール無しでテストができるように、特別な入力と出力のクラスを使用します。 :: use Symfony\Component\Console\Tester\CommandTester; use Symfony\Bundle\FrameworkBundle\Console\Application; use Acme\DemoBundle\Command\GreetCommand.php; 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:: :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester` クラスを使用すれば、全てのコンソールアプリケーションのテストもできます。 サービスコンテナからサービスを取得する -------------------------------------- コマンドのベースクラスに :class:`Symfony\Component\Console\Command\Command` ではなく、 :class:`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` メソッドにコマンド名を渡し、実行したいコマンドを探します。 そして、指定したい引数とオプションを渡し :class:`Symfony\\Component\\Console\\Input\\ArrayInput` クラスを新しく作成します。 最後に、 ``run()`` メソッドを呼んで、実際にコマンドを実行し、そのコマンドの返り値を返します。全てうまく行けば ``0`` が返ってきますし、何か問題があれば、他の整数値が返ってきます。 .. note:: ほとんどの場合、コマンドライン上で実行されないコードからコマンドを呼び出すのは、次の理由から良いアイデアではありません。まず、コマンドの出力は、コンソールのために最適化されています。しかし、より大事なこととして、コマンドをコントローラのように考えることができます。コントローラは、モデルを使用し処理を行い、ユーザにフィードバックを表示します。ウェブからコマンドを呼ぶのではなく、コードをリファクタリングして、ロジックを新しいクラスに移すべきです。 .. 2011/11/28 ganchiku e39f5a8b06c0f1ed0634829ac9fbc02a7ac5523d