Symfony2コマンドの出力先を変更 : Symfony Advent Calender 2011 JP - 4日目 -

taka512です。Symfony Advent Calendar JP 2011の4日目の記事です。
Symfonyユーザ会の大御所から引き続いての4日目ということで緊張で手がプルプル震えつつ書いてます。
今回はSymfony2コマンドを実行した際の出力先を変更する方法を紹介します。


Symfony2にはコマンドという仕組みがあります。
例えばTestCommand.phpを下記のように作成したとします。

$ vi src/Taka512/TestBundle/Command/TestCommand.php
  1 <?php
  2
  3 namespace Taka512\TestBundle\Command;
  4
  5 use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
  6 use Symfony\Component\Console\Input\InputInterface;
  7 use Symfony\Component\Console\Output\OutputInterface;
  8
  9 class TestCommand extends ContainerAwareCommand{
 10     protected function execute(InputInterface $input, OutputInterface $output){
 11         $output->writeln('TEST');
 12         $output->writeln('DAYO');
 13     }
 14     protected function configure(){
 15         $this->setName('taka512:test');
 16     }
 17 }

下記コマンドを実行すると標準出力に出力されます。

$ php app/console taka512:test
TEST
DAYO

さて、ここで自分はcronからこのコマンド実行したいと考えるようになりました。
ただし、cronから実行するには2つの修正したい点があります。
・標準出力には出力しない
・結果は最後に一度メールで通知を受けたい
「writeln」を使わなければ良いというのも良い判断だと思いますが、今回はwritelnの内容をメール送信する方法を探りました。


1.「$output」は何者か確認

まず、writelnが存在するクラスである$outputが何者かを見ていきます

$ vi app/console
  1 #!/usr/bin/env php
  2 <?php
  3
  4 require_once __DIR__.'/bootstrap.php.cache';
  5 require_once __DIR__.'/AppKernel.php';
  6
  7 use Symfony\Bundle\FrameworkBundle\Console\Application;
  8 use Symfony\Component\Console\Input\ArgvInput;
  9
 10 $input = new ArgvInput();
 11 $env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV'    ) ?: 'dev');
 12 $debug = !$input->hasParameterOption(array('--no-debug', ''));
 13
 14 $kernel = new AppKernel($env, $debug);
 15 $application = new Application($kernel);
 16 $application->run();


$ vi vendor/symfony/src/Symfony/Component/Console/Application.php 
107     public function run(InputInterface $input = null, OutputInterface $output = null)
108     {
109         if (null === $input) {
110             $input = new ArgvInput();
111         }
112
113         if (null === $output) {
114             $output = new ConsoleOutput();
115         }

「app/console」の16行目で何も引数がわたされなければ「ConsoleOutput」クラスとなるということがわかりました。
ConsoleOutputクラスをメール送信を行うクラスに置き換えができれば良さそうです。
ConsoleOutputクラスのソースは下記構成となってます。


(1)でコンソールはストリーム出力と設定
(2)でストリームへの出力処理実装
(3)で出力フォーマット処理の実装

(1) ./vendor/symfony/src/Symfony/Component/Console/Output/ConsoleOutput.php
↓を継承
(2) ./vendor/symfony/src/Symfony/Component/Console/Output/StreamOutput.php
↓を継承
(3) ./vendor/symfony/src/Symfony/Component/Console/Output/Output.php
↓を実装
(4) ./vendor/symfony/src/Symfony/Component/Console/Output/OutputInterface.php


2. メール送信クラスの実装

前項の(1)〜(2)は標準出力への出力の実装クラスであるので(3)を継承しつつ新規にメール送信クラスを作成します。
構成は下記となります。ソースは少し長くなるので貼り付けせずにリンクしてます。
ソースは 「./vendor/Taka512/lib/Taka512/Console/Output」ディレクトリを作成してファイルを置いてます。
(オートローダに「'Taka512' => __DIR__ . '/../vendor/Taka512/lib',」を登録する必要があります)


(1)バッファされた結果をメールする
https://github.com/taka512/Symfony2-lib/blob/master/lib/Taka512/Console/Output/MailOutput.php
(2)writelnされた結果をバッファ+バッファをフラッシュするflushメソッドを追加
https://github.com/taka512/Symfony2-lib/blob/master/lib/Taka512/Console/Output/BufferOutput.php

(1) ./vendor/Taka512/lib/Taka512/Console/Output/MailOutput.php
↓を継承
(2) ./vendor/Taka512/lib/Taka512/Console/Output/BufferOutput.php
↓を継承
(3) ./vendor/symfony/src/Symfony/Component/Console/Output/Output.php
↓を実装
(4) ./vendor/symfony/src/Symfony/Component/Console/Output/OutputInterface.php


3. flushメソッドの追加

前項の(2)で実装したflushメソッドを「TestCommand.php」に追加します。

$ vi src/Taka512/TestBundle/Command/TestCommand.php
  9 class TestCommand extends ContainerAwareCommand{
 10     protected function execute(InputInterface $input, OutputInterface $output){
 11         $output->writeln('TEST');
 12         $output->writeln('DAYO');
 13         $output->flush();  // 追加
 13     }


4. ConsoleOutputクラスをMailOutputクラスに置換

まだ、「$output」はConsoleOutputクラスのままなのでここをMailOutputクラスに置き換えを行います。
「app/console」ファイルは修正すると危険なので「app/batch」ファイルを新規に作成します。
16行目と25、26行目を追加してます。

$ cp app/console app/batch
$ vi app/batch
  1 #!/usr/bin/env php
  2 <?php
  3
  4 // if you don't want to setup permissions the proper way, just uncomment the following PHP line
  5 // read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information
  6 //umask(0000);
  7
  8 set_time_limit(0);
  9
 10 require_once __DIR__.'/bootstrap.php.cache';
 11 require_once __DIR__.'/AppKernel.php';
 12
 13 use Symfony\Bundle\FrameworkBundle\Console\Application;
 14 use Symfony\Component\Console\Input\ArgvInput;
 15 // add
 16 use Taka512\Console\Output\MailOutput;
 17
 18 $input = new ArgvInput();
 19 $env = $input->getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
 20 $debug = !$input->hasParameterOption(array('--no-debug', ''));
 21
 22 $kernel = new AppKernel($env, $debug);
 23 $application = new Application($kernel);
 24 // need Container
 25 $kernel->boot();
 26 $output = new MailOutput($kernel->getContainer()->get('mailer'));
 27 $application->run(null, $output);


これでコマンドを実行すると標準出力には何も出力されずにメールが送信されるようになります。

$ php app/batch taka512:test
$

以上長くなってしまったのですが、Symfony2の挙動を変更したい場合の例を紹介してみました。
(実際はメールの送信/受信先をパラメータから取得するとか他にも処理を追加する必要があります)
ということで5日目はたくさんネタを持ってそうなfivestrさんです。よろしくお願いします。