July 11, 2014

Symfony 2でコントローラからコマンドを実行する

まずコマンドのクラスを作成する。

src/Acme/DemoBundle/Command/GreetCommand.php:

<?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') // 何でもOK
      ->setDescription('挨拶する')
      // 必須の引数
      ->addArgument('id', InputArgument::REQUIRED, 'ID')
      // 引数を取る任意のオプション
      ->addOption('name', 'a',
          InputOption::VALUE_REQUIRED, 'あなたの名前');
  }

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

    if (!is_numeric($id)) {
      $output->writeln("<error>IDを数字で指定してください。</error>");
      return 1; // 終了コード 1
    }

    if ($name !== null) { // nameオプションが指定された
      $msg = "${id}番の${name}さん、こんにちは。";
    } else {
      $msg = "${id}番の方、こんにちは。";
    }
    $output->writeln($msg);

    return 0; // 終了コード 0
  }
}

コマンドをsrc/Acme/DemoBundle/Resources/config/services.ymlにサービスとして追加する。

services:
  # 挨拶するコマンド
  DemoGreetCommandService:
    class: Acme\DemoBundle\Command\GreetCommand
    calls:
      - [setContainer, ["@service_container"] ]

services.xmlを使用している場合は以下のように記述する。

<services>
  <service id="DemoGreetCommandService" class="Acme\DemoBundle\Command\GreetCommand">
    <call method="setContainer">
      <argument type="service" id="service_container" />
    </call>
  </service>
</services>

コントローラでは以下のようにして呼び出す。

use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;

...

public function greetAction()
{
  $input = new ArgvInput(array(
    // 引数を並べていく。最初の引数は何でもよい。
    'demo:greet', 4, '--name', '三郎',
  ));
  $output = new ConsoleOutput();

  $greetCommand = $this->get('DemoGreetCommandService');
  $exitCode = $greetCommand->run($input, $output);
  if ($exitCode !== 0) { // コマンドが異常終了した
    return new Response('コマンド実行失敗');
  }

  return new Response('コマンドを実行しました');
}

上のようにConsoleOutputを使うと、コマンドの出力はターミナルに出力される。出力を受け取りたい場合は、MemoryWriterを以下のように使う。$output->getOutput()でコマンドの出力を受け取れる。

public function greetAction()
{
  $input = new ArgvInput(array(
    // 引数を並べていく。最初の引数は何でもよい。
    'demo:greet', 4, '--name', '三郎',
  ));
  $output = new MemoryWriter();

  $greetCommand = $this->get('DemoGreetCommandService');
  $exitCode = $greetCommand->run($input, $output);
  if ($exitCode !== 0) { // コマンドが異常終了した
    return new Response('コマンド実行失敗:' . $output->getOutput());
  }

  return new Response( $output->getOutput() );
}

また、以下のようにしてコマンド内から別のコマンドを呼び出すこともできる。

src/Acme/DemoBundle/Command/ByeCommand.php:

<?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;
use Symfony\Component\Console\Input\ArgvInput;

class ByeCommand extends ContainerAwareCommand
{
  protected function configure()
  {
    $this
      ->setName('demo:bye')
      ->setDescription('別れの挨拶をする')
      ->addArgument('id', InputArgument::REQUIRED, 'ID');
  }

  protected function execute(InputInterface $input, OutputInterface $output)
  {
    $id = $input->getArgument('id');

    $child_input = new ArgvInput(array(
      'demo:greet', $id
    ));
    $child_output = new MemoryWriter();

    $greetCommand = $this->getContainer()
      ->get('DemoGreetCommandService');
    $exitCode = $greetCommand->run($child_input, $child_output);
    if ($exitCode !== 0) {
      $output->writeln('<error>コマンド実行失敗:'
        . $child_output->getOutput() . '</error>');
      return 1;
    }
    $output->writeln( $child_output->getOutput() );

    $output->writeln('またね。');

    return 0;
  }
}

// https://github.com/predakanga/CronBundle/blob/master/Command/MemoryWriter.php
use Symfony\Component\Console\Output\Output;

/**
 * MemoryWriter implements OutputInterface, writing to an internal buffer
 */
class MemoryWriter extends Output {
  protected $backingStore = "";

  public function __construct($verbosity = self::VERBOSITY_NORMAL) {
    parent::__construct($verbosity);
  }

  public function doWrite($message, $newline) {
    $this->backingStore .= $message;
    if($newline) {
      $this->backingStore .= "\n";
    }
  }

  public function getOutput() {
    return $this->backingStore;
  }

  public function clear() {
    $this->backingStore = "";
  }
}
Posted by Nao Iizuka <iizuka@kyu-mu.net>
Powered by Bitter