こんにちは!今回もページネーションについて、PHP初心者の僕がどこよりもわかり易く解説していきます!
前回の準備編から引き続き、今回は「実践編」です!
下準備については「PHP初心者でも出来た!ページネーションを超わかり易く解説~準備編~」で解説しているので、コチラを見てからの方がわかりやすいと思います。
【目次】
ソースコード
<?php
//一ページに表示する記事の数をmax_viewに定数として定義
define('max_view',4);
try{
//test@localhostでblogに接続
$pdo = new PDO('mysql:dbname=blog;host=localhost;charset=utf8','test','パスワード');
} catch (PDOException $error) {
//エラーの場合はエラーメッセージを吐き出す
exit("データベースに接続できませんでした。<br>" . $error->getMessage());
}
//必要なページ数を求める
$count = $pdo->prepare('SELECT COUNT(*) AS count FROM posts');
$count->execute();
$total_count = $count->fetch(PDO::FETCH_ASSOC);
$pages = ceil($total_count['count'] / max_view);
//現在いるページのページ番号を取得
if(!isset($_GET['page_id'])){
$now = 1;
}else{
$now = $_GET['page_id'];
}
//表示する記事を取得するSQLを準備
$select = $pdo->prepare("SELECT title,file_path FROM posts ORDER BY no DESC LIMIT :start,:max ");
if ($now == 1){
//1ページ目の処理
$select->bindValue(":start",$now -1,PDO::PARAM_INT);
$select->bindValue(":max",max_view,PDO::PARAM_INT);
} else {
//1ページ目以外の処理
$select->bindValue(":start",($now -1 ) * max_view,PDO::PARAM_INT);
$select->bindValue(":max",max_view,PDO::PARAM_INT);
}
//実行し結果を取り出しておく
$select->execute();
$data = $select->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ページネーション</title>
</head>
<body>
<ul>
<?php
//各記事のタイトルにリンクを貼って表示
foreach ( $data as $row ) {
echo "<li><a href='$row[file_path]'>{$row[title]}</a></li>";
}
?>
</ul>
<?php
//ページネーションを表示
for ( $n = 1; $n <= $pages; $n ++){
if ( $n == $now ){
echo "<span style='padding: 5px;'>$now</span>";
}else{
echo "<a href='./home.php?page_id=$n' style='padding: 5px;'>$n</a>";
}
}
?>
</body>
</html>
前回までの下準備がしっかりできていれば↓のように表示されるはずです。
記事も更新した順に表示されていて、画面下部にはページネーションも表示されていますね!
次は各処理について細かく解説していきます。
各処理を詳しく解説
まず、どんなアルゴリズムで実現しているかを確認しましょう。
①1ページに表示する記事数を決定
②データベースに接続
③総記事数と表示する記事数を元にページ数を算出
④現在のページID(ページ番号)を取得
⑤現在のページIDと最大表示数を元に、テーブルからデータを取得
⑥タイトルにリンクを貼って表示
⑦ページネーションを作成
大まかにこの7ステップです。
特に③~⑤はページネーションを実現する上で肝になる部分なので、僕も頑張って解説します!(笑)
①1ページに表示する記事数を決定
//一ページに表示する記事の数をmax_viewに定数として定義
define('max_view',4);
defineは定数を定義する時に使います。
ここでは”max_view”に定数「4」を定義していて、これが1ページに表示する最大の記事数になります!
②データベースに接続
//test@localhostでblogに接続
try{
$pdo = new PDO('mysql:dbname=blog;host=localhost;charset=utf8','test','パスワード');
} catch (PDOException $error) {
//エラーの場合はエラーメッセージを吐き出す
exit("データベースに接続できませんでした。<br>" . $error->getMessage());
}
ここではPHPからデータベースに接続しています。
失敗した場合に崩れたwebページが表示されるとかっこ悪いので、エラー処理もしておきましょう。
$pdo = new PDO('mysql:dbname=blog;host=localhost;charset=utf8','test','パスワード');
最初の引数にはDBMSを指定します。今回の場合は「mysql」ですよね。
次にデータベース(dbname=)、ホスト名(host=)、文字コード(charset=)を「;」でつなげて指定します。
他にもポート番号だったりを指定することが出来ますが、この3つだけは最低限指定したほうが良いです。
最後はユーザ名とパスワードを指定しています。この2つはホスト名等と違いカンマ区切りで別々に指定するので注意が必要です。
また、この操作はDBへの接続+インスタンス化を行っているので、$pdoがPDUクラスのメソッドを使えるようになります。
catch (PDOException $error) {
//エラーの場合はエラーメッセージを吐き出す
exit("データベースに接続できませんでした。<br>" . $error->getMessage());
}
ここではエラー処理をしています。
これはテンプレートみたいなものなので、そのまま使って問題ないです。
③総記事数と表示する記事数を元にページ数を算出
//必要なページ数を求める
$count = $pdo->prepare('SELECT COUNT(*) AS count FROM posts');
$count->execute();
$total_count = $count->fetch(PDO::FETCH_ASSOC);
$pages = ceil($total_count['count'] / max_view);
prepareでSQLを準備しexecuteで実行するのはPDOの基本です。
このSQLの場合は外部からの入力値を用いないため、
query(‘SELECT COUNT(*) AS count FROM posts’)として一発で実行することも出来ます。
後ほど出てきますが、プレースホルダを用いるような場合はprepareでSQLを準備し、bindValueで値を代入、executeで実行するという流れが基本になるので覚えておきましょう。
//必要なページ数を求める
$count = $pdo->prepare('SELECT COUNT(*) AS count FROM posts');
$count->execute();
「SELECT COUNT(*) AS count FROM posts」はpostsテーブルのレコード数を数え、その結果のカラム名を「count」とするSQLです。
postsテーブルには1レコード毎に記事のタイトルとファイルパスが格納されていますから、この操作は「記事数を数えている」と考えることが出来ます。
今回の場合だと記事数は「10」になり、この結果を含むPDO Statementが$countに返されます。
$total_count = $count->fetch(PDO::FETCH_ASSOC);
$pages = ceil($total_count['count'] / max_view);
$count->fetchは実行したSQLの結果を1行ずつ取り出す操作です。(このSQLは1行しか出力されないので、fetchAllでも同じことです。)
「PDO::FETCH_ASSOC」の指定は、カラム名を添字にした配列(辞書とか連想配列と同じもの)で結果を取り出すことを意味します。
今回は「COUNT(*)」に対し「AS count」と指定したSQLを実行しているので、「count」が記事数のカラム名になっています。
さらにその結果を$total_countに格納しているので、「$total_count[‘count’]」と指定すれば記事数である「10」が出力されます。
つまり「$total_count[‘count’] / max_view」は
「総記事数÷1ページに表示する記事数」と同じことがわかります。
「ceil」は切り上げを行う関数なので、今回の場合は「10÷4≒3」となり、$pagesには「3」が入ります。
この$pagesの値が必要なページ数です!
④現在のページID(ページ番号)を取得
//現在いるページのページ番号を取得
if(!isset($_GET['page_id'])){
$now = 1;
}else{
$now = $_GET['page_id'];
}
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
//ページネーションを表示
for ( $n = 1; $n <= $pages; $n ++){
if ( $n == $now ){
echo "<span style='padding: 5px;'>$now</span>";
}else{
echo "<a href='./home.php?page_id=$n' style='padding: 5px;'>$n</a>";
}
}
ここが少しだけ複雑で、HTTPのGETメソッドを利用してページング(ページの切り替え)を実現しています。
GETメソッドとは簡単に言うと、URLの一部を利用してデータをやり取りする手法みたいなものです。
//現在いるページのページ番号を取得
if(!isset($_GET['page_id'])){
$now = 1;
}else{
$now = $_GET['page_id'];
}
PHPではあらかじめ「$_GET」変数が用意されていて、GETメソッドから取得した値はここに格納されます。
また「isset」は値が存在するかを確認する関数で、「$_GET['page_id']」に何か値が入っていればTrue、なければFalseを返します。
このif文は「isset」を「!」で否定しているので、値が存在しない場合にTrueとなります。
当たり前ですが、初めてこのhome.phpを開いた際はGETに何も値がセットされていません。
よって現在のページ番号を表す「$now」に「1」を代入します。
//ページネーションを表示
for ( $n = 1; $n <= $pages; $n ++){
if ( $n == $now ){
echo "<span style='padding: 5px;'>$now</span>";
}else{
echo "<a href='./home.php?page_id=$n' style='padding: 5px;'>$n</a>";
}
}
かなり後のコードですが、先程のページ番号の取得はこの処理と深く関連しています。
まずこのfor文に着目すると、「1〜$pages」までのページ番号を表示する処理であることが解ると思います。
「$now=1」の状態でこの処理まで来た場合は、
ページ下部に「 1 2 3 」のようなページネーションが表示されます。
}else{
echo "<a href='./home.php?page_id=$n' style='padding: 5px;'>$n</a>";
}
さらにこの数字にリンクを貼る処理に注目すると、
リンク先に「href='./home.php?page_id=$n'」が指定されていることがわかります。
例えば「$n=2」の時、リンク先は「home.php?page_id=2」になります。
この「?page_id=2」がGETメソッドで、リンクを踏んでhome.phpを開く時、「$_GET['page_id']」に「2」を代入した状態で移動します。
//必要なページ数を求める
if(!isset($_GET['page_id'])){
$now = 1;
}else{
$now = $_GET['page_id'];
}
リンクを踏んでhome.phpを開いたので、またこの処理に戻ってきます。
しかし先程と違うのは「$_GET['page_id']」に「2」が格納されているということです。
つまり値がセットされているのでelseの処理を実行し、「$now」に現在のページ番号である「2」が代入されます。
すこし複雑ですが、この様なやり方でページングを実現しています。
今はまだ「$nowの値が変わっただけじゃん!」って感じですが、この後の処理でこの数字が大活躍するので安心して下さい(笑)
⑤現在のページIDと最大表示数を元に、テーブルからデータを取得
//表示する記事を取得するSQLを準備
$select = $pdo->prepare("SELECT title,file_path FROM posts ORDER BY no DESC LIMIT :start,:max ");
if ($now == 1){
//1ページ目の処理
$select->bindValue(":start",$now -1,PDO::PARAM_INT);
$select->bindValue(":max",max_view,PDO::PARAM_INT);
} else {
//1ページ目以外の処理
$select->bindValue(":start",($now -1 ) * 4,PDO::PARAM_INT);
$select->bindValue(":max",max_view,PDO::PARAM_INT);
}
//実行し結果を取り出しておく
$select->execute();
$data = $select->fetchAll(PDO::FETCH_ASSOC);
ここが今までの下準備の集大成です!
少し長いですが、一つずつ確認していきましょう。
//表示する記事を取得するSQLを準備
$select = $pdo->prepare("SELECT title,file_path FROM posts ORDER BY no DESC LIMIT :start,:max ");
まずは表示する記事を取得するためのSQLです。
「SELECT title,file_path FROM posts」でpostsテーブルからタイトルとファイルパスを取り出します。
次に「ORDER BY no DESC」で「no」フィールドの降順にレコードを並べ替えます。
「PHP初心者でも出来た!ページネーションを超わかり易く解説~準備編~」で解説していますが、「no」フィールドは自動で採番する設定になっています。
↓
つまり、後から追加した記事ほど「no」の値が大きくなります!
それを降順で並べ替えるわけですから、最新記事がレコードの上の方に並びますよね。
そしてトドメの「LIMIT :start,:max」です!
LIMITは第一引数の行番号から第二引数の値分レコードを取得する操作なので、「:start」と「:max」に適切な値を代入することで、特定の位置から最新順に記事を取得することが可能になります。
if ($now == 1){
//1ページ目の処理
$select->bindValue(":start",$now -1,PDO::PARAM_INT);
$select->bindValue(":max",max_view,PDO::PARAM_INT);
} else {
//1ページ目以外の処理
$select->bindValue(":start",($now -1 ) * max_view,PDO::PARAM_INT);
$select->bindValue(":max",max_view,PDO::PARAM_INT);
}
ここでは先程準備したLIMITの引数に値を代入していきます。
bindValueはprepareで準備したSQLのプレースホルダにデータ型を指定して値を代入することができるメソッドです。(PDO::PARAM_INTは整数型を表す)
ここでは「:start」と「:max」がプレースホルダなので、それぞれに値を代入していきます。
//1ページ目の処理
$select->bindValue(":start",$now -1,PDO::PARAM_INT);
$select->bindValue(":max",max_view,PDO::PARAM_INT);
まず「$now=1」の場合を考えます。
この場合「:start」に「0」、「:max」に「4」が代入されるので、
行番号0から4レコード取り出す操作になります。
↓
「降順に並んだ記事の上から4つを取得する=1ページ目の時は最新の4記事が表示される」ということです!
ここで注意なのが行番号が1から始まらない点です。
これはLIMITの仕様なのでそういうもんだと思って下さい(笑)
//1ページ目以外の処理
$select->bindValue(":start",($now -1 ) * max_view,PDO::PARAM_INT);
$select->bindValue(":max",max_view,PDO::PARAM_INT);
ここでは「$now=1」以外の処理を考えます。
先程と異なるのが「(":start",($now -1 ) * max_view」の部分です
仮に「$now=2」と仮定してこの計算を行うと、「:start=4」「:max=4」になります。
これをLIMITに代入すると「行番号4から4レコード取り出す操作」になります。
LIMITでは「行番号0=1レコード目」であることを先程確認しているので、「行番号4=5レコード目」になりますよね。
つまりこの操作では「5〜8番目に新しい記事」を取得することが出来ます。
現在のページ番号を駆使してテーブルから取り出す範囲を決定しているってわけですね〜
//実行し結果を取り出しておく
$select->execute();
$data = $select->fetchAll(PDO::FETCH_ASSOC);
プレースホルダに値を代入して、ページ番号に対応した記事を取得する準備が出来たらexecuteで実行です!
そして実行結果を「fetchAll」で取得するんですが、先程登場した「fetch」とは少し動作が異なります。
「fetch」が実行結果を一行づつ取り出すのに対し、「fetchAll」は実行結果を一つの配列として一度に返してくれます。
今回はあとで値を取り出すので、「fetchAll」でまとめて実行結果を取得しておきます。
⑥タイトルにリンクを貼って表示
いよいよラストスパートです!ここでは先程取り出したデータを使って、記事一覧を表示させていきます
<ul>
<?php
//各記事のタイトルにリンクを貼って表示
foreach ( $data as $row ) {
echo "<li><a href='$row[file_path]'>{$row[title]}</a></li>";
}
?>
</ul>
foreachで先程取り出したデータが格納されている「$data」変数から1レコードずつ値を取り出す操作を、データがなくなるまで続けます。
先程の「fetchAll」では取り出し方に「PDO::FETCH_ASSOC」を指定しているので、カラム名が配列のキーになっています。
よって「href='$row[file_path]'
」は各記事のファイルパスをリンクに設定する操作で、「{$row[title]}
」はその記事のタイトルを表示する操作です!
こうすることで記事タイトルにリンクを貼って表示することが出来ますね。
⑦ページネーションを作成
④の部分でも解説しましたが、軽くおさらいです!
<?php
//ページネーションを表示
for ( $n = 1; $n <= $pages; $n ++){
if ( $n == $now ){
echo "<span style='padding: 5px;'>$now</span>";
}else{
echo "<a href='./home.php?page_id=$n' style='padding: 5px;'>$n</a>";
}
}
?>
これは現在選択しているページ番号以外のページにページング用リンクを貼る処理です。
例えば「$now=2」であれば、
$n=1の時...False
$n=2の時...True
$n=3の時...False のような結果になります。
処理をよく見るとTrueのときにはaタグを使っていないので、Trueの場合はリンクを貼らないことがわかります。
よって「 1 2 3 」のようなページネーションが表示されます。
動作確認
お疲れ様でした!ここでは実際にページを表示させてみましょう!
#systemctl start httpd
#systemctl start mysqld
サーバを起動して、ブラウザ上で今回作成したhome.phpを開いて下さい。
ドキュメントルート直下にblogフォルダを作成し、その下にhome.phpを作成したので、「localhost/blog/home.php」と指定すれば大丈夫です。
この辺の設定や構成については前回のPHP初心者でも出来た!ページネーションを超わかり易く解説~準備編~」を参考にして下さい。
こんな感じで表示されれば成功です!
試しにページネーションの「3」をクリックしてみます。
URLが「localhost/blog/home.php?page_id=3」となり、GETメソッドを上手く使ってページングを実現出来ていることがわかりますよね!
さらに「橋本環奈の魅力を伝えたい」をクリックしてみます。
前回作った「test1.php」へのリンクが適切に貼られていることが確認できましたね!
他にもいろいろポチポチしてページネーションを実現できた喜びを噛みしめて下さい(笑)
おわりに
いかがだったでしょうか?
2本立てでかなりボリューミーな記事になってしまいましたが、どこよりも丁寧に解説した自信あります!!(笑)
僕もPHP始めたての初心者なので、初心者の方に寄り添った記事が書けたかなぁと思います。
ページネーションは当たり前の機能ですが、PDOの使い方やデータベースの扱いを学ぶ上でとても勉強になるので、是非チャレンジしてみてほしいです!!
また、繰り返しになりますがPHP初心者でも出来た!ページネーションを超わかり易く解説~準備編~」コチラの記事もよろしくお願いします。
次は音楽系の記事を書こうかなぁ〜
最後までありがとうございました!!