6-3:ユーザー管理機能を作成する2

2019/05/31

概要

会員一覧にページングを付けて見やすくします。
また、ファイルで管理できるようにCSVダウンロード機能を実装していきます。

フォルダ階層

完成イメージ

会員一覧ページング

CSVダウンロード

ダウンロードファイル(users.csv)

全体の手順

手順は以下の通りです。

  1. ページングの実装
  2. CSVダウンロード機能の実装

ページング実装

会員が増えたときに備えて1ページ10件ずつ表示でページングを付けます。

ページングデザイン追加

ページングのデザインを追加しておきます。
会員一覧テーブルの下にページングを付けます。
admin/users.php

                    </table>
+                   <ul class="paging">
+                       <li><a href="">« 最初</a></li>
+                       <li><a href="">1</a></li>
+                       <li><span>2</span></li>
+                       <li><a href="">3</a></li>
+                       <li><a href="">最後 »</a></li>
+                   </ul>
                </div>
            </div>
        </div>
    </main>

CSSも編集します
admin/styles.css

/*paging*/
.paging {
    margin: 0 0 10px;
    padding: 10px 10px 5px;
    text-align: right;
}

.paging li {
    display: inline;
    margin: 0 1px;
}

.paging a,
.paging span {
    color: #4c586f;
    padding: 5px 8px;
}

.paging span {
    background-color: #4c586f;
    border-radius: 5px;
    color: #fff;
}

すると、一覧テーブルの右下にページングが表示されました。
">

ひとまず、デザインはOKです。

必要な情報の設定

実際にページングを設定していきます。
ページングの実装で必要な情報は

  1. 何件ずつ表示させるか(今回は10件)
  2. 現在表示しているページ
  3. 表示するページに応じたレコード取得開始位置
  4. 全件のレコード数
  5. 全件をn件ずつ表示させた場合の総ページ数(今回は10件ずつ表示させた場合)
  6. 次のページ数
  7. 一つ前のページ数
    です。

上記の情報を設定すると下記のコードになります。
$pageはページングのリンクのパラメータで指定してGET取得します。
そうすることで、他のパラメータも動的に変動するように設定します。
基本の設定(まだusers.phpに記述しない)

<?php

    //1. 何件ずつ表示させるか(固定。今回は10件ずつ)
    $rows = 10; 

    //2. 現在表示しているページ数(GETで取得。初回など送られてこなければ1を設定する)
    $page = isset($_GET['page'])? $_GET['page'] : 1;  

   //3. 表示するページに応じたレコード取得開始位置(2ページ目は、10件目から表示なので、10*(2-1)で$offset=10)
    $offset = $rows * ($page-1);

    //4. 全件のレコード数。変数の割当が必要無いのでqueryで実行し、fetchColumn()で取得したcountを返す。
    $all_rows = $dbh->query("SELECT COUNT(*) FROM users")->fetchColumn();

    //5.  全件をn件ずつ表示させた場合のページ数。全件÷表示件数をして、0以下の場合は、ページ数は1に固定。
    if(($all_rows % $rows) <= 0){
        $pages = (int)($all_rows/$rows);
    }else{
        $pages = (int)($all_rows/$rows)+1;
    }

    //6.  次のページ数(基本的に現在ページ+1。現在ページ+1が全ページ数より大きくなってしまうとページが無いのでその場合は''とする)
    $next = ($page+1 > $pages)? '' : $page+1;

   //7.  一つ前のページ数(基本的に現在ページ-1。現在ページ-1が0になってしまうとページが無いのでその場合は''とする)
    $prev = ($page-1 <= 0)? '' : $page-1;

今回もほとんど同じですが、users.phpでは検索窓を設定したので、検索条件が入ってる場合を考慮して4番の全レコード数を設定しなければいけません。
上記コードを少し改良してusers.phpに追加します。
DB接続の下にコードを追加します。
admin/users.php

    //ページング設定
    //1. 何件ずつ表示させるか(固定。今回は10件ずつ)
    $rows = 10; 

    //2. 現在表示しているページ数(GETで取得。初回など送られてこなければ1を設定する)
    $page = isset($_GET['page'])? $_GET['page'] : 1;  

    //3. 表示するページに応じたレコード取得開始位置(2ページ目は、10件目から表示なので、10*(2-1)で$offset=10)
    $offset = $rows * ($page-1);

    //4. 全件のレコード数。
    if($name == '')
    {
        //変数の割当が必要無いのでqueryで実行し、fetchColumn()で取得したcountを返す。
        $all_rows = $dbh->query("SELECT COUNT(*) FROM users")->fetchColumn();

    }else{
        //検索条件を考慮
        $all_rows_stmt = $dbh->prepare("SELECT * FROM users WHERE name like :name");
        $all_rows_stmt->bindValue(":name",'%'.$name.'%');
        $all_rows_stmt->execute();
        $all_rows = $all_rows_stmt->rowCount();
    }

    //5.  全件を10件ずつ表示させた場合のページ数。全件÷表示件数をして、0以下の場合は、ページ数は1に固定。
    if(($all_rows % $rows) <= 0){
        $pages = (int)($all_rows/$rows);
    }else{
        $pages = (int)($all_rows/$rows)+1;
    }

    //6.  次のページ数(基本的に現在ページ+1。現在ページ+1が全ページ数より大きくなってしまうとページが無いのでその場合は''とする)
    $next = ($page+1 > $pages)? '' : $page+1;

    //7.  一つ前のページ数(基本的に現在ページ-1。現在ページ-1が0になってしまうとページが無いのでその場合は''とする)
    $prev = ($page-1 <= 0)? '' : $page-1;
    //ページング設定終わり

レコード取得

必要な情報は取得できたので、それに応じたレコードを取得します。
SELECT文にlimitとoffsetを指定して、必要な部分の必要な数のレコードだけを取得するように編集します。
bindParamするとき、limitとoffsetは整数が入るので整数型であることを明記します。
admin/users.php

    //7.  一つ前のページ数(基本的に現在ページ-1。現在ページ-1が0になってしまうとページが無いのでその場合は''とする)
    $prev = ($page-1 <= 0)? '' : $page-1;
    //ページング設定終わり

    if($name == '')
    {
-       $stmt = $dbh->prepare("SELECT * FROM users");
+       $stmt = $dbh->prepare("SELECT * FROM users limit :offset,:rows");

    }else{
-       $stmt = $dbh->prepare("SELECT * FROM users WHERE name like :name");
+       $stmt = $dbh->prepare("SELECT * FROM users WHERE name like :name limit :offset,:rows");
        $stmt->bindValue(":name",'%'.$name.'%');
    }

+   $stmt->bindParam(":offset",$offset,PDO::PARAM_INT);
+   $stmt->bindParam(":rows",$rows,PDO::PARAM_INT);
    $stmt->execute();
    $users = $stmt->fetchAll(PDO::FETCH_ASSOC);

ページングのリンク

htmlでページングの表示はできているので、それに正しいリンク先を設定していきます。
リンク先はusers.phpにpageパラメータとnameパラメータを付けたものです。
今回表示するページングは
「最初」には一番最初のページをリンク。
「最後」には一番最後のページをリンク。
その間の数字は現在ページを挟んで1ページ前と次のページをリンクさせます。

「最初」は初回表示と同じなので、pageパラメータ無しです。
検索条件がセットされている可能性があるのでnameパラメータは付けます。
admin/users.php

    <ul class="paging">
-       <li><a href="">« 最初</a></li>
+       <li><a href="./users.php?name=<?php echo $name; ?>">« 最初</a></li>
        <li><a href="">1</a></li>
        <li><span>2</span></li>
        <li><a href="">3</a></li>
       <li><a href="">最後 »</a></li>
    </ul>

「次」は一番最後のページなので、必要な情報の設定の5番の$pagesをpageパラメータにセットします。
admin/users.php

    <ul class="paging">
        <li><a href="./users.php?name=<?php echo $name; ?>">« 前</a></li>
        <li><a href="">1</a></li>
        <li><span>2</span></li>
        <li><a href="">3</a></li>
-       <li><a href="">最後 »</a></li>
+       <li><a href="./users.php?page=<?php echo $pages; ?>&name=<?php echo $name; ?>">最後 »</a></li>
    </ul>

1ページ前は、必要な情報の設定の7番の$prevで取得できます。
$prevは現在ページが1ページの場合は存在しないので、if文を使って存在する場合のみ表示させます。
admin/useres.php

    <ul class="paging">
        <li><a href="./users.php?name=<?php echo $name; ?>">« 前</a></li>
-       <li><a href="">1</a></li>
+       <?php if ($prev != ''): ?>
+            <li><a href="./users.php?page=<?php echo $prev; ?>&name=<?php echo $name; ?>"><?php echo $page-1; ?></a></li>
+       <?php endif; ?>
        <li><span>2</span></li>
        <li><a href="">3</a></li>
        <li><a href="./users.php?page=<?php echo $pages; ?>&name=<?php echo $name; ?>">次 »</a></li>
    </ul>

現在ページは$pageで取得できます。
admin/users.php

    <ul class="paging">
        <li><a href="./users.php?name=<?php echo $name; ?>">« 前</a></li>
        <?php if ($prev != ''): ?>
            <li><a href="./users.php?page=<?php echo $prev; ?>&name=<?php echo $name; ?>"><?php echo $page-1; ?></a></li>
       <?php endif; ?>
-       <li><span>2</span></li>
+       <li><span><?php echo $page; ?></span></li>
        <li><a href="">3</a></li>
        <li><a href="./users.php?page=<?php echo $pages; ?>&name=<?php echo $name; ?>">次 »</a></li>
    </ul>

次ページは必要な情報の設定の6番の$nextで取得できます
$nextは現在ページが総ページ数の場合存在しないので、if文を使って表示します。
admin/users.php

    <ul class="paging">
        <li><a href="./users.php?name=<?php echo $name; ?>">« 前</a></li>
        <?php if ($prev != ''): ?>
            <li><a href="./users.php?page=<?php echo $prev; ?>&name=<?php echo $name; ?>"><?php echo $page-1; ?></a></li>
       <?php endif; ?>
        <li><span><?php echo $page; ?></span></li>
-       <li><a href="">3</a></li>
+       <?php if ($next != ''):  ?>
+           <li><a href="./users.php?page=<?php echo $next; ?>&name=<?php echo $name; ?>"><?php echo $page+1; ?></a></li>
+       <?php endif; ?>
        <li><a href="./users.php?page=<?php echo $pages; ?>&name=<?php echo $name; ?>">次 »</a></li>
    </ul>

これでページのリンクが完成しました。
最初ボタン・最後ボタン・各ページボタンの動作確認をして正しく動作していればページングの完成です。

CSVダウンロード機能の実装

会員リストをエクセル等で扱えるように、CSVファイルでダウンロードする機能を付けます。

ダウンロードボタン追加

会員一覧テーブルの上にボタンを追加します。
あとで、ダウンロード実行ファイルをdownload.phpという名前で作成するのでlocation.hrefはdownload.phpを指定しておきます。
admin/users.php

+   <button type="button" class="btn btn-gray" onclick="location.href='download.php'">CSV出力</button>
    <form class="serch" action="users.php" method="GET">
             <input type="text" name="name" placeholder="名前検索">
             <button type="submit" class="btn btn-blue">検索</button>
    </form>
    <div class="list">
        <table>
            <thead>
                <tr>
                    <th>id</th>
                    <th>名前</th>
                    <th>メールアドレス</th>
                    <th>DM配信</th>
                    <th>操作</th>
                </tr>
            </thead>

admin/styles.css

/*共通CSS*/
~~省略~~
.btn-blue {
    background-color: #4a80d6;
    border: 1px solid #4a80d6;
    color: #fff;
    padding: 10px 15px;
    font-size: 15px;
}

+.btn-gray {
+    background-color: #a2aab0;
+    border: 1px solid #a2aab0;
+    color: #fff;
+    padding: 10px 15px;
+    font-size: 15px;
+}

会員一覧画面にCSVダウンロードボタンが追加されました。

ダウンロード機能実装

adminフォルダにdownload.phpを作成します。

CSVファイル作成

会員一覧のCSVをダウンロードするために、まずCSVを作成します。
ログインチェックをします。
admin/download.php

<?php

    //sessionでログイン制限
    session_start();

    if($_SESSION['admin_login'] == false){
        header("Location:./index.html");
        exit;
    }

ダウンロードする内容をDBから取得します。
DBに接続して、sqlをexecute()、fetchAllで配列にして$usersに格納します。
今回は簡単に全件取得にしています。
admin/download.php

    <?php

        //sessionでログイン制限
        session_start();

        if($_SESSION['admin_login'] == false){
            header("Location:./index.html");
            exit;
        }

+       try{
+           $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
+       }catch(PDOException $e){
+           var_dump($e->getMessage());
+           exit;
+       }

+       $stmt = $dbh->prepare("SELECT * FROM users");
+       $stmt->execute();
+       $users = $stmt->fetchAll(PDO::FETCH_ASSOC);

次にcsvファイルを作成して取得したデータを書き込んでいきます。
まず、fopen()関数でファイルを開きます。
ファイル名を指定して、なければその名前のファイルを作成します。第二引数で開くmodeを指定しています。wは書き出しのみでオープンするという意味です。
詳しくはここに記載しています。
admin/download.php

    $stmt = $dbh->prepare("SELECT * FROM users");
    $stmt->execute();
    $users = $stmt->fetchAll(PDO::FETCH_ASSOC);

+   $fp = fopen('./users.csv','w');

そして、usersをforeachで回しながらfputcsv()を使って行を CSV 形式にフォーマットし、ファイルポインタに書き込んで行きます。
foreachで回し終わったら、fclose()でファイルを閉じます。
admin/download.php

    $stmt = $dbh->prepare("SELECT * FROM users");
    $stmt->execute();
    $users = $stmt->fetchAll(PDO::FETCH_ASSOC);

    $fp = fopen('./users.csv','w');

+    foreach($users as $user){
+       fputcsv($fp,$user);
+    }
+
+    fclose($fp);

この状態でusers.phpからアクセスしてダウンロードボタンを押してみます。
ページが移動して、download.phpに遷移しますがdownload.phpではなにも表示させていないので真っ白なページになります。
フォルダを確認してみます。adminフォルダの中にusers.csvができていると思います。
エディタで開いてみると下記のような状態だとOKです。

必要に応じて、ヘッダーを作成します。
ファイルを開いて、データを書き込む前にヘッダーを作成します。
admin/download.php

    $fp = fopen('./users.csv','w');

+   $header = ['ID','名前','メールアドレス','パスワード','住所','DM配信','登録日時','更新日時'];
+   fputcsv($fp,$header);

    foreach($users as $user){
        fputcsv($fp,$user);
    }

この状態でダウンロードを実行してみると、一番上にヘッダーが追加されたのが確認できます。

このファイルをエクセルで開いてみます。
すると、日本語で入力した部分が文字化けしてしまっています。

そのため、CSVの形式をUTF-8のBOM付きにする必要があります。
BOMはbyte order markの略で、Unicodeで符号化したテキストの先頭に付与されるテキストファイルの符号化方式の種類を判別するための情報のことです。
admin/download.php

    $fp = fopen('./users.csv','w');

+   //BOMあり
+   fwrite($fp, "\xEF\xBB\xBF");

    $header = ['ID','名前','メールアドレス','パスワード','住所','DM配信','登録日時','更新日時'];
    fputcsv($fp,$header);

    foreach($users as $user){
        fputcsv($fp,$user);
    }

    fclose($fp);

ダウンロード実行してファイルをエクセルで確認してみます。
日本語が正しく表示されました。

次に、今はCSVファイルは作成されますが、ダウンロードボタンを押すと真っ白な画面に遷移してしまいダウンロードが実行されていないので、ダウンロードボタンを押したらダウンロードが実行されるようにします。
users.csvファイルの作成後にheader(Location:'')を追加することで作成されたusers.csvのダウンロードが実行されます。
admin/download.php


    foreach($users as $user){
        fputcsv($fp,$user);
    }

    fclose($fp);

+   header('Location:./users.csv');

users.phpからダウンロードボタンを押して確認します。
するとusers.phpの表示のまま、ダウンロードができるようになりました。

これでCSVダウンロードも完了です。

コード

https://github.com/bluecode-io/web-basic/tree/basic6-3