前回(URLルーティングを作る – part zero : mod_rewrite)はmod_rewriteを使ったURLルーティングの基本的な実装をしてみた。今回はより実際的なURLルータを作るために役立ちそうなPEAR::Net_URL_Mapperの使い方を調べてみる。

●Net_URL_Mapperのinstall

PEARコマンドでインストールできる。

pear install --alldeps Net_URL_Mapper-beta

本エントリの時点でバージョンは0.9.0(beta)。丸1年以上前のリリースなのがちょっと気になるが…。

●基本的な使い方

ウノウラボ Unoh Labs: PEAR::Net_URL_MapperでURLルーティングを制御する
http://labs.unoh.net/2007/07/pearnet_url_mapperurl.html
この記事のおさらいになるが、基本的な使い方を。

◆最も基本的な使い方

<?php
require_once "Net/URL/Mapper.php" ;
$path = $_SERVER['REQUEST_URI'] ;
$mapper = Net_URL_Mapper::getInstance() ;
$mapper->connect('/:controller/:action/:sexylady/:wiseman/:strongman/') ;
$url_param = $mapper->match($path) ;
// 結果表示
echo "path:[{$path}]\n" ;
echo "========\n" ;
if (!$url_param)
{
    echo "no match." ;
}
else
{
    var_dump($url_param) ;
}
echo "========\n" ;
?>
// requested with [http://kwappa.example.com/doronbo/cheat/drj/tzr/byk]
path:[/doronbo/cheat/drj/tzr/byk]
========
array(5) {
  ["controller"]=>
  string(7) "doronbo"
  ["action"]=>
  string(5) "cheat"
  ["sexylady"]=>
  string(3) "drj"
  ["wiseman"]=>
  string(3) "tzr"
  ["strongman"]=>
  string(3) "byk"
}
========

リクエストされたURLをスラッシュ区切りにしてパラメータ配列を生成している。

注意すべきポイントは…

【○】マッチする:
http://kwappa.example.com/doronbo/cheat/drj/tzr/byk
http://kwappa.example.com/doronbo/cheat/drj/tzr/byk/
http://kwappa.example.com/doronbo/cheat/drj/tzr/byk?foo=bar
【×】マッチしない:
http://kwappa.example.com/doronbo/cheat/drj/tzr/byk/dokurobe
http://kwappa.example.com/doronbo/cheat/drj/tzr/byk/?foo=bar

最後のスラッシュの扱いがややセンシティブなので気をつける必要がある。

◆デフォルト値を指定する

connectメソッドの第2引数はパラメータのデフォルト値を配列で指定することができる。

<?php
// 前略
$mapper->connect(
    '/:controller/:action/:sexylady/:wiseman/:strongman/',
    array(
        'action'     => 'steal',
        'sexylady'   => 'dronjo',
        'strongman'  => 'tonzura',
        'wiseman'    => 'boyakki'
    )
) ;
// 後略
?>
// requested with [http://kwappa.example.com/doronbo/]
path:[/doronbo/]
========
array(5) {
  ["action"]=>
  string(6) "steal"
  ["sexylady"]=>
  string(6) "dronjo"
  ["strongman"]=>
  string(7) "tonzura"
  ["wiseman"]=>
  string(7) "boyakki"
  ["controller"]=>
  string(7) "doronbo"
}
========
// requested with [http://kwappa.example.com/doronbo/lie/drz]
// >途中までパラメータを指定する
path:[/doronbo/lie/drz]
========
array(5) {
  ["action"]=>
  string(3) "lie"
  ["sexylady"]=>
  string(3) "drz"
  ["strongman"]=>
  string(7) "tonzura"
  ["wiseman"]=>
  string(7) "boyakki"
  ["controller"]=>
  string(7) "doronbo"
}
========

◆値のルールを指定する

connectメソッドの第2引数はパラメータのバリデートをkey => patternの配列で指定することができる。

<?php
// 前略
$mapper->connect(
    '/:controller/:action/:mechanism/:hero_id/:hero_name',
    // 初期値
    array(
        'action'    => 'sortie',
        'mechanism' => 'one',
        'hero_id'   => ,
        'hero_name' => 'gan_chan'
    ),
    // バリデートルール
    array(
        'action'    => '\p{L}+',        // アルファベット
        'mechanism' => '[a-zA-Z_]+',    // 別の書き方(アンダースコアも許可)
        'hero_id'   => '\d{1}',         // 数字1文字
        'hero_name' => '\w{5,10}'       // 5~10文字の単語
    )
) ;
// 後略
?>

**[hero_id]は数字1桁、[hero_name]**は5~10文字の単語、という制限がかかる。マッチしない場合はnullが返り、マッチしたがルール違反の場合は例外を投げる。

<?php
try
{
    $url_param = $mapper->match($path) ;
}
catch (Exception $e)
{
    echo $e->toHtml() ;
}
?>

<td align="center" bgcolor="#cccccc">
  <strong>Function</strong>
</td>

<td align="center" bgcolor="#cccccc">
  <strong>Location</strong>
</td>
<td>
  Net_URL_Mapper->match(&#8216;/yatterman/battl…&#8217;)
</td>

<td>
  /path/to/docroot/router.php:45
</td>
<td>
  {main}
</td>

<td>
</td>
Net_URL_Mapper_InvalidException: A path was found but is invalid. in /path/to/docroot/router.php on line 45
Exception trace
#
1

【○】マッチする:

// requested with [http://kwappa.example.com/yatterman/attack/spanner]
path:[/yatterman/attack/spanner]
========
array(5) {
  ["action"]=>
  string(6) "attack"
  ["mechanism"]=>
  string(7) "spanner"
  ["hero_id"]=>
  int(0)
  ["hero_name"]=>
  string(8) "gan_chan"
  ["controller"]=>
  string(9) "yatterman"
}
========
// requested with [http://kwappa.example.com/yatterman/pose/1]
path:[/yatterman/pose/1]
========
array(5) {
  ["action"]=>
  string(4) "pose"
  ["mechanism"]=>
  string(3) "one"
  ["hero_id"]=>
  string(1) "1"
  ["hero_name"]=>
  string(8) "gan_chan"
  ["controller"]=>
  string(9) "yatterman"
}
========

バリデートルールを指定しなかったときと挙動が違うので注意。

  1. {pose}が[action]にマッチ
  2. {1}が[mechanism]にマッチしない>デフォルト値を設定
  3. {1}が[hero_id]にマッチ
  4. 以降のパラメータはデフォルト値
【×】マッチしない:
http://kwappa.example.com/yatterman/escape/bicycle/0/drj/tzr/byk

**[action][mechanism][hero_id][hero_name]**まではマッチするが、それ以降のパラメータ数が合わないのでマッチしない。

【例外】Net_URL_Mapper_InvalidExceptionがthrowされる
http://kwappa.example.com/yatterman/pochittona/butamo/odaterya/kininoboru

パラメータの数はあっているが、[hero_id]{odaterya}が\d{1}にマッチしない

◆とにかくURLからパラメータを取得する

パラメータ名のprefixを[*]にすることで、URLパラメータ的には複数の値をまとめて受け取ることができる。実処理は「値にスラッシュを含められるかどうか」の違い。デフォルト値にnullを指定しておけば値なしでも大丈夫。

<?php
// 前略
$mapper->connect(
    '/:controller/:action/*params',
    // 初期値
    array(
        'action'    => 'panish',
        'params'    => null
    )
) ;
// 後略
?>
// requested with [http://kwappa.example.com/dokurobe/]
path:[/dokurobe/]
========
array(3) {
  ["action"]=>
  string(6) "panish"
  ["params"]=>
  NULL
  ["controller"]=>
  string(8) "dokurobe"
}
========
// requested with [http://kwappa.example.com/dokurobe/explode/hore/oshioki/dabe]
path:[/dokurobe/explode/hore/oshioki/dabe]
========
array(3) {
  ["action"]=>
  string(7) "explode"
  ["params"]=>
  string(17) "hore/oshioki/dabe"
  ["controller"]=>
  string(8) "dokurobe"
}
========

これをこんな感じで使うとだいぶURLルーティングっぽくなってくる。

<?php
$dokurobe_param = explode("/", $url_param['params']) ;
var_dump($dokurobe_param) ;
/*
array(3) {
  [0]=>
  string(4) "hore"
  [1]=>
  string(7) "oshioki"
  [2]=>
  string(4) "dabe"
}
*/
?>

◆パラメータからURLを生成する

Net_URL_Mapper#generateメソッドを使うことで、パラメータからURLを生成することができる。phpDocumentorでシグネチャを引っ張り出してみると…

generate (line 265)

Generate an url based on given parameters

Will attempt to find a path definition that matches the given parameters and will generate an url based on this path.

  • return: String if a rule was found, false otherwise
  • access: public

string|false generate ([array $values = array()], [array $qstring = array()], [string $anchor = ”])

  • array $values: Values to be used for the url generation
  • array $qstring: Key/value pairs for query string if needed
  • string $anchor: Anchor (fragment) if needed

コードはこんな感じ。

<?php
$url = $mapper->generate(
    // パラメータ
    array(
        'controller' => 'yatterman',
        'action'     => 'generate',
        'hero_id'    => '0',
    ),
    // クエリ
    array(
        'foo'        => 'bar',
        'hoge'       => 'piyo',
    ),
    // アンカー
    "anchor_name"
) ;
/*
    $url : /yatterman/generate/one/0/gan_chan?foo=bar&hoge=piyo#anchor_name
*/
?>

省略されたパラメータには、connectで指定したデフォルト値が入る。デフォルト値が見つからなかったりバリデートルールに違反したりするとfalseが返る。パラメータを省略した形でのURL生成はしてくれないので、使い勝手としては微妙な感じ。

generateに関連するメソッドが二つほどあるのでついでに紹介。

<?php
$mapper->setPrefix("/prefix_of_url/") ;         // スラッシュはtrimされる
$mapper->setScriptName("router_script.php") ;
$url = $mapper->generate(
    /* 前回と同じ */
) ;
/*
    $url : router_script.php/prefix_of_url/yatterman/generate/one/0/gan_chan?foo=bar&hoge=piyo#anchor_name
*/
?>

ということで思ったより長くなってしまったが、Net_URL_Mapperの使い方を追ってみた。次回(URLルーティングを作る – part two : KwappUrlRouter)はフレームワーク内で実際に使えるURLルータを設計してみる。