Phalcon コマンドラインアプリケーション用のルーティング

  • KJ
  • 2014/09/19

Phalcon でコマンドラインアプリケーションを作成する時に MVC アプリケーションと同様にルーティング設定とかできた方がよいかと思い、コマンドラインのルーティング関連クラスを更新しました。

実装

Phalcon\Cli\Router

基本的には Phalcon\Mvc\Router と同じように処理させたいので Phalcon\Mvc\Router の実装をみながら足りない部分を追加、変更します。

Phalcon\Cli\Router::__construct

コンストラクタは引数で boolean defaultRoutes = true を取るように変更して、 defaultRoutes が真の場合は次のルーティングを設定するようにします。

  • ^(?::delimiter)?([a-zA-Z0-9\\_\\-]+)[:delimiter]{0,1}$
  • ^(?::delimiter)?([a-zA-Z0-9\\_\\-]+):delimiter([a-zA-Z0-9\\.\\_]+)(:delimiter.*)*$

:delimiter はルーティングを追加した時に Phalcon\Cli\Router\Route::getDelimiter() の値に置換されます(既定値は半角空白)。

Phalcon\Cli\Router::handle

handle では引数が配列だった場合は今までと変わらない処理とし文字列だった場合はルーティングの判別処理を行うようにします。

ルーティングの判別処理は Phalcon\Mvc\Router::handle とほとんど同じです。

  1. 設定されているルートクラス (Phalcon\Cli\Router\Route) を逆順に取得
  2. ルートクラスのパターンを確認
  3. マッチした場合はルートクラスに定義されているタスクアクションを呼び出し

Phalcon\Cli\Router::add

ルーティングの追加という処理はないので新規に作成して、ルートクラス (Phalcon\Cli\Router\Route) を追加できるようにします。

コンストラクタでデフォルトのルーティングを追加のも同じ処理になります。

Phalcon\Cli\Router::getMatchedRoute

ルーティングがマッチした時のルートクラスを取得します。

Phalcon\Cli\Router::wasMatched

ルーティングがマッチしたかどうか確認します。

この他にもいくつかメソッドを追加しています。

Phalcon\Cli\Router\Route

コマンドラインアプリケーション用のルートクラスを新規で作成します。

基本的には Phalcon\Mvc\Router\Route と同じです。

Phalcon\Cli\Router\Route::delimiter

デリミタを設定するスタティックメソッドになります。

Phalcon\Cli\Router\Route::getDelimiter

デリミタを取得するスタティックメソッドになります。

既定値は Phalcon\Cli\Router\Route::DEFAULT_DELIMITER (空白) になります。

Phalcon\Cli\Router\Route::__construct

コンストラクが呼び出された時にスタティックメンバーのデリミタを取得してクラスのプロテクテッドメンバーに設定します。

Phalcon\Cli\Router\Route::compilePattern

既定パターンの置換を行います。

  • :delimiter
  • this->_delimiter + :namespace
  • this->_delimiter + :module
  • this->_delimiter + :task
  • this->_delimiter + :action
  • this->_delimiter + :params
  • this->_delimiter + :int

this->_delimiter はクラスのデリミタメンバーになります。

Phalcon\Cli\Router\Route::reConfigure

正規表現のパターンだった場合は :delimiter の置換処理を追加します。

Phalcon\Cli\Console

Phalcon\Cli\Console クラスにルーティング処理を行いやすくするためにコマンドライン引数をパースするメソッドを追加します。

Phalcon\Cli\Console::setArguments

引数は setArgument(arguments=null, boolean str=true, boolean shift=true) に設定します。

  • arguments 1 つ目の引数は $argv が渡されることを想定しています。

  • str 2 つ目の引数は arguments 配列を implode で連結するかのフラグになります。既定値では連結処理を実行します。

  • shift 3 つ目の引数は arguments 配列を shift するかのフラグになります。既定値では arguments$argv であることは想定しているので shift 処理します。

この他、引数が - (ハイフン) で始まっていた場合はコマンドラインオプションとするような処理を追加してあります。

引数およびコマンドラインオプションはクラスメンバーに設定します。

Phalcon\Cli\Console::handle

引数が設定されいる場合は今までと同じですが、設定されていないはクラスメンバーの arguments をルーティングクラスの引数に設定するように変更しています。

クラスメンバーのコマンドラインオプションをディスパチャークラスの setOptions に渡します。

Phalcon\Cli\Dispatcher

コマンドラインオプションの設定、取得をできるようにメソッドを追加します。

Phalcon\Cli\Dispatcher::setOptions

コマンドラインオプションの設定します。

Phalcon\Cli\Dispatcher::getOptions

コマンドラインオプションの取得します。


これで Phalcon のコマンドラインアプリケーションも MVC アプリケーションと同様にルーティング設定できるようになります。

動作サンプル

動作内容

Phalcon 本体には入っていないので github.com/karakurihiden から 2.0.0-cli ブランチ git clone し、Zephir でビルドます。

Phalcon にマージされているので 2.0.0 ブランチを git clone し、Zephir でビルドます。

$ git clone -b 2.0.0 --single-branch --depth=1 https://github.com/phalcon/cphalcon.git
$ cd cphalcon
$ ../zephir/bin/zephir compile
$ cd ..

Phalcon の起動用に php.sh をスクリプト作成して、実行権限を付与します。

php.sh

#!/bin/sh
dir=$(cd $(dirname ${BASH_SOURCE:-$0});pwd)
phplibdir=$dir/cphalcon/ext/modules
configextdir=`php-config --extension-dir`
if [[ "${configextdir}" =~ versions ]]; then
    extradir=`echo ${configextdir} | sed -e 's|/versions/|/pecl/|' -e 's|/\([0-9]*\.[0-9]*\)\.[0-9]*\([^/]*\)/.*$|/\1\2|g'`/modules
fi
extension_args=
for module in json.so pdo.so
do
    if [ -f ${configextdir}/${module} ]; then
        cp ${configextdir}/${module} ${phplibdir}/${module}
    elif [ -n "${extradir}" -a -f ${extradir}/${module} ]; then
        cp ${extradir}/${module} ${phplibdir}/${module}
    fi
    extension_args="${extension_args} -d extension=${module}"
done
PHP_INI_SCAN_DIR=$dir php -d extension_dir=$phplibdir ${extension_args} -d extension=phalcon.so "$@"
$ chmod 755 php.sh

コマンドラインアプリケーションの実行ファイル(cli.php)を作成します。

cli.php

<?php
$di = new \Phalcon\DI\FactoryDefault\Cli;

$loader = new \Phalcon\Loader;
$loader->registerDirs(array(dirname(__FILE__) . '/tasks'))->register();

$console = new \Phalcon\Cli\Console;
$console->setDI($di);

try {
  $console->setArgument($argv)->handle();
} catch (\Phalcon\Exception $e) {
  echo $e->getMessage(), PHP_EOL;
  exit(255);
}

タスクディレクトリを作成します。

$ mkdir tasks

メインタスク(tasks/MainTask.php)を作成します。

MainTask.php

<?php
class mainTask extends \Phalcon\CLI\Task
{
  public function mainAction()
  {
    echo "mainAction\n";
  }
}

これでメインタスクが実行可能になったので実行してみます。

$ ./php.sh cli.php
mainAction

Phalcon\Cli\Router にてデフォルトで main タスクの main アクションを呼び出すように設定しているので引数に何も設定しない場合は main が呼ばれます。

タスク、アクションに main を設定しても同じ結果です。

$ ./php.sh cli.php main main
mainAction

テストアクションを実行してみると

$ ./php.sh cli.php main test
Action 'test' was not found on handler 'main'

実装していないのでエラーになります。

テストアクションを実装します。

引数を 2 つ取れるようにしておきます。

MainTask.php

public function testAction($arg1 = '', $arg2 = '')
{
  echo "test : 1 -> {$arg1}, 2 -> {$arg2}\n";
}

実行すると、引数もちゃんと取得できます。

$ ./php.sh cli.php main test
test : 1 -> , 2 ->
$ ./php.sh cli.php main test a
test : 1 -> a, 2 ->
$ ./php.sh cli.php main test a b
test : 1 -> a, 2 -> b

別のタスクも追加してみます。

ExampleTask.php

<?php
class exampleTask extends \Phalcon\CLI\Task
{
  public function mainAction()
  {
    echo "example:mainAction\n";
  }

  public function echoAction($arg = '')
  {
    echo "{$arg}\n";
  }
}
$ ./php.sh cli.php example
example:mainAction
$ ./php.sh cli.php example echo hoge
hoge

t で main タスクの test アクションが呼ぼれるようにルーティングを設定してみます。

cli.php

$di->setShared('router', function() {
  $router = new \Phalcon\Cli\Router(true);
  $router->add('t :params', array(
    'task' => 'main',
    'action' => 'test',
    'params' => 1
  ));
  return $router;
});
$ ./php.sh cli.php t
test : 1 -> , 2 ->
$ ./php.sh cli.php t hoge
test : 1 -> hoge, 2 ->
$ ./php.sh cli.php t hoge foo
test : 1 -> hoge, 2 -> foo

e で example タスクの echo アクションが呼ぼれるようにルーティングを設定します。

cli.php

$di->setShared('router', function() {
  $router = new \Phalcon\Cli\Router(true);
  $router->add('t :params', array(
    'task' => 'main',
    'action' => 'test',
    'params' => 1
  ));
  $router->add('e :params', array(
    'task' => 'example',
    'action' => 'echo',
    'params' => 1
  ));
  return $router;
});
$ ./php.sh cli.php e test
test

コマンドラインオプションに取得して処理を分岐するようにしてみます。

ExampleTask.php

$options = $this->dispatcher->getOptions();
if (isset($options['u'])) {
  echo "opt-u: ", strtoupper($arg), "\n";
}
if (isset($options['lower'])) {
  echo "opt-lower: ", strtolower($arg), "\n";
}
if (isset($options['func'])) {
  echo "opt-func: ", call_user_func_array($options['func'], array($arg)), "\n";
}
echo "{$arg}\n";
$ ./php.sh cli.php -u e hoge
opt-u: HOGE
hoge
$ ./php.sh cli.php e HOGE --lower
opt-lower: hoge
HOGE
$ ./php.sh cli.php e HOGE --func=strtolower
opt-func: hoge
HOGE
$ ./php.sh cli.php e hoge --func=strtoupper
opt-func: HOGE
hoge