CakePHP2.x系のページャー機能(paginator)を使うとルーティング(Router, routes)がうまく動かず意図しないURLになる問題について対応しました。
確認バージョン CakePHP2.10.2
問題というか使い方が難しかったです。
マニュアルにもそこまで詳しくは載っていなかったので、Cakeのソースを見ながらデバッグして確かめる必要がありました。結構な時間(8時間)を費やしてしまったので、これが誰かの参考になれば何よりです。
やりたい事
SEOを目的としてURLを簡略化・短くしたい かつ パラメータのように見えるURLは全部静的ページのようなURLにしたい
id=1のアイテムページ一覧
/items/view/1
のようなURLを
/items/1
にしたい
id=1のアイテム一覧ページの2ページ目以降
/items/view/1/page:2
のようなURLを
/items/1/2
にしたい
問題点
やりたい事の1ページ目だけだったらroutes.phpに以下を書くだけで出来ます。
Router::connect('/items/:id', array('controller' => 'items', 'action' => 'view'), array('id' => '[0-9]+'));
ただし、2ページ目以降はpaginateとroutesとの連携をうまくやらないと出来ませんでした。
対応結果
routes.php
Router::connect('/items/:id', array('controller' => 'items', 'action' => 'view'), array('id' => '[0-9]+'));
Router::connect('/items/:id/:page', array('controller' => 'items', 'action' => 'view'),
array('id' => '[0-9]+', 'page' => '[0-9]+'));
pageを追加しました。
controller
public $paginate = array(
'limit' => PAGE_LIMIT,
'order' => array('Item.id' => 'asc')
);
public function view($id = null, $page = null) {
if (!$id) {
$id = $this->params['id'];
}
if (!$page) {
$page = $this->params['page'];
}
if (isset($page) && !empty($page)) {
$this->paginate['page'] = $page;
}
$this->Paginator->settings = $this->paginate;
$items = $this->Paginator->paginate('Item');
$this->set('items', $items);
}
view
$this->Paginator->options(array('url' => array(
'controller' => strtolower($this->name),
'action' => strtolower($this->action))
));
if (isset($id) && !empty($id)) {
$this->Paginator->options['url']['id'] = $id;
}
// ページャー表示
if ($this->Paginator->hasPrev()) {
echo $this->Paginator->first(' << ');
echo $this->Paginator->prev(' < ');
}
echo $this->Paginator->numbers(array('modulus' => 5));
if ($this->Paginator->hasNext()) {
echo $this->Paginator->next(' > ');
echo $this->Paginator->last(' >> ');
}
見た目はこんなシンプルな感じになります↓ ※cssは別途設定要
別モデルの場合
controller
// Hogeモデルのデータを使いたい場合
public $paginate = array(
'Hoge' => array(
'limit' => PAGE_LIMIT,
'order' => array('Hoge.id' => 'asc')
)
);
public function view($id = null, $page = null) {
if (!$id) {
$id = $this->params['id'];
}
if (!$page) {
$page = $this->params['page'];
}
if (isset($page) && !empty($page)) {
$this->paginate['Hoge']['page'] = $page;
}
$hoges = $this->paginate('Hoge', array('Hoge.item_id' => $id));
$this->set('hoges', $hoges);
}
という感じです。
最後に
色々ぐぐったら情報は出てきたのですが、やりたいパターンが微妙に違うせいか、どれもうまくいかず、結局フレームワークを読みながらデバッグを繰り返してこれにたどり着きました。
無理にフレームワークを使いこなさないで、自分でページャーのコードを書くというやり方ももちろんあるのですが、フレームワークを勉強するという前提があった為、CakePHPのページャー・ルーティング機能を使用しての実装にこだわりました。
上に記載した例もかなりマニアックなので需要があると思いませんが、たぶん似たところではまっているケースもあると思います。その場合は、自分のコードやぐぐったら出てくる他の例で実装を試しながら、CakePHPのソースを読み込みつつ、CakePHPのコアな部分をデバッグして理解を深めていくしかなさそうです。
追記2017/10/24
$this->Paginator->settings = $settings;
で設定した後に
$this->Paginator->settings = array('conditions' => $conditions, 'page' => $page);
と設定すると上書きされて消されてしまいます。
以下のようにいっぺんに設定しないと出来ませんでした。
$settings = $this->paginate; $settings['conditions'] = $conditions; $settings['page'] = $page; $this->Paginator->settings = $settings;
追記2017/10/25
上記の例とは別にキーワード検索結果一覧を作ったのですが、どうしても1ページ目のリンクがおかしくなっていてはまりました。
Router::connect('/items/search/:keyword/:page', array('controller' => 'items', 'action' => 'search'),
array('keyword' => '.*', 'page' => '[0-9]+'));
// ↓ここが必要だった
Router::connect('/items/search/:keyword', array('controller' => 'items', 'action' => 'search'),
array('keyword' => '.*'));
原因は上記の2行目が必要だった事でした。1ページ目はページ番号がつかないので全然気づきませんでした。
本当にこのページャーとルートの設定には時間がかかってしまいました。
これが誰かのお役に立てれば何よりです。

