平井です。
以前のエントリで紹介したEthnaで「きれいなURL」を実現する方法についてです。
私が構築したアプリケーションにおいて、コードを修正することでパフォーマンスを改善できました。
内容を以下にご紹介します。

前回ご紹介したままの実装を下に再掲します。

< ?php
/**
 *  Hoge_UrlHandler.php
 */

require_once 'Net/Url/Mapper.php';

/**
 *  URLハンドラクラス
 *
 */
class Hoge_UrlHandler extends Ethna_UrlHandler
{
    /** @var array アクションマッピング */
    var $action_map = array(

      // エントリポイントでつける名前
      'index' => array(

        // ログイン
        ‘/login’ => array(
          ‘action’ => ‘login’,
        ),

        // ページ送り
        ‘/view/:category/:offset’ => array(
          ‘action’ => ‘view’,
          ‘category’ => ‘’,
          ‘offset’ => ‘’,
        ),
      ),
    );

    /**
     *  Hoge_UrlHandlerクラスのインスタンスを取得する
     */
    function &getInstance($class_name = null)
    {
        $instance =& parent::getInstance(__CLASS__);
        return $instance;
    }

    /**
     * Net_URL_MapperでURLリライティングに対応する
     *
     * @param array $http_vars
     * @link http://labs.cybozu.co.jp/blog/tsuruoka/anubis/blog_show/45
     * @return array
     */
    function requestToAction($http_vars)
    {
        if (isset($http_vars[’__url_handler__’]) == false
            || isset($this->action_map[$http_vars[’__url_handler__’]]) == false) {
            return array();
        }
        $url_handler = $http_vars[’__url_handler__’];
        $action_map = $this->action_map[$url_handler];
        // parameter fix
        $method = sprintf(”_normalizeRequest_%s”, ucfirst($url_handler));
        if (method_exists($this, $method)) {
            $http_vars = $this->$method($http_vars);
        }
        // normalize
        if (isset($http_vars[’__url_info__’])) {
            $path = $http_vars[’__url_info__’];
        } else {
            $path = “”;
        }
        list($path, $is_slash) = $this->_normalizePath($path);
        $mapper = Net_URL_Mapper::getInstance($http_vars[’__url_handler__’]);
        foreach ($this->action_map[$http_vars[’__url_handler__’]] as $key => $value) {
            $mapper->connect($key, $value);
        }
        $result = $mapper->match($path);
        $http_vars = $this->buildActionParameter($http_vars, $result[’action’]);
        unset($result[’action’]);
        $http_vars = array_merge($http_vars, $result);
        return $http_vars;
    }
}
?>

この実装で構築したアプリケーションコードに対してxdebugでプロファイルをかけてみると、上のコード内の71行目付近、

foreach ($this->action_map[$http_vars[’__url_handler__’]] as $key => $value) {
  $mapper->connect($key, $value);
}
$result = $mapper->match($path);

このループ内のconnectメソッドのコストが比較的高いことが判明しました。

この実装では、ループは$action_mapの要素の数だけループします。ループを抜けた後、matchメソッドでURLを評価します。
「マッピングの情報をすべて読み込んでから、URLを評価する」というイメージの実装になってるようですね。

私が構築したアプリケーションでは、以下のようなコードにすることでパフォーマンスを少し改善することができました。

$result = null;
foreach ($this->action_map[$http_vars['__url_handler__']] as $key => $value) {
    $mapper->connect($key, $value);
    $result = $mapper->match($path);
    if(!is_null($result)) break;
    $mapper->reset();
}

この実装では、ループ毎にmatchメソッドでURLを評価しています。
ループ内で$action_mapの要素を順に評価し、該当するアクションがあった時点でループを抜ける実装です。
connectの呼び出しが減って、matchとresetが増えるイメージになります。

パフォーマンスの厳密な評価はしていませんし、ベンチもとってませんし、アプリケーションの構成によっても変わると思います。
よく呼び出されるアクションを、$actioin_mapの上位に定義するとよさそうです。

ご意見、ご指摘をお待ちしております!

参考

EthnaでNet_URL_Mapperを使う

PEAR::Net_URL_MapperでURLルーティングを制御する

コメント

素敵ですね!次はxdebugでプロファイルする方法のエントリを是非!

2008年2月1日 金曜日  halt より

ありがとうございます!xdebugのエントリも、・・・そのうちに!(笑)

2008年2月1日 金曜日  平井 より

参考にさせていただきました。
80件あるルールの中央付近のページで時間を計測してみました。
0.025~0.027s => 0.011~0.013s 速くなっています!!

ところで、/user /user/ /user/index.html を同じアクションに渡したい場合ってどのように書けばいいのでしょうか?
/user*dmy などと書いてみましたすが、/users とかもマッチしちゃいます。
なので今は
/user /user/*dmy の2つのルールを仕方なしに書いてます。

2008年4月22日 火曜日  JK より

コメントありがとうございます。
ベンチマークもとっていただいて助かりますw

PEARのNet_URL_Mapperのmatchメソッドは文字列のマッチングを行っているだけなので、
「index.htmlがDirectoryIndexである」といった判断はできないと思います。

“/user”や”/user/”が”/user/index.html”にリライトされるような設定をmod_rewriteに書けば、
Ethna側では”/user/index.html”だけで済むような気もします。
が、mod_rewrite使うのなら、そもそもUrlHandlerいらないですねw。

なので、やはりJKさんのように複数のルールを書くしかないのではないでしょうかー。

解決方法がわかりましたら、是非教えてください!

2008年4月26日 土曜日  平井 より

/user/index.html を使用しない方針になりました。
/user/:args
で /user /user/ /user/$DIR$
すべてがマッチできるようなので、これに落ち着いてます。
おそらく “.”などが含まれると、だめなんでしょうねぇ

コードを見ていないので分かりませんが、マッチの自由度が低すぎるようなきがします、正規表現でマッチできれば色々できそうな気がするのでそのうち改造に挑戦してみようかと思います。

‘#/user/([\w]+)/(?:index.html)#’
=> array(’action’ => ‘\1′, ‘dir’ => ‘\2′, ),

のような感じで…

2008年4月29日 火曜日  JK より

Net_URL_Mapperのソースをみると、正規表現でのマッチングに対応しているようですね。
であれば、かなり自由度があがりそうですね。
ふむふむ。

2008年5月1日 木曜日  平井 より

コメントをどうぞ