PHPで途中のページ番号を「…」で省略のページネーションを作った

code

Udemy「PHP+MySQL Webサーバーサイドプログラミング入門」のSec5「Twitter風ひとここ掲示板」の型を引き継いで「簡単SNS」を制作していく際に投稿が増え、ページングも工夫してみようと思い、「PHP ページネーション」と調べてもなかなか望む形のものにヒットせず…やっと見つけたのが「WebTecNote」さんのこちらの記事でした。2009/10/22と古いものですが、僕が見た中ではコードも解説もわかりやすく参考にさせて頂きました。今回はそのコードを拝借し、自分なりの変更点なども含めてまとめていきます。

ちなみに「ページネーション・ページング」とは1ページに全て表示するのは見にくいので1ページにつき10件などの区切りをつけて1ページ目10件、2ページ目10件…と「次へ」や「前へ」でリンクさせるものです。ページの上部にあるページもありますが、Googleなどでよく見る下部に配置してあるパターンが一般的であり、CSSで見た目を変更することでオシャレな見た目を演出することも可能です。

目指す完成形

今回目標としていたのは全てのページ番号を表示せず途中で「…」になるものです。5ページ目なので前後2ページを案内するものを作ります。

  • 最初の「1」
  • 最後の「10」
  • 前へ
  • 次へ
  • 最初へ
  • 最後へ

を備えています。「1があるなら“最初へ”は要らない、10があるなら“最後へ”も要らない」という考えもあるかと思いますが、その辺りは好みの問題でしょうか?また1ページ目の時は「1,2,3,…,10

10ページ目の時は「1,…,8,9,10」と表示します。この途中省略のアルゴリズムが思いつかず苦戦していました…

ちなみにUdemyの講座で制作していた時点でのページネーションは「前へ」「次へ」しかないので少し物足りない印象でした。少し調べるとページ数を全て表示するものが見つかりました。

「10」までならありそうなデザインですが、これが20、30ページとなると全て表示は難しそうです。このコードを先に載せておきます。$pageが現在のページ、$maxPageが最大ページ数です。$_REQUEST[‘page’]で取得し、$maxPageはceil($cnt[‘cnt’] / $show_posts);でデータ件数を取得したものを1ページに表示する件数で割って、ceilで切り上げています。このあたりは探せば解説があるはずです。

<ul class="paging">
   <?php if ($page > 1) : ?>
      <li><a href="index.php?page=<?php print($page - 1); ?>"><<</a></li>
   <?php endif; ?>

   <?php for ($x = 1; $x <= $maxPage; $x++) : ?>
       <li>
          <?php if ($x == $_REQUEST['page']) : ?>
            <a href="index.php?page=<?php print($x); ?>" class="now_page"><?php print $x; ?></a> <!-- 現在のページだったらnow_pageで色付け -->
          <?php else : ?>
            <a href="index.php?page=<?php print($x); ?>"><?php print $x; ?></a>
          <?php endif; ?>
      </li>
   <?php endfor; ?>

    <?php if ($page < $maxPage) : ?>
       <li><a href="index.php?page=<?php print($page + 1); ?>"> >> </a></li>
    <?php endif; ?>
</ul>

これを見て「ふ~ん」と思えたなら、「…」で省略のページネーションも理解できるはずです。for文を使って1~$maxPageまで回せば全ページ番号が表示できますね。ここまではよかったのですが、途中を「…」にどうするのか?for文を「$page-2から$page+2までまわす」を考えましたが、それでは1ページ目の時に(1 – 2)となって0と-1まで表示することになります。

サンプルコード

今回のメインである「途中を「…」で省略するページネーション」のコードを紹介します。「WebTecNote」さんのコードを参考にCSSでスタイルを当てる都合若干、変更点もありますが、アルゴリズムは同じものです。ページネーションが必要な個所で

<div class="paging">
  <?php paging($maxPage, $page, 2); ?>
</div>

といった具合に関数paging()を呼び出して使いますが、その際変数もセットします。関数を使いたいファイル側で必要なのは

  • $maxPage 最大ページ数 ($totalPageに入れる$maxPage)
  • $page 現在のページ番号 ($pageに入れる$page、同じ名前になりましたが…)
  • 「2」は関数内では$pageRangeで扱われ、現在のページ($page)から前後-2,-1,$page,+1,+2を表示する

このページネーションの処理だけで分量があるのでpaging.phpに以下のコードをまとめて使いたいファイルでrequire(‘paging.php’);を読み込んでおいて、先ほどのpaging()関数を呼び出すのがいいと思います。

先ほども少し書きましたが、僕の場合は「1ページに表示する件数$show_posts」も変数で管理してあるので、それに応じて$maxPageも変動します。それではコードです、解説は後で。

<?php
function paging($totalPage, $page, $pageRange) {

  $prev = max($page - 1, 1);
  $next = min($page + 1, $totalPage);

  $start = max($page - $pageRange, 2); // ページ番号始点
  $end = min($page + $pageRange, $totalPage - 1); // ページ番号終点

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

  //最初のページへのリンク
  if ($page > 1) {
    print '<a class="paging_item" href="?page=1" title="最初のページへ"><<</a>';
  } else {
    print '<span class="paging_item"><<</span>';
  }

  // 前のページへのリンク
  if ($page > 1) {
    print '<a class="paging_item" href="?page=' . $prev . '" title="前のページへ"><</a>';
  } else {
    print '<span class="paging_item"><</span>';
  }

  // 最初のページ番号へのリンク
  if ($page > 1) {
    print '<a class="paging_item" href="?page=1">1</a>';
  } else {
    print '<span class="paging_item current">1</span>';
  }

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

  //ページリンク表示ループ
  foreach ($nums as $num) {

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

  if (($totalPage - 1) > $end) print "..."; //ドットの表示

  //最後のページ番号へのリンク
  if ($page < $totalPage) {
    print '<a class="paging_item" href="?page=' . $totalPage . '">' . $totalPage . '</a>';
  } else {
    print '<span class="paging_item current">' . $totalPage . '</span>';
  }

  // 次のページへのリンク
  if ($page < $totalPage) {
    print '<a class="paging_item" href="?page=' . $next . '">></a>';
  } else {
    print '<span class="paging_item">></span>';
  }

  //最後のページへのリンク
  if ($page < $totalPage) {
    print '<a class="paging_item" href="?page=' . $totalPage . ' title="最後のページへへ">>></a>';
  } else {
    print '<span class="paging_item">>></span>';
  }
}

個別の解説

$prev = max($page - 1, 1);
$next = min($page + 1, $totalPage);

これは「前へ」「次へ」に使いますが、-1と+1だけでなく$page == 1の時はmax(0, 1)でマイナスにならないようにmaxで大きい方を選択し$prevは「1」になります。

$nextは例えば$page == 5の時はmin(6, 10)で「6」なので5ページ目にいる時に「次へ」を押すと6ページ目に飛びます。$page == 10の時はmin(11, 10)で「10」なので最大ページ数を超えることはありません。

$start = max($page - $pageRange, 2); // ページ番号始点
$end = min($page + $pageRange, $totalPage - 1); // ページ番号終点
  $nums = []; // ページ番号格納用
  for ($i = $start; $i <= $end; $i++) {
    $nums[] = $i;
  }
~~略~~
if ($start > $pageRange) print "..."; // ドットの表示
  //ページリンク表示ループ
  foreach ($nums as $num) { 
    if ($num == $page) {
      print '<span class="paging_item current">' . $num . '</span>';
    } else { 
      print '<a class="paging_item" href="?page=' . $num . '" class="num">' . $num . '</a>';
    }
  }
  if (($totalPage - 1) > $end) print "..."; //ドットの表示

ここから$pageRange(=2で解説します)が加わるので「…」省略に関わる変数を定義します。端の「1」「10」はどのページにいても表示しますので、$start,$endはそれ以外のページ番号を表すものです。

$page == 3の時はmax(3-2, 2)で$startは「2」になり、$endはmin(3+2, 10-1)で$end「5」になります。空の配列$numsを用意しfor文は「2~5までまわす」つまり「1と10」以外に表示するのは「2, 3, 4, 5」となります。

後ろの「…」の解説です。if文は「(10 – 1) > 5」の時に「…」を表示なので成り立ちます。これで「1と10」 の間は「2, 3, 4, 5, …」になりました。この状態では前半に「…」がないのですが、こちらのif文はif(2 > 2)の時に「…」は成り立たないので無し。

$page == 4になってもmax(4-2, 2)なので$startに変化はありませんが、$page == 5になればmax(5-2, 2)で$startは「3」になりif(3 > 2)が成り立ち「1と3」の間に「…」が表示されます。(このアルゴリズムはとても思いつかないですよ…)他にも数字を当てはめ確認してください。ここまでで本記事のメインは終わりですが、一応他も解説します。

「最初へ」「前へ」「次へ」「最後へ」は簡単で、「$page > 1」「$page < $totalPage」の時にはそれぞれaタグでリンクを貼ってelseはspanでリンク出来ない仕様です。「1へ」「$prevへ」「$nextへ」「$totalPageへ」となりますが、ものによって1ページ目の時は「最初へ」や「前へ」を表示しないもあるかと思います、お好みでどうぞ。

お手本コードとの相違点

WebTecNote」さんのコードとの大きな違いは「$pageが1or 10の時」です。CSSでスタイルを付けていることでの変更点ですが、if($page > 1)でない時つまり$page == 1の時にspanタグにして現在のページであるcurrentクラスをつけています。

.paging {
  margin-top: 30px;
  text-align: center;
}
.paging_item {
  border: 1px solid #999;
  padding: 10px;
  margin: 0px 4px;
}
.paging .current {
  background-color: #7fbfff;
  color: #fff;
}

$page == 10の時も同様です。foreach文では$numsから取り出した$num == $pageの時にcurrentで色を付けていたのでその逆になるのでした。

お手本コードにあった「1ページ目の時だけ$endを倍にする」も今回は必要なかったので削りました。$end = min(1+2, 10-1)で「3」になり「1, 2, 3, …, 10」が

if ($page == 1) {
  $end = $pageRange * 2; // 終点再計算
}

があると$end = 2 * 2で「1, 2, 3, 4, …, 10」になります。それだけの違いです。

9/28追記

表示の部分で直しがありましたので、追記します。最終ページ目の番号を常にaタグ、spanタグで表示していますので、投稿が少ないユーザーのプロフィール画面を見ると

このように最初の「1」と最後の「1」が表示されてしまい、おかしいですね。しかし最終ページに飛びたい時はaタグは必要で、最後にいる時はリンクはいらないのでspanタグのままでいい。そこでelse ifで条件を足してみました。

  //最後のページ番号へのリンク
  if ($page < $totalPage) {
    print '<a class="paging_item" href="?page=' . $totalPage . '">' . $totalPage . '</a>';
  } else {
    print '<span class="paging_item current">' . $totalPage . '</span>';
  }
  //最後のページ番号へのリンク
  if ($page < $totalPage) {
    print '<a class="paging_item" href="?page=' . $totalPage . '">' . $totalPage . '</a>';
  } else if($totalPage == 1) {
    print '';
  } else {
    print '<span class="paging_item current">' . $totalPage . '</span>';
  }

こうすることで「$totalPageが1の時は何もないを表示してね」ができました。これで投稿が少ないユーザーのプロフィール画面での表示も

このように「1」がダブることもなくなり、思っていた見た目になりました。

コメント

タイトルとURLをコピーしました