カスタムクエリでのページ付け

元記事はこちら

By shird10

プロジェクトに携わったとき、テーブルから一式のデータを取得するカスタムクエリが必要になりました。結果をページネーションさせる必要もあり、サンプルをそのまま使ったり、基本のページネーションではうまく扱えないようでした。そこでpaginatorオブジェクトを使用して、カスタムモデルオブジェクトを使用してカスタムクエリを設定し、リンクを適切に構築できるようビュー向けにオプションを設定しました。

クライアント向けのアプリケーションの作成において(クライアントは”簡易的な”フォーラムを求めた)、自分のブログにいくらか記事を書いたのですが、ここでもシェアしておこうと思います。検索語が悪かったのかもしれませんがこの種の記事を私は見つけられませんでした。

この簡易フォーラムは2つのテーブルを持ちます。ForumsとPostsで、Usersテーブルと関連しています。全てのフォーラムはForumsテーブルに保持されており、トピックと投稿はPostsコントローラで取得します。モデルは互いに関連しています(ユーザは複数の投稿を持つ[User hasMany Posts]、フォーラムは複数の投稿を持つ[Forum hasMany Posts]、投稿はユーザとフォーラムに所属する[Posts belongsTo User, Posts belongsTo Forum])。全てのアクションを、コントローラ中に

var $uses = array("Forums", "Posts", "Users");

を記述してforums_controller.phpに作成します。各トピックでページネーションさせたいと思いますが、基本のページネーションは使用できません。

基本のページネーションはモデル全体とその関連付けされたものを取得するだけです。これは、Bakeメソッドがするように基本のページネーションを使用すると、トピックスも別のフォーラムに属する投稿も含めて、投稿テーブルの内容を全て取得してきてしまうということを意味します。

全てカスタマイズが必要なわけではないので、基本のページネーションを調整して、リクエストされた投稿だけ取得するようにします。そのためには、forums_controller.phpにいくらか調整が必要です。変数定義の部分で、以下のように入力しました。

var $paginate = array(
'Post'    => array(
'limit'    => 2,
'page'    => 1,
'order'    => array(
'Post.created'    => 'asc')
)
);

投稿のためのモデルオブジェクトを設定します。ビューでリンクのテストをしやすいように、とりあえずページごとに2件ずつ取得するようにし(テスト後に20に変更します)、ソート順を設定します。viewtopicsアクションを作成しました。(引数は、 viewtopic($forum = null, $topic = null) としました)

この関数は、2つの引数をとります。クエリとリンクの生成のためにforum_idとtopic_idが必要です。基本のfind関数とは違うので、ページネーションのために
conditionsを設定しなければなりません。

$cond = array('Post.parent_topic' => $topic);

変数 $topic はいわば「親」あるいはトピックの投稿です。この変数はparent_id == $topicであるデータをpaginatorが取得できるようにします。しかし「親」(トピックの投稿)も取得する必要があるので、条件を以下のように調整します。

$cond = array('OR' =>array('Post.parent_topic' => $topic, 'Post.post_id' => $topic));

これで “WHERE (`Post`.`parent_topic` = x) OR (`Post`.`post_id` = x)” となり、このトピックのすべての投稿を取得できます。これでpaginatorを呼び出せます。しかし、forums_controllerからPostモデルを呼び出しています。paginatorを呼び出す際にモデルオブジェクトを設定して、それから検索条件を設定します。

$this->set('posts', $this->paginate("Post", $cond));

これで、希望通りの投稿を検索するようになり、結果セットを配列に取り込んでビューでページネーションできるようになりました。URLの2つのパラメータが必要になるので、paginatorヘルパーをインクルードしていないところでも適切にリンクを表示できるように調整が必要です。

全ての投稿を表示できるように、viewtopicアクションを作成しました。paginatorヘルパーに関しては、ドキュメントの「4.9 ページ付け(Pagination)」を参照してください。結果の件数、何ページあるか、各ページへのナビゲーションの設定を行います。viewtopic.ctpの最初で、以下のように調整しました。

View Template:
echo $paginator->counter(array( 
'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
));

ビューの最後で、以下のように調整しました。

View Template:
<div class="paging">
    <?php echo $paginator->prev('<< '.__('Previous Page', true), array(), null, array('class'=>'disabled'));?> 
 |     <?php echo $paginator->numbers();?> 
    <?php echo $paginator->next(__('Next Page', true).' >>', array(), null, array('class'=>'disabled'));?> 
</div>

(あなたが作るものとは違うかもしれませんが、大丈夫です)基本のページネーションのビューの調整ができました。

上部はこれでOKです。問題ありません。しかし、ページ下部のナビゲーションが少し壊れています。たとえば、ポイントすると、(例): example.com/forums/viewtopic/x/y/page:2 のようになるはずです。しかし
example.com/forums/viewtopic/page:2 となり、URLにパラメータが含まれていないのでエラーになります。先に記述したpaginatorではパラメータを渡してくれないので、パラメータを渡す部分を追記する必要があります。

PaginatorのAPIドキュメントを参照すると、利用可能な関数の一覧があります。APIドキュメントや、その関数内のコードを見ることは助けになります。以下のメソッドを見てみましょう。

prev ($title= ‘<< Previous', $options=array(), $disabledTitle=null, $disabledOptions=array()) numbers ($options=array()) next ($title= 'Next >>’, $options=array(), $disabledTitle=null, $disabledOptions=array())

prev()とnext()は4つのパラメータがあります。リンクタイトル、オプション、リンクがdisableの場合のリンクタイトル、リンクがdisableの場合のオプションです。numbers()のパラメータはオプション1つだけです。リンクを活かすために必要なのはオプションの設定です。しかし、オプションの設定にはAPIドキュメント(のソースコード)に記載されている固有のキーが必要です。ここでは「$options[‘url’] – アクションのURL」に注目します。

先にあげたコードを修正してフォーラムとトピックを正しく取得できるようにするには、prevタグを以下のように調整します。

$paginator->prev('<< '.__('Previous Page', true), array('url' => $paginator->params['pass']), null, array('class'=>'disabled'));?>

読みやすくするために、各パラメータを説明します。

‘<< '.__('Previous Page', true)

—— タイトル。

array(‘url’ => $paginator->params[‘pass’])

—— 今回調整したオプション部。urlキーを渡し、paginatorオブジェクトのparams配列を渡します(リンクからあらゆるタイプのパラメータを保持している)。この例のように2つのパラメータがある場合には、あるいは10個ある場合には、全てを渡すことができます。

null
—— リンクがdisableの場合のリンクタイトル。変更なしです。

array(‘class’=>’disabled’)
—— リンクがdisableの場合のオプション。同じものを保持させます。

以下の部分をコピーして、
array(‘url’ => $paginator->params[‘pass’])
prev()、next()、numbers()メソッドにそれぞれペーストすればOKです。結果をページネーションできますし、リンクパラメータも正しく動作します。prev()、next()、numbers()は次のようになっているはずです。
example.com/forums/viewtopic/x/y/page:2 (あるいは /page:3 または /page:4 のように、現在どのページかによって変わってきます。)

これで、カスタムクエリでのページネーションが実装できました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です