アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

PHPでコマンドライン・アプリケーションを簡単に作成する

こんにちは、小川です。

本日は、PHPで簡単にコマンドライン・アプリケーションを作成できるライブラリをご紹介します。
(本日ご紹介するライブラリは実際には開発途中のものでドキュメントなども存在しません。こういうものがある、程度でとらえていただければ幸いです。)

さて、みなさんsymfonyはご存じでしょうか。symfonyといえば仏Sensio社が提供するオープンソースPHPフレームワークです。symfonyはいわゆるフルスタックフレームワークMVCをベースに様々な機能が提供されています。symfonyにはsymfonyコマンドが存在しており、実際の開発ではこのsymfonyコマンドをもちいて、アプリケーションの雛形やデータベース操作などをコマンドラインで行うことが可能です。

こういったコマンドラインでアプリケーションの雛形を生成したりする機能は、symfony以外のフレームワークでも広く採用されています。こういったオリジナルのコマンドを作っておけば楽なことは色々とあるでしょう。
ただ、実際に作ろうと思った場合、例えばコマンドに渡す引数の展開や、コマンドの一覧の確認であったり、コマンドの出力結果に色をつけたいなど、少々面倒なことも考慮しなくてはなりません。

そこで登場するのが、本日ご紹介する「Console」というSymfonyコンポーネントです。Consoleの前に、Symfonyコンポーネントについて説明します。


SymfonyコンポーネントSymfony 2.0

Symfonyコンポーネントは、Symfonyの内部から機能を独立させたライブラリ群です。例えばYAMLのパース/ダンプを行う「YAML」、Observerパターンをもちいてオブジェクト間で疎結合なやりとりを行うための「Event Dispatcher」、他にも現在のsymfonyではまだ使われていない、DIコンテナの実装である「Dependency Injection」などがあります。

これらは前述の通りスタンドアロンなライブラリです。Symfony ComponentsというWebサイトが別途用意されており、ここからドキュメントなども閲覧可能です。

Symfony Componentsのページをご覧になればわかると思うのですが、Consoleコンポーネントはこのページには掲載されていません。Consoleコンポーネントはまだ開発中のものです。

src/Symfony/Components/Console at master from fabpot's symfony - GitHub

現在のsymfonyの最新バージョンは1.4です。バージョン1系の開発は1.4で終了となり、次のバージョンは2.0となります。バージョン2.0からは開発リポジトリGitHubに移行されており、まだまだ開発は始まったばかりですが、Symfonyの次期バージョンの開発の様子をここから見ることが可能です。

Consoleコンポーネントもこの中に含まれています。2.0のブランチに含まれるSymfonyコンポーネントは上記のWebサイト上にあるコンポーネントとは若干違っており、PHP 5.3以上専用であること、オートローダーを個別に持っておらず、共通のオートローダーを利用する必要があるなどいくつか注意が必要となります。

現在このブランチにはオートローダーとコンポーネントしか入っていないため、とりあえず丸ごと持ってきてしまえば良いかと思います。


■Consoleコンポーネントコマンドライン・アプリケーションの作成

まずはConsoleコンポーネントを利用するために、GitHubからSymfony 2.0のリポジトリを複製します。とりあえずてきとうなディレクトリの中で次のコマンドを実行します。
利用に当たって、PHP 5.3.0以上が必須となります。
(Gitは分散型バージョン管理システムの1つです。今回は特に解説しません。)



$ git clone git://github.com/fabpot/symfony.git


これでリポジトリの複製を含むsymfonyというディレクトリができました。
何はともあれ、簡単なコマンド用スクリプトを作ります。



#!/usr/bin/env php-5.3.1
<?php
$symfonyLibDir = realpath(__DIR__ . '/symfony/src');
require $symfonyLibDir . '/Symfony/Foundation/ClassLoader.php';

$classLoader = new Symfony\Foundation\ClassLoader();
$classLoader->registerNamespace('Symfony', $symfonyLibDir);
$classLoader->register();

use Symfony\Components\Console\Application;
$application = new Application(basename(__FILE__));
$application->run();


このコードをmycommandという名前で保存し、実行権限を付与しておきます。
なお、1行目(shebang)は各自の環境に合わせて変更をお願いします。

コードの内容を説明します。3~8行目はオートローダーの設定です。10行目以降がConsoleアプリケーションの利用になります。
11行目でApplicationというクラスを生成しています。コンストラクタの第1引数にファイル名を渡していますが、ここにはコマンドの名前を指定します。コマンド名=ファイル名ですので、ここではファイル名を指定しておきます。第2引数は指定していませんが、本来はここにアプリケーションのバージョンを指定します。
作成したアプリケーションのrun()メソッドを呼び出すと、コマンド呼び出し時の引数をパースして処理してくれます。
(名前空間を利用していますが、名前空間がわからない方は以前僕が勉強会で発表した資料があるので参考にしていただければと思います。)

このコマンドの実行結果は次のようになります。



コマンドを実行してみると、下部にAvailable commandsという項目が表示されています。デフォルトではhelpとlistの2つが用意されています。Usageにある「command」という部分にコマンド名を指定するような仕組みになっています。上の画像ではcommandには何も指定していません。commandが省略された場合、listコマンドが呼び出されるようになっています。

helpコマンドは各コマンドのヘルプを表示するためのコマンドです。listコマンドのヘルプを参照したい場合、「./mycommand help list」と入力するとlistコマンドのヘルプが表示されます。


■カスタムコマンドを作成

先ほどまででコマンドライン・アプリケーション自体は作成できました。もちろんこれだけでは役に立ちません。実際にカスタムコマンドを作成します。
例として、引数で渡した文字列を大文字にして出力するコマンドを作成します。



<?php

use Symfony\Components\Console\Input\InputArgument;
use Symfony\Components\Console\Input\InputInterface;
use Symfony\Components\Console\Output\OutputInterface;
use Symfony\Components\Console\Command\Command;

class StrtolowerCommand extends Command
{
  protected function configure()
  {
    $this
      ->setDefinition(array(
        new InputArgument('string', InputArgument::REQUIRED, '文字列'),
      ))
      ->setName('strtolower')
      ->setDescription('渡された文字列を小文字に変換する')
      ->setHelp(<<<EOF
<info>strtolower</info>コマンドは渡された文字列を全て小文字に変換します:

  <info>./symfony strtolower string</info>

EOF
      );
  }

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

    $output->writeln($lowerString);
  }
}


上記をStrtolowerCommand.phpとして保存します。configure()とexecute()という2つのprotectedなメソッドを実装していますが、configure()ではコマンドの引数の設定やヘルプ・コマンド名の設定を行っています。
execute()は実際にコマンドが実行されたときの挙動です。

少し細かく見ていきます。configure()で、まずはsetDifinition()を実行しています。これは引数の定義です。そこにInputArgumentオブジェクトを渡していますが、これは引数を表すオブジェクトです。InputArgumentのコンストラクタの第1引数は引数につける名称です。第2引数にInputArgument::REQUIREDという定数を渡していますが、これは必須の引数であることを指定しています。必須でない場合はOPTIONALを指定します。第3引数は引数の説明です。第4引数は省略していますが、デフォルト値を設定したい場合はここに含めます。

setName()はコマンド名の設定です。今回はstrtolowerコマンドとします。単純に名前を設定するだけではなく、「:(コロン)」を用いて名前空間を指定することが可能です。symfony 1.4などを使ったことがある方はご存じかと思われますが、例えばDoctrineに関連するコマンドはdoctrineという名前空間に属しており、doctrine:buildやdoctrine:migrateのようなコマンド名になります。名前空間を指定しておけばコマンド名の衝突も避けられますし、listコマンドに名前空間を指定することで、特定の名前空間に属するコマンドの一覧の表示が可能になります。

また、コマンド名にはエイリアスを指定することも可能です。setAliases()メソッドに配列でエイリアス名を渡すと、その名前でもコマンドが呼び出せるようになります。

execute()では、まず入力された引数を取得しています。入力情報は第1引数の$inputに渡されるオブジェクトが保持しており、$input->getArgument("setDefinitionで指定した引数名")で取得可能です。第2引数の$outputは出力情報を扱うオブジェクトです。このオブジェクトのwriteln()メソッドに文字列を渡すことで、文字列を出力します。

ではこのコマンドを実際に実行可能にするため、mycommandファイルを開いて次のように修正します。



#!/usr/bin/env php-5.3.1
<?php
$symfonyLibDir = realpath(__DIR__ . '/symfony/src');
require $symfonyLibDir . '/Symfony/Foundation/ClassLoader.php';

$classLoader = new Symfony\Foundation\ClassLoader();
$classLoader->registerNamespace('Symfony', $symfonyLibDir);
// オートロードの指定
$classLoader->registerNamespace('', __DIR__);
$classLoader->register();

use Symfony\Components\Console\Application;

$application = new Application(basename(__FILE__));
// コマンドの追加
$application->addCommand(new StrtolowerCommand());
$application->run();




ここで重要となるのはsetDefinition()に指定する引数などの設定です。InputArgumentの他にInputOptionクラスもあります。オプションは引数と違い、--で始まるものです。例として、先ほどのStrtolowerコマンドに渡された文字列の中にスペースが入っていた場合に取り除くstrip-spacesオプションを追加してみます。



<?php

use Symfony\Components\Console\Input\InputArgument;
use Symfony\Components\Console\Input\InputOption;     // 追加(1)
use Symfony\Components\Console\Input\InputInterface;
use Symfony\Components\Console\Output\OutputInterface;
use Symfony\Components\Console\Command\Command;

class StrtolowerCommand extends Command
{
  protected function configure()
  {
    $this
      ->setDefinition(array(
        new InputArgument('string', InputArgument::REQUIRED, '文字列'),
        // 追加(2)
        new InputOption('strip-spaces', 's', InputOption::PARAMETER_NONE, '指定した場合は空白を削除'),
      ))
      ->setName('strtolower')
      ->setDescription('渡された文字列を小文字に変換する')
      ->setHelp(<<<EOF
<info>strtolower</info>コマンドは渡された文字列を全て小文字に変換します:

  <info>./symfony strtolower string</info>

EOF
      );
  }

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

    // 追加(3)
    if ($encoding = $input->getOption('strip-spaces')) {
      $strips = array(' ', ' ');
      $lowerString = str_replace($strips, array_fill(0, count($strips), ''), $lowerString);
    }

    $output->writeln($lowerString);
  }
}


StrtolowerCommand.phpに3箇所追加しました。(1)はクラスのインポートです。(2)はオプションの追加です。InputOptionのコンストラクタはInputArgument若干違い、第2引数にはショートカット名を渡します。本来は--strip-spacesと指定しますが、第2引数に文字列を指定しておけば、例えば上記の用にsと指定した場合は-sで呼び出せるようになります。第3引数はオプションに渡す値の種別です。PARAMETER_NONEは--strip-spacesのように、オプションに対して値を指定しない場合に使用します。PARAMETER_REQUIREDを指定すると、--strip-spaces=trueのように値を必ず指定しなければなりません。どちらでも良い場合はPARAMETER_OPTIONALを、同じオプションに複数の値を指定したい場合はPARAMETER_IS_ARRAYを指定します。あとはInputArgumentとほとんど同じです。

というわけでコマンドを再度実行してみます。



このようにして、コマンドの追加も簡単におこなえます。引数の指定など細かくおこなえますし、メソッドを2つ実装するだけでいいのはとても楽です。


■対話式シェルを作成

Consoleコンポーネントは対話式のシェルを作成することも可能です。



<?php

use Symfony\Components\Console\Input\InputInterface;
use Symfony\Components\Console\Output\OutputInterface;
use Symfony\Components\Console\Command\Command;
use Symfony\Components\Console\Shell;

class ShellCommand extends Command
{
  protected function configure()
  {
    $this
      ->setName('shell');
  }

  protected function execute(InputInterface $input, OutputInterface $output)
  {
    $shell = new Shell($this->application);
    $shell->run();
  }
}


このコードをShellCommand.phpという名前で保存します。忘れずにmycommandにShellCommandを追加しましょう。



<?php
// ...

$application->addCommand(new ShellCommand());
$application->run();


これで完了です。Shellというクラスを生成してrun()メソッドを呼ぶだけです。その際にコンストラクタにApplicationを渡していますが、Commandクラスの中で$this->applicationを参照するとApplicationオブジェクトが取得可能です。



このシェルのいいところは、タブを押すことで補完をしてくれることや、ホームディレクトリに .histry_アプリケーション名 というファイルを作成して履歴の保存をしてくれるところです。ちなみにCtrl+Cで抜けられます。


簡単にではありましたが、Consoleコンポーネントのご紹介は以上です。Symfony 2が開発中で、実際にSymfonyコンポーネントが将来どうなるかなどまだまだ見えないところがあると思います。
ただ、このSymfonyコンポーネントSymfony以外でも使える有用なライブラリが揃っており、また今後もどんどん増えていく予定です。Symfony Componentsのページに掲載されているコンポーネントは今回使ったものとは違い、PHP 5.3以前のものでも利用可能です。興味のある方はぜひこのSymfonyコンポーネントを使ってみてください。