明滅するプログラマの思索

WEBエンジニアとして勤務している一介の男が、日々気づいたことをまとめるブログです

Console CommandLine を使って汎用的なコマンドを作る

PHPPEAR にはConsole_CommandLineというライブラリが公開されています。
Console_CommandLinePHPコマンドライン型のアプリケーションを作成するためのライブラリで、引数やオプションを手軽に利用したり、出力時の強調機能、ヘルプ出力機能などを含んでいます。
今回は、Console_CommandLineを利用して、引数により処理を切り替えられる汎用的なコマンドの実装を行ってみます。

目標としては、以下のような状況を考えます。

  • コマンド用スクリプトを用意し、1つ目の引数に実際に行いたい処理を指定します。
  • 実際の処理は、引数ごとに用意された別々のスクリプトが受け持ちます。
  • 2つ目以降の引数・オプションは、対応する処理スクリプトで任意に指定できるようにします。

コマンド用スクリプト php_command.php を用意します。
引数には実際に実行する処理を指定します。
以下のような感じです。

php php_command.php insert_table "Hello" -m test

ここで、実際に実行する処理 insert_table は別スクリプト command_insert_table.php が担当することにします。

導入環境

ソフトウェア バージョン
PHP 5.6

PEAR のライブラリ Console_CommandLine を利用します。

php_command.php

まずフロントとなるスクリプトphp_command.phpを実装します。

php_command.php

<?php
if (class_exists('\Console_CommandLine') === false) {
    echo 'This program requires Console CommandLine. Try to install by PEAR.';
    exit;
}

/*
 * 実行可能な処理の一覧を用意
 */
$command_collection = [
    'insert_table' => 'InsertTable'
];

$cli_parser = new CommandLine([
    'version' => '1.0.0'
]);
foreach ($command_collection as $command => $class) {
    $cli_parser->addCommand($command);
}

$command = $cli_parser->getCommand();
if ($command) {
    $class_name = $command_collection[$command];
    new $class_name();
    exit;
}

class CommandLine extends \Console_CommandLine
{
    public function getCommand()
    {
        $res = $this->getArgcArgv();
        if (isset($res[1][1])) {
            return $res[1][1];
        }
        return false;
    }
}

$command_collection は、実行可能な処理のリストを格納します。ここに無い処理は実行対象外となります。

command_insert_table.php

実際の処理を格納するためのスクリプトの前に、その親となるクラスを定義します。

command_base.php

<?php
abstract class CommandBase
{
    const COMMAND = 'dummy';
    const VERSION = '1.0.0';
    const DESCRIPTION = '';

    protected $parse_result;

    public function __construct()
    {
        // Console::CommandLine をコールし、インスタンスを生成する
        $cli_parser = new \Console_commandLine([
            'version' => static::VERSION,
            'description' => static::DESCRIPTION
        ]);
        $command_parser = $cli_parser->addCommand(static::COMMAND);
        foreach ($this->getArguments() as $mode => $options) {
            $command_parser->addArgument($mode, $options);
        }
        foreach ($this->getOptions() as $name => $options) {
            $command_parser->addOption($name, $options);
        }
        $parse_result = $cli_parser->parse();
        if (is_null($parse_result->command_name)) {
            echo '`'.static::COMMAND.'` is Undefined Command.';
            exit;
        }
        $this->parse_result = $parse_result->command;
        $this->run();
    }

    abstract protected function run();

    protected function getArguments()
    {
        return [];
    }

    protected function getOptions()
    {
        return [];
    }
}

処理そのものは以下のようになります。

command_insert_table.php

<?php
class InsertTable extends CommandBase
{
    const COMMAND = 'insert_table';

    protected function run()
    {
        /*
         * 引数およびオプションを受け取る
         */
        $text = $this->parse_result->args['text'];
        $mode = $this->parse_result->options['mode'];

        $sql = "INSERT INTO table (id, text) VALUES(null, mysqli_real_escape_string($text))";

        switch ($mode) {
            case 'test':
                echo $sql;
                break;
            case 'execute':
            default:
                $mysqli = new mysqli("localhost", "my_user", "my_password", "world");
                mysqli_query($mysqli, $sql);
        }
        exit;
    }

    protected function getArguments()
    {
        return [
            "text" => ['description' => '挿入テキスト']
        ];
    }

    protected function getOptions()
    {
        return [
            'mode' => [
                'short_name' => '-m'
                ,'long_name' => '--mode'
                ,'action'    => 'StoreString'
                ,'description' => 'execute mode (execute | test ; default : execute)'
                ,'default'   => "execute"
            ]
        ];
    }
}

引数に insert_table を指定した場合、クラスInsertTableの run() メソッドが実行されます。

php php_command.php insert_table "Hello" -m test

上記の引数のうち、"Hello" は InsertTableの引数として、-m testInsertTableのオプションとして、それぞれパースされます。
これにより、各処理ごとに引数・オプションの指定ができるようになりました。

捕捉となりますが、コマンドphp_command.phpで定義されているクラスCommandLineConsole_CommandLineのラッパークラスで、Console_CommandLine::getCommand() メソッドを上書きしています。
デフォルトの getCommand() メソッドはその時点で引数及びオプションのチェックを行ってしまうためです。
処理を行うスクリプトによって、引数およびオプションは異なるため、ここでチェックしないようにするためにわざわざラッパークラスを用意しています。