CakePHP 1.2 prefix付きURL生成時の注意点

※CakePHP バージョン 1.2限定の話題です。

Router::url によるURLの生成時に、prefix パラメータは考慮されない。ありがちな例として携帯用URLを作るために次のような route を追加した場合、

Router::connect('/m/:controller/:action/*',
                array('prefix' => 'mobile'));

URL生成時には prefix パラメータの有無によらず、この route がマッチする。各種ヘルパー($html->link | $paginator | $form->create)でPC用のURLに意図しないプレフィクスが付いてしまう、という問題の原因はこれである。

// どちらも /m/foo/bar になってしまう!
$url->link('link', array('controller' => 'foo', 'action' => 'bar', 'prefix' => 'mobile'));
$url->link('link', array('controller' => 'foo', 'action' => 'bar'));

この問題を回避するためのイディオムとして、prefix パラメータの他に [prefix名] => true というパラメータを設定する。

Router::connect('/m/:controller/:action/*',
                array('prefix' => 'mobile',
                      'mobile' => true));

URL生成時にはこちらのパラメータを使う。

// こちらは /m/foo/bar
$url->link('link', array('controller' => 'foo', 'action' => 'bar', 'mobile' => true));
 
// こちらは /foo/bar
$url->link('link', array('controller' => 'foo', 'action' => 'bar'));

参考:

3.4.5.5 プリフィックスルーティング(Prefix Routing) :: The Cookbook
現在は正しいコード例が示されている。
“Prefix利用時のPaginatorHelperが吐くURLが正しく表示されない” フォーラム – CakePHP Users in Japan:
cakephp.jpのフォーラムにて。検索パラメータが引き継げないのはおそらく connectNamed が設定されていないから?

なお Routing.admin によるルーティングは純粋なプレフィクスルーティングではなく、Router の中で特別扱いされているのでこの問題は生じない。また CakePHP 1.3 ではプレフィクスルーティングの機能は大きく拡張されるため、最終的にどのようになるのかは分からない(コードを斜め読みした限りでは、個々の route に個別に設定する必要は無くなり、Configure::write(‘Routing.prefixes’, array(…)); で一括指定できるようになるらしい。ただ少なくとも 1.3.0-alpha においては、1.2同様の prefix も使えるようだ)。

追記:

[prefix名] => true を設定してもうまくプレフィクスが付かない場合は、route 側にパラメータが足りない可能性が高い。最もありがちなのは名前付きパラメータを使用する場合。

// こんなURLを作りたいが => /m/foo/bar/type:99 
// こうなってしまう => /foo/bar/mobile:1/type:99
$html->link('link', array('controller' => 'foo', 'action' => 'bar', 'mobile' => true, 'type' => 99));

こういうときは Router::connectNamed を使って routes.php の中で宣言しておく必要がある。

Router::connectNamed(array('type'));

Router::connectNamed についての説明はAPIドキュメントが最も詳しい。ちなみに page や sort といった paginator 関連のパラメータはデフォルトで設定されるので改めて設定する必要はない(だからこそ気が付きにくいとも言えるが)。

3 Responses

  1. konpai より:

    初めておじゃまします。
    こちらのテキストのおかげで、立った今まで直面していた難問をようやく解くことができたばかりの者です。・・・が、どうも僕にはCakePHPのRouter::url()にはバグがあるとしか思えず、ちょっとご意見いただけないかと思ってコメントさせていただくことにしました。

    先ほど直面した現象は、以下の通りです。
    Adminルーティング有効のプロジェクトで「/admin/routers/test」という文字列をRouter::parse()にかけて配列化し、少しいじった後にRouter::url()にかけて文字列に戻した。
    返ってきた文字列は、「/routers/test/admin:1」・・・。

    以下の手順で簡単に再現できると思います。環境はCakePHP1.2.5です(万が一、1.2.6で再現しなかったらすみません。今ちょっと時間が取れないので・・・)。

    1.Adminルーティング有効化

    2.任意のコントローラーに以下のアクションを書いてアクセス。

    function test() {
    $url = “/admin/routers/test”;
    pr($url);
    echo “”;

    $url = Router::parse($url);
    pr($url);
    echo “”;

    $urlStr = Router::url($url);
    pr($urlStr);
    echo “”;

    // ※ここから先は正常なURLを取得するためのソース
    unset($url['prefix']);
    pr($url);
    echo “”;

    $urlStr = Router::url($url);
    pr($urlStr);
    echo “”;

    die;
    }

    3.出力は以下のようになります。

    /admin/routers/test

    Array
    (
    [pass] => Array
    (
    )
    [named] => Array
    (
    )
    [controller] => routers
    [action] => test
    [plugin] =>
    [prefix] => admin
    [admin] => 1
    )

    /routers/test/admin:1

    Array
    (
    [pass] => Array
    (
    )
    [named] => Array
    (
    )
    [controller] => routers
    [action] => test
    [plugin] =>
    [admin] => 1
    )

    /admin/routers/test

    問題だと感じるのは、文字列を元にRouterで構成した配列を、そのままRouterで文字列に戻すことができない、という点です。
    なぜプリフィクスルーティングを正常に機能させるために、URL配列からキー「prefix」を除去しなければならないのか。(笑)

    なにぶんにもまだCakePHPを充分に理解してはいないので、何かとんでもないカン違いをしている恐れがないではないですが・・・どうも、納得ができずにいます。
    これって、バグではなくあくまでもRouterの仕様・・・ってことになるんでしょうか・・・??

  2. tkyk より:

    konpaiさん、コメントありがとうございます。

    ご相談のコードを
    CakePHP 1.2.5 + PHP 5.2.9
    CakePHP 1.2.6 + PHP 5.3.0
    という環境で試してみましたが、
    私の環境では両方とも以下のように正しい結果が返ってきました。

    ————-
    /admin/routers/test

    Array
    (

    [prefix] => admin
    [admin] => 1
    )

    /admin/routers/test/

    Array
    (

    [admin] => 1
    )

    /admin/routers/test
    —————-

    おそらく config/routes.php 内の何かの設定が干渉しているのではないかと思われます。

    PHP/CakePHPのバージョンとroutes.phpの設定内容を公開して、
    cakephp.jp のフォーラムで相談してみてはいかがでしょうか?
    http://cakephp.jp/modules/newbb/

  3. konpai より:

    わあ、どうもすみません。(汗)とんだお手数をおかけしました。
    なんだか、CakePHPに取り組みだしてからは気をつけていてもフライング気味に質問を投げてしまうことが多いようです。まだレベル的についていけてないってことなんでしょうね。
    あとはまあ、空のCakePHPプロジェクトを比較テスト用に常備しておくくらいの工夫は不可欠なのかな。そのあたり、近いうちになんとかします。(大汗

    ただ実は、いま慌てて状況をさらっているうちに重大な書き落としに気づきました。
    問題のプロジェクトでは、複数のルーティングを併用する必要上、標準のAdminルーティングは使わずにすべてベタ打ちのプリフィクスルーティングにしてあるのをすっかり忘れていて・・・すみません。
    それで試してみたところ、標準のAdminルーティングに切り替えてみるとRouter::url()はたしかに正しく動作しました。しかしそれを解除してベタ打ちのプリフィクスAdminルーティング(下記)にすると先ほど書き込んだとおりの挙動になってしまいます。

    Router::connect(
    ‘/admin/:controller/:action/*’,
    array(
    ‘prefix’ => ‘admin’,
    ‘admin’ => true
    )
    );

    これだけだと何かが足りないということなのかどうか・・・もうちょっと自力で洗ってみようと思います。もし万一、有意義な発見でもあったらお知らせに来ます。(笑)
    どうもありがとうございました。

Leave a Reply