Caution: You are browsing the legacy symfony 1.x part of this website.
Cover of the book Symfony 5: The Fast Track

Symfony 5: The Fast Track is the best book to learn modern Symfony development, from zero to production. +300 pages in full color showing how to combine Symfony with Docker, APIs, queues & async tasks, Webpack, Single-Page Applications, etc.

Buy printed version

リストをページ分割する方法

1.2
Symfony version Language

概要

symfonyはページャコンポーネント: sfPropelPagerを提供します(Criteriaクラスの)Criteriaオブジェクトからの結果のリストを表示のためにページのセットに分割し、ページと結果オブジェクトにアクセスするメソッドを提供します。

sfPropelPagerオブジェクト

sfPropelPagerクラスはモデルの章で説明されているPropel抽象化レイヤーを利用します。

このチュートリアルではsfPropelPagerメソッドを使用して10×10の記事リストを表示するシンプルな例を説明します。ArticleオブジェクトはgetPublished()getTitle()getOverview()getContent()アクセサーメソッドを持ちます。

ページ分割されていないcriteriaリクエストの結果が欲しい場合、次の内容が必要になります:

class articleActions extends sfActions
{
  public function executeList()
  {
    ...
    $c = new Criteria();
    $c->add(ArticlePeer::PUBLISHED, true);
    $articles = ArticlePeer::doSelect($c);
    $this->articles = $articles;
    ...
  }
}

変数$articlesは、テンプレートからアクセス可能で、リクエストにマッチするArticleオブジェクトの配列を格納します。

ページ分割されたリストを取得するには、微妙に異なるアプローチが必要です; 配列の代わりにsfPropelPagerオブジェクトに結果が収納されなければなりません:

class articleActions extends sfActions
{
  public function executeList()
  {
    ...
    $c = new Criteria();
    $c->add(ArticlePeer::PUBLISHED, true);
    $pager = new sfPropelPager('Article', 10);
    $pager->setCriteria($c);
    $pager->setPage($this->getRequestParameter('page', 1));
    $pager->init();
    $this->pager = $pager;
    ...
  }
}

このアクションの後で、Criteriaの定義の違いの後が違います:

  • 10×10のArticleオブジェクトをページ分割するために新しいページャーを作成する
  • Criteriaからページャーへ影響を与える
  • 現在のページをリクエストされたページもしくは最初のものに設定する
  • ページャを初期化する(すなわち、criteriaに関連したリクエストを実行する)
  • 変数$pager経由でテンプレートにページャーを渡す

listSuccess.phpテンプレートはsfPropelPagerオブジェクトにアクセスできます。 このオブジェクトは現在のページとすべてのページのリストを知っています。 このオブジェクトはページとページのオブジェクトをアクセスするメソッドを持ちます。 操作する方法を見てみましょう。

結果全体の数字を表示するために、getNbResults()メソッドを使います:

<?php echo $pager->getNbResults() ?> results found.<br />
Displaying results <?php echo $pager->getFirstIndice() ?> to  <?php echo $pager->getLastIndice() ?>.

リクエストされたページの記事を表示するために、ページのオブジェクトを取得するpagerオブジェクトのgetResults()メソッドを使ってください:

<?php foreach ($pager->getResults() as $article): ?>
  <?php echo link_to($article->getTitle(), 'article/read?id='.$article->getId()) ?>
  <?php echo $article->getOverview() ?>
<?php endforeach ?>

複数のページを移動する

haveToPaginate()メソッドのおかげでページャオブジェクトは1つのページ(例では10)に表示される結果数が最大値を超えているかを知っています。

(« < > »)リストの底でナビゲーションリンクを追加するには、getfirstPage()getPreviousPage()getLastPage()などのナビゲーションメソッドを使います。 現在のページはgetPage()によって与えられます。これらすべてのメソッドは整数: リクエストされたページのランクを返します。

特定のページを指定するために、getLinks()メソッドへのコールによって取得されるリンクのコレクションをとおしてループを使います:

<?php if ($pager->haveToPaginate()): ?>
  <?php echo link_to('&laquo;', 'article/list?page='.$pager->getFirstPage()) ?>
  <?php echo link_to('&lt;', 'article/list?page='.$pager->getPreviousPage()) ?>
  <?php $links = $pager->getLinks(); foreach ($links as $page): ?>
    <?php echo ($page == $pager->getPage()) ? $page : link_to($page, 'article/list?page='.$page) ?>
    <?php if ($page != $pager->getCurrentMaxLink()): ?> - <?php endif ?>
  <?php endforeach ?>
  <?php echo link_to('&gt;', 'article/list?page='.$pager->getNextPage()) ?>
  <?php echo link_to('&raquo;', 'article/list?page='.$pager->getLastPage()) ?>
<?php endif ?>

これは次のようにレンダリングされます:

   « < 1 - 2 - 3 - 4 - 5 > »

記事がいったん表示されると、ページ分割されたリストに戻ることなく前か次の記事に直接移動できるので、カーソルが必要になります。

複数のオブジェクトでのナビゲーション

リストの範囲内にあるページ単位のナビゲーションは簡単ですが、ユーザーはオブジェクト単位で進むリストに戻ることは望まないでしょう。 sfPropelPagerオブジェクトのcursor属性は現在のオブジェクトのオフセットを保持します。

これによってreadSuccess.phpテンプレートで記事単位のナビゲーションが可能になります。 最初に、listSuccess.phpテンプレートのコードを少し修正します:

<?php $cursor = $pager->getFirstIndice(); foreach ($pager->getResults() as $article): ?>
  <?php echo link_to($article->getTitle(), 'article/read?cursor='.$cursor) ?>
  <?php echo $article->getOverview() ?>
<?php ++$cursor; endforeach ?>

readアクションはcursorパラメータを扱う方法を知る必要があります:

class articleActions extends sfActions
{
  public function executeRead()
  {
    ...
    if ($this->getRequestParameter('cursor'))
    {
      $article = $pager->getObjectByCursor($this->getRequestParameter('cursor'));
    }
    else if ($this->getRequestParameter('id'))
    {
      $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
    }
 
    // エラー
    $this->forward404Unless($article);
  }
}

getObjectByCursor($cursor)メソッドは指定したポジションでカーソルを設定し、その位置でオブジェクトを返します。

setCursor($cursor)メソッドを使ってオブジェクトの取得と結果なしでカーソルを設定できます。 カーソルがいったん設定されると、この位置(getCurrent())での現在のオブジェクトだけでなく、前のもの(getPrevious())と次のもの(getCurrent())を取得できます。

このことは少しの修正で記事単位のナビべーション用に必要なテンプレートにreadアクションが移動できることを意味します:

class articleActions extends sfActions
{
  public function executeRead()
  {
    ...
    if ($this->getRequestParameter('cursor'))
    {
      $pager->setCursor($this->getRequestParameter('cursor'));
      $previous_article = $pager->getPrevious();
      $article = $pager->getCurrent();
      $next_article = $pager->getNext();
    }
    else if ($this->getRequestParameter('id'))
    {
      $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
    }
 
    // エラー
    $this->forward404Unless($article);
  }
}

note

前もしくは次のオブジェクトが存在しない場合はgetPrevious()getNext()メソッドはnullを返します。

readSuccess.phpテンプレートは次のように見えます:

<h1><?php echo $article->getTitle() ?></h1>
<p class="overview"><?php echo $article->getOverview() ?></p>
<div class="content">
  <?php echo $article->getContent() ?>
</div>
&lt; <?php echo link_to_if($previous_article, $previous_article->getTitle(), 'article/read?id='.$previous_article->getId()) ?>
-
&gt; <?php echo link_to_if($next_article, $next_article->getTitle(), 'article/read?id='.$next_article->getId()) ?>

ソートの順序を変更する

sfPropelPagerオブジェクトはCriteriaオブジェクトに依存するので、ページャオブジェクトにソートが割り当てられる前に、criteriaでソートを指定することでページャの順序を変更する作業が行われます。

たとえば、リストナビゲーションインターフェイスにソートカラムの選択を追加できます:

class articleActions extends sfActions
{
  public function executeList()
  {
    ...
    $c = new Criteria();
    $c->add(ArticlePeer::PUBLISHED, true);
    if ($this->getRequestParameter('sort'))
    {
      $c->addAscendingOrderByColumn(ArticlePeer::translateFieldName($this->getRequestParameter('sort'), BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME));
    }
    else
    {
      // デフォルトで日付によってソートされる
      $c->addAscendingOrderByColumn(ArticlePeer::UPDATED_AT);
    }
    $pager = new sfPropelPager('Article', 10);
    $pager->setCriteria($c);
    $pager->init();
    $this->pager = $pager;
    ...
  }
}

listSuccess.phpテンプレートに次のコードを追加します:

Sort by : <?php echo link_to('Title', 'article/list?sort=title') ?> - <?php echo link_to('Id', 'article/list?sort=Id') ?>

ページごとの結果数を変更する

setMaxPerPage($max)メソッドはページャを再処理する必要なしに(init()を再び呼び出す必要がない)ページごとに表示される結果数を変更します。 パラメーターとして0の値を渡す場合、ページャーは単独のページですべての結果を表示します。

class articleActions extends sfActions
{
  public function executeList()
  {
    ...
    $c = new Criteria();
    $c->add(ArticlePeer::PUBLISHED, true);
    $pager = new sfPropelPager('Article', 10);
    $pager->setCriteria($c);
    if ($this->getRequestParameter('maxperpage'))
    {
      $pager->setMaxPerPage($this->getRequestParameter('maxperpage'));
    }
    $pager->init();
    $this->pager = $pager;
    ...
  }
}

listSuccess.phpテンプレートに次のコードを追加します:

Display : <?php echo link_to('10', 'article/list?maxperpage=10') ?> - <?php echo link_to('20', 'article/list?maxperpage=20') ?> results per page

selectメソッドを変更する

sfPropelPagerに依存するアクションのパフォーマンスを最適化する必要がある場合、シンプルなdoSelect()の代わりにdoSelectJoinXXX()を使うページャーを強制したいことがあります。 sfPropelPagerオブジェクトのsetPeerMethod()メソッドによって簡単に実現できます:

$pager->setPeerMethod('doSelectJoinUser');

ページを表示する際にページャは実際にdoSelect()クエリを処理することに注意してください。 最初のクエリ($pager->init()によって起動される)はdoCountを行うだけですが、次のコードを呼び出すことでこのメソッドをカスタマイズできます:

$pager->setPeerCountMethod('doCountJoinUser');

ページャで追加情報を保存する

ページャオブジェクトの中で特定のコンテキストを保存する必要があるかもしれません。 sfPropelPagerクラスが通常の方法でパラメーターを処理できるのはそういうわけです:

$pager->setParameter('foo', 'bar');
 
if ($pager->hasParameter('foo'))
{
  $pager->getParameter('foo');
  $pager->getParameterHolder()->removeParameter('foo');
}
 
$pager->getParameterHolder()->clearParameters();

これらのパラメーターはけっしてページャーによって直接使用されません。

カスタムパラメーターをもっと学びたいのであれば、2章を参照してください。