CakePHP3のルーティングでactionの引数の順番を指定する


概要

CakePHP3系のルーティングで、action側で受け取る引数の順番を指定する方法について調べる機会がありましたが、その方法が公式のドキュメントには見当たらなかったので記事として残しておきます。
もし、該当する公式のドキュメントがありましたら、コメント欄からでも教えていただけると助かります!

動作確認環境

  • CakePHP: 3.5.17
  • PHP: 7.1
  • MySQL:: 5.6

問題点

まず、前提条件として、対象ページのURLの形式が https://example.com/{ユーザー名}/{記事ID}/ のようになっており、2ページ目以降のURLの形式が https://example.com/{ユーザー名}/{記事ID}/page/{ページ番号}/ のようになるとします。
さらに、ルーティングの処理でユーザーIDと記事ID、さらに2ページ目以降であればページ番号を渡すようにして、コントローラ側のアクションでは下記のような順番で引数を受け取ることを期待するものとします。

// ページ番号が最後に来てほしい
public function view($userId, $articleId, $page = null)
{
    // いい感じの処理
}

まずは、Cookbookのルーティングの記事等を参考に、下記のように実装してみましたが、actionに入ってくる引数の順番は$articleId, $page, $userIdとなり、期待する順にはなりませんでした。

$routes->connect(
    "/{ユーザー名}/:articleId/page/:page/",
    [
        'controller' => 'Articles',
        'action' => 'view',
        // この値がactionの $page に入ってしまう
        $userId
    ]
)->setPass(['articleId', 'page']);

上記のケースでは、コントローラ側で$this->request->getParam()を利用してpage情報を取得することで、順番を指定せず対処こともできますが、setPassでaction側の引数にマッピングできることを考えれば、$userIdも含めた順番の指定ができるのではないかと考えました。

解決方法

結局、それらしい情報は見つからなかったため、関連する箇所のソースを読むことに。
\Cake\Routing\Route\Route::parse にそれらしい記述を見つけて下記の様に実装してみたところ、無事、引数の順番を指定することができました。

$routes->connect(
    "/{ユーザー名}/:articleId/page/:page/",
    [
        'controller' => 'Articles',
        'action' => 'view',
        'userId' => $userId
    ]
)->setPass(['userId', 'articleId', 'page']);

ポイントは、第二引数で$userIdを指定する際に、配列のキーに適当な文字列を当てているところです。
これでsetPassで順番を指定することができるようになり、当初想定していた順番で引数を受け取ることができるようになりました。

また、上記の方法の副次的な効果として、View側でのリンク生成時も明示的にどのパラメータの値かを指定することができます。

echo $this->Html->link(
    'Link title',
    [
        'controller' => 'Articles',
        'action' => 'view',
        'userId' => $userId,
        'articleId' => $articleId,
        'page' => $page,
    ]
);

最後に

今回紹介した方法は、ソースを見る限り特に問題なさそうですが、公式のドキュメントには記載がないため、もし利用する場合には自己責任でお願いします。

参考URL