[PHP] ページング機能の仕組みとか作り方とか

ページング機能というのは、「複数に分かれたページの前後ページへ移動するためのナビゲーションリンク」のことで
このブログにも下の方に次のページへ移動させるためのページ番号リンクがありますよね。それです。
名称はページングだったりページネーションだったりしてますが、海外だとpaginationの方が一般的なようです。

先頭の何ページ目かまではページ番号、それ以上は三点リーダーとかでぼかしたりするGoogleライクなものや、
前後への矢印だけしか表示させなかったりするものなど、スタイルは色々ありますが
これが自作しようとすると結構面倒臭い。そしてプログラミング初心者だとまず仕組みが良く分からない。
結構よく使うのに作り方や仕組みの解説をあんまり見ない気がするので書いてみます。

10周年記念にVue.js版を書きました!
[Vue] ページネーション機能の作り方とコンポーネント作成入門

2019/09/12 Vue版に合わせて全面的に書き換え&動作サンプル追加しました

ページング機能で最低限必要な情報

以下2つは必須。

  • 最大ページ数(10ページまでなら10)
  • 現在表示しているページのページ番号(1ページ目を表示しているなら1)

ページ数とページ番号はカウントを0から始めるか1から始めるかで変わりますが、
この記事では1から始める事を前提にして説明しています。

最大ページ数

全部で何ページ必要かという意味です。
最大ページ数は次の計算で求める事が出来ます。

保存されている情報の総数 ÷ 1ページあたりに表示する情報の数

ログファイルに100件保存されていて、1ページ当たりに10件表示するなら、最大ページ数は10になるわけです。
1ページあたり8件にすると余りが出ますが、余りが出る場合はceilで繰り上げします。

$count = 54; // データの総数
$perPage = 5; // 1ページあたりのデータ件数
$totalPage = ceil($count / $perPage); // 最大ページ数

現在表示しているページ番号

現在表示しているページ番号は前や後ろのページに移動する度に変動するものなので、ページを移動する都度保存しなければなりません。
オーソドックスなのはGET値ですが、その場合表示するリンクのURLにするだけで良いので簡単です。

$page = (int) $_GET['page'];

現在表示しているページの1つ前と1つ後のページ番号の計算

現在表示しているページが最初のページ(1)なら、
次のページは+1して2、前のページは無し(0)。

現在表示しているページが3ページ目(3)なら、
次のページは+1して4、前のページは-1して2。

現在表示しているページが最後のページ(10)なら、
次のページはなし、前のページは-1して9。

要するに前のページは-1、次のページは+1すればページ番号が求められるわけですが、
単純に現在のページに加算/減算しただけではマイナスになったりするので、min/max関数で補正します。

min — 最小値を返す
max — 最大値を返す

// 前のページ番号は1と比較して大きい方を使う
$prev = max($page - 1, 1);

// 次のページ番号は最大ページ数と比較して小さい方を使う
$next = min($page + 1, $totalPage);

ダミーデータの作成

ダミーデータの作成と、最大ページ数の計算をするサンプルソースです。
データが配列に入っていればcountで数を得られます。

/**
 * ダミーデータジェネレータ
 * @param $length {int} データの数
 */
function createDummy($length) {
  $dummy = [];
  foreach(array_fill(0, $length, null) as $k => $v) {
    $dummy[] = 'Item ' . ($k + 1);
  }
  return $dummy;
}

$items = createDummy(54); //ダミーデータ
$count = count($items); // データの数
$perPage = 5; // 1ページあたりのデータ件数
$totalPage = ceil($count / $perPage); // 最大ページ数
$page = (int) $_GET['page']; // 現在のページ

/* 確認用
print "<pre>page:" . $page . "\n";
print "count:" . $count . "\n";
print "perPage:". $perPage . "\n";
print "totalPage:" . $totalPage . "</pre>";
*/

このダミーデータは以下2つのページング機能のサンプルで利用できます。

前後リンクだけのページング機能

上記を踏まえてページング機能のサンプルを挙げます。
まずは、どれだけページ数があっても表示するリンクは前後2つだけなページング。

«前へ | 次へ»

このタイプのページングでは、最初のページと最後のページにおいて以下2つのロジックが必要です。

  • 最初のページでは「前へ」を表示しない
  • 最後のページでは「次へ」を表示しない

「○○のページでは●●する」を書くならif文ですよね。

サンプルソース

/**
 * 前後ページへのリンク表示
 * @param int $totalPage 最大ページ数
 * @param int $page 現在のページ番号
 */
function paging($totalPage, $page = 1){
  $page = (int) htmlspecialchars($page);

  $prev = max($page - 1, 1); // 前のページ番号
  $next = min($page + 1, $totalPage); // 次のページ番号

  if ($page != 1) { // 最初のページ以外で「前へ」を表示
    print '<a href="?page=' . $prev . '">&laquo; 前へ</a>';
  }
  if ($page < $totalPage){ // 最後のページ以外で「次へ」を表示
    print '<a href="?page=' . $next . '">次へ &raquo;</a>';
  }

  /*確認用
  print "<pre>current:".$page."\n";
  print "next:".$next."\n";
  print "prev:".$prev."</pre>";*/
}

paging($totalPage, $_GET['page]);

ページ番号が表示されるページング機能

ページ番号が表示されるページングは「全部で何ページあるのか」や、「現在地がどこか」が分かりやすいので広く利用されています。

1 2 3 4 510 次へ»

この場合は中央に現在のページ番号が来るように処理するのが一般的です。
Googleの場合は前が5ページまで、後ろは4ページまでを番号で表示、「前へ」「次へ」を矢印で表示してあります。

現在のページ番号を中央にする場合、「現在表示しているページ番号の前後に並べるページ番号の数」が必要になります。
これは現在のページ番号に、前後に表示したいページ番号の数を足したり引いたりすれば求められますが、

$pageRange = 2; // $pageから前後に表示するページ番号の数
$start = $page - $pageRange;
$end = $page + $pageRange;

このままだと開始がマイナスになったり、最大ページ数を超えたりするので min/max関数で比較して補正します。

$start = max($page - $pageRange, 1);
$end = min($page + $pageRange, $totalPage);

これをforループにかければ、現在地を中心にしたページ番号配列が作れます

$nums = []; // ページ番号格納する
for ($i = $start; $i <= $end; $i++) {
  $nums[] = $i;
}

具体的な補正処理はサンプルソースを読んでください。

サンプルソース

現在表示しているページ番号は$_GET[“page”]に格納します。

/**
 * ページ番号リンクの表示
 * @param int $totalPage データの最大件数
 * @param int $page 現在のページ番号
 * @param int $pageRange $pageから前後何件のページ番号を表示するか
 */
function paging2($totalPage, $page = 1, $pageRange = 2){
    
  // ページ番号
  $page = (int) htmlspecialchars($page);
  
  // 前ページと次ページの番号計算
  $prev = max($page - 1, 1);
  $next = min($page + 1, $totalPage);
  
  // $startと$endはドット前後に最初と最後のページ番号リンクを表示するので、繰り上げ/繰り下げ
  $nums = []; // ページ番号格納用
  $start = max($page - $pageRange, 2); // ページ番号始点
  $end = min($page + $pageRange, $totalPage - 1); // ページ番号終点

  if ($page === 1) { // 1ページ目の場合
    $end = $pageRange * 2; // 終点再計算
  }

  // ページ番号格納
  for ($i = $start; $i <= $end; $i++) {
    $nums[] = $i;
  }
  
  //最初のページへのリンク
  if ($page > 1 && $page !== 1){
    print '<a href="?page=1" title="最初のページへ">« 最初へ</a>';
  } else {
    print '<span>« 最初へ</span>';
  }
  
  // 前のページへのリンク
  if ($page > 1) {
    print '<a href="?page=' . $prev . '" title="前のページへ">&laquo; 前へ</a>';
  } else {
    print '<span>&laquo; 前へ</span>';
  }
  
  // 最初のページ番号へのリンク
  print '<a href="?page=1">1</a>';

  if ($start > $pageRange) print "..."; // ドットの表示

  //ページリンク表示ループ
  foreach ($nums as $num) {
  	
    // 現在地のページ番号
    if ($num === $page) {
      print '<span class="current">' . $num . '</span>';
    } else {
      // ページ番号リンク表示
      print '<a href="?page='. $num .'" class="num">' . $num . '</a>';
    }

  }
  
  if (($totalPage - 1) > $end) print "..."; //ドットの表示
	
  //最後のページ番号へのリンク
  if ($page < $totalPage) {
    print '<a href="?page='. $totalPage .'">' . $totalPage . '</a>';
  } else {
    print '<span>' . $totalPage . '</span>';
  }
  
  // 次のページへのリンク
  if ($page < $totalPage){
    print '<a href="?page='.$next.'">次へ &raquo;</a>';
  } else {
    print '<span>次へ &raquo;</span>';
  }
  
  //最後のページへのリンク
  if ($page < $totalPage){
    print '<a href="?page=' . $totalPage . ' title="最後のページへへ">最後へ »</a>';
  } else {
    print '<span>最後へ »</span>';
  }
  
  /* 確認用
  print "<pre>current:".$page."\n";
  print "next:".$next."\n";
  print "prev:".$prev."\n";
  print "start:".$start."\n";
  print "end:".$end."</pre>";
  */
}

表示するデータの処理

表示しているページ番号が分かれば、表示するデータの処理も出来るようになります。

今までに挙げたサンプルは最初のページを1としているので、データが配列である場合など
処理するデータの形式によっては、表示開始点を計算する際にページ番号-1が必要となります。

以下はページ番号を渡してデータ配列にフィルターをかける関数と、
それを利用した表示のサンプルソースです。

// ページ番号でデータにフィルタかける
function filterData($page, $perPage, $data) {
  return array_filter($data, function($i) use ($page, $perPage) {
    return $i >= ($page - 1) * $perPage && $i < $page * $perPage;
  }, ARRAY_FILTER_USE_KEY);
}

$filterData = filterData($page, $perPage, $items);

print '<ol>';
foreach ($filterData as $data) {
  print '<li>' . $data . '</li>';
}
print '</ol>';

動作サンプル

paizaにサンプル置いときました。
(paizaのプレビューはGET値取れないので$pageを直接整数に書き換えないと確認できません)

PHP7系向けにクラス化してみた版

インターフェース実装、抽象クラスなど、オブジェクト指向で書き直してみたやつです。
久しぶりすぎて完全に浦島太郎でした🙄ので7系の新機能についてはコメントに説明を添えておきました。

Vue(Javascript)版もあります:
[Vue] ページネーション機能の作り方とコンポーネント作成入門

「[PHP] ページング機能の仕組みとか作り方とか」への6件のフィードバック

  1. ピンバック: NekoProjectブログ
  2. はじめまして、お世話になっております。

    ページ番号が表示されるページング機能
    について質問をさせていただきたいのですが・・・。このスクリプトをコピーしてphpファイルに保存しただけでは動作しませんよね?logファイルなど作成するのでしょうか?また、ファイル名はスクリプトのどこに格納されていますか?

    php初心者なので質問がわかりにくい場合は申し訳ございません。回答を、宜しくお願いいたします。

    返信
  3. 只隈 さん>

    >このスクリプトをコピーしてphpファイルに保存しただけでは動作しませんよね?
    ただの関数なので動作に必要な情報を引数で渡して実行すれば動きます。

    一番最後の「表示するデータの処理」にあるソースが実行サンプルです。
    省略していますが、同じファイルかinclude等して読み込んでいるファイル内に
    サンプルのページング関数があるという前提になっています。

    >logファイルなど作成するのでしょうか?また、ファイル名はスクリプトのどこに格納されていますか?
    この質問は意図がよくわかりませんでした。

    返信
  4. はじめまして、ページング機能のphp大変わかりやすく重宝しました。
    ただ一つバグがでまして・・・
    $disp=5 で、
    4ページをクリックしたときに
    « 前へ1…2345 と、なってほしいところで
    « 前へ12 345 と、表示されます。
    よろしければ修正方法をお教えください。

    返信

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください