Chapter17 一覧と詳細ページ

概要と目標 レコードの抽出方法と
SQLインジェクションの対策。

SELECT文を使ったレコードの抽出方法と、
SQLインジェクションという脆弱性の対策方法を学習しましょう。

今回のゴール

RERUN

レコードの抽出 SELECT文は実行した後、
連想配列として取得する。

SELECT文は、以前紹介したquery()メソッドだけでは画面に表示できない。
実行結果を変数に代入し、連想配列として取得する必要がある。
実行結果を連想配列にするには、fetchAll()メソッドを使えば便利。

fetchAll()メソッド ・・・ SQLの実行結果を配列して取得するメソッド
$SQL実行結果が入った変数->fetchALL(配列のスタイル)

詳細はPHPマニュアルを参照

配列のスタイル
スタイル 説明
PDO::FETCH_ASSOC カラム名をキーとした連想配列として取得
PDO::FETCH_NUM カラムに「0」からの番号を付けて通常の配列として取得
PDO::FETCH_BOTH (デフォルト) 上記の連想配列と配列の両方とも取得

SELECT文で
レコードを抽出してみよう。

  1. 前回作成したデータベース「mini-cms」がない場合は、MySQLモニタを起動し、
    「mini-cms」 › 「setup」フォルダ内にある「create-db.sql」で、データベースを作成。
  2. 「mini-cms」フォルダ内にある「index.php」をテキストエディタで開く
  3. 前回記述したINSERT文をSELECT文に変更し、 実行結果を連想配列として取得するコードを記述
mini-cms/index.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT * FROM posts ORDER BY created DESC';

    // SQLを実行
    $stmt = $dbh->query($sql);

    // 実行結果を連想配列として取得
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    print_r($result);

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>新着記事一覧</title>
</head>
<body>
  <h1>新着情報</h1>

</body>
</html>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 連想配列として取得できているかを確認
ブラウザでの表示例

RERUN

新着情報一覧の表示 取得した連想配列は、
ループで画面に出せばよい

連想配列となったSELECT文の抽出結果は、ループを活用して画面に出力すればよい。

新着情報の一覧を表示してみよう。

  1. 「mini-cms」 フォルダ内にある「index.php」をテキストエディタで開く
  2. print_r($result); の部分をコメントにし、
    h1要素の下に、ループを使って用語説明リストとして画面に出力するコードを記述
    • dt要素 ・・・ 記事の作成日 <time> ~ </time>で囲む)
    • dd要素 ・・・ 記事のタイトル
mini-cms/index.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT * FROM posts ORDER BY created DESC';

    // SQLを実行
    $stmt = $dbh->query($sql);

    // 実行結果を連想配列として取得
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    // print_r($result);

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>新着記事一覧</title>
</head>
<body>
  <h1>新着情報</h1>

  <dl>
    <?php foreach($result as $row) : ?>
    <dt><time><?php echo h($row['created']) ?></time></dt>
    <dd><?php echo h($row['title']) ?></dd>
    <?php endforeach; ?>
  </dl>
</body>
</html>

答えを見る

  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 新着情報が表示できているかを確認
ブラウザでの表示例

RERUN

日付フォーマットの変更 日付のフォーマットを変更するには、
date関数と、strtotime関数を組み合わせる。

datetime型のレコードは、年月日が「-」で区切られ、時間分秒が、「:」で区切られる。
このフォーマットを変更するには、以前紹介したdate関数と、
strtotime関数を組み合わせる事で、指定日時のフォーマットを変更できる。

date関数 ・・・ 日付/時刻を書式化する関数
date(フォーマット, タイムスタンプ)
                    省略可

タイムスタンプ: 「1970年1月1日 00:00:00」からの経過ミリ秒を指定。省略すると time() の値

詳細はPHPマニュアルを参照

strtotime関数 ・・・ 英文形式の日付をタイムスタンプに変換する関数
strtotime(英文形式の日付)

英文形式の日付: 「2025-12-17 13:16:29」や「yesterday」や「+7 day」などが指定可能

詳細はPHPマニュアルを参照

日付のフォーマットを変更してみよう。

  1. 「mini-cms」フォルダ内の「index.php」をテキストエディタで開く
  2. date関数と、strtotime関数を使って、公開日のフォーマットを変更
    date('Y年m月d日', strtotime($row['created']))
    また、フォーマットの変更に伴いtime要素に、datetime属性を追加
mini-cms/index.php
<dl>
  <?php foreach($result as $row) : ?>
  <dt><time datetime="<?php echo h($row['created']); ?>"><?php echo h(date('Y年m月d日', strtotime($row['created']))); ?></time></dt>
  <dd><?php echo h($row['title']); ?></dd>
  <?php endforeach; ?>
</dl>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 日付のフォーマットが変更されているかを確認
ブラウザでの表示例

RERUN

詳細ページのHTML 詳細ページは、どの記事だって、
1枚の同じPHPファイル。

どの記事であっても詳細ページは、1枚の同じPHPファイルで表示する。
まずは、記事を表示するテンプレートとなるHTMLを作成しておく。

詳細ページのHTML部分を作成しよう。

  1. 「mini-cms」フォルダ内に「detail.php」を作成する
  2. 下記を参考に記事を表示するHTMLを作成
mini-cms/detail.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>[記事のタイトル]</title>
</head>
<body>
  <h1>[記事のタイトル]</h1>
  <ul>
    <li>公開日: [記事の公開日]</li>
    <li>カテゴリ: [記事のカテゴリ名]</li>
  </ul>
  <p>
    [記事の内容]
  </p>
  <p><a href="./">一覧に戻る</a></p>
</body>
</html>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「detail.php」にアクセス
    http://localhost/php-lessons/mini-cms/detail.php
  3. HTMLの内容を確認
ブラウザでの表示例

RERUN

詳細ページへのリンク 記事ごとに表示する内容を変えるには、
URLにGETパラメータを付ける。

「posts」テーブルには記事ごとに固有の番号を割り当てる「id」フィールドがある。
1枚のPHPファイルで、記事ごとに表示内容を切り分けるには、
その記事IDをGETパラメータとしてリンク先のURLに付加する。

詳細ページのリンクURL
detail.php?id=記事ID

詳細ページにジャンプする
リンクを作成しよう。

  1. 「mini-cms」フォルダ内にある「index.php」をテキストエディタで開く
  2. 記事のタイトルに「detail.php」にジャンプするリンクを作成し、
    リンク先URLに記事IDのGETパラメータを付加する
mini-cms/index.php
<dl>
  <?php foreach($result as $row) : ?>
  <dt><time datetime="<?php echo h($row['created']); ?>"><?php echo h(date('Y年m月d日', strtotime($row['created']))); ?></time></dt>
  <dd>
    <a href="detail.php?id=<?php echo h($row['id']); ?>">
      <?php echo h($row['title']); ?>
    </a>
  </dd>
  <?php endforeach; ?>
</dl>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 各リンクに記事IDのGETパラメータが付いていることを確認
ブラウザでの表示例

RERUN

記事IDのレコードを抽出 記事IDを取得し、
そのIDと一致するレコードを取得。

URLに付加された記事IDのGETパラメータを取得し、
その記事IDと一致するレコードをSELECT文で抽出すれば、 記事ごとに内容を切り分けれる。

詳細ページのSELECT
SELECT p.*, c.category_name FROM posts AS p JOIN categories AS c
  ON p.category_id = c.id WHERE p.id = 記事ID;

ファイルの読み込みと、
GETパラメータのチェックをしてみよう。

  1. 「mini-cms」フォルダ内の「detail.php」をテキストエディタで開く
  2. ファイルの冒頭に、「config.php」、「functions.php」の読み込みと、
    GETパラメータが付加されていなかった時の処理を記述
mini-cms/detail.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  // GETパラメータのチェック
  if ( empty($_GET['id']) ) {
    // $_GET['id'] が 空 の場合
    header('Location: index.php');
    exit();
  }
?>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 詳細ページへのリンクをクリックし問題なく表示されるかを確認
    アドレスバーからGETパラメータを削除した際は、リダイレクトされるかも確認

GETパラメータを使って、
記事のレコードを取得してみよう。

  1. try ~ catch構文を使って、PDOでデータベースに接続し、
    SELECT文で、記事のレコードを取得する処理を記述
mini-cms/detail.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  // GETパラメータのチェック
  if ( empty($_GET['id']) ) {
    // $_GET['id'] が 空 の場合
    header('Location: index.php');
    exit();
  }

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT p.*, c.category_name FROM posts AS p JOIN categories AS c ON p.category_id = c.id WHERE p.id = '. $_GET['id'];

    // SQLを実行
    $stmt = $dbh->query($sql);

    // 実行結果を連想配列として取得
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    print_r($result);

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 詳細ページのリンクをクリックし記事のレコードが取得できていることを確認
ブラウザでの表示例

RERUN

一見うまく行っているように見えるが、
上記のコードに、SQLインジェクションという脆弱性がある。

SQLインジェクション
SQL文に悪意のあるコードを埋め込み、不正なクエリを発行する攻撃方法。
例えば、以下のSQL文には脆弱性がある。
$sql = "select * from users where password = '{$_POST['passowrd']}'";
この場合、攻撃者がフォームから ' or '' = ' というコードを渡すと以下のようなSELECT文になる。
select * from users where password = '' or '' = '';
これは、「パスワードが空」か「空が空」の全フィールドを抽出するというSELECT文となる。
「空が空」という条件は必ず当てはまる条件となり、全てのレコードが抽出されてしまう。
このような悪意のあるコードを実行させないためにも、SQL文には変数を埋め込まない。
プリペアド・ステートメントを利用すれば、SQLインジェクションの対策が可能。

プリペアドステートメント SQLにプレースホルダを埋め込み、
後から値をガッチャンコ。

SQL文にフォームから受け取ったデータを組み込む場合は、プリペアド・ステートメントを利用する。
プリペアド・ステートメントは、SQL文に「?(プレースホルダー)」を埋め込んで、
後から変数で値を渡すテンプレートのようなもの。

ステートメントを用意してみよう。

プリペアド・ステートメントを使うには、まずprepare()メソッドで、ステートメントを用意する必要がある。

prepare()メソッド ・・・ ステートメントを用意するメソッド
$変数名 = $DBハンドル->prepare(プレースホルダーを埋め込んだSQL文)

戻り値: ステートメントハンドル

詳細はPHPマニュアルを参照

  1. 「mini-cms」フォルダ内の「detail.php」をテキストエディタで開く
  2. SQL文の「$_GET['id']」を埋め込んでいる箇所を「?」(プレースホルダー)に置き換え、
    「SQLを実行」の処理を、prepareメソッドで、ステートメントを用意するコードに変更
mini-cms/detail.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  // GETパラメータのチェック
  if ( empty($_GET['id']) ) {
    // $_GET['id'] が 空 の場合
    header('Location: index.php');
    exit();
  }

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT p.*, c.category_name FROM posts AS p JOIN categories AS c ON p.category_id = c.id WHERE p.id = ?';

    // ステートメント用意
    $stmt = $dbh->prepare($sql);

    // 実行結果を連想配列として取得
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    print_r($result);

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>

プレースホルダーに値をガッチャンコしてみよう。

用意したステートメントのプレースホルダーに値を埋め込むには、bindValue()メソッドを使う。

bindValue()メソッド ・・・ ステートメントを用意するメソッド
$ステートメントハンドル->bindValue(何番目の「?」か, 埋め込む値, データ型)

詳細はPHPマニュアルを参照

主なbindValue()メソッドのデータ型
定数 説明
PDO::PARAM_INT 整数値型
PDO::PARAM_STR 文字列型
  1. bindValue()メソッドで、$_GET['id']をバインド
    $_GET['id']の手前に「(int)」と記述し数値型に変換しておく)
mini-cms/detail.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  // GETパラメータのチェック
  if ( empty($_GET['id']) ) {
    // $_GET['id'] が 空 の場合
    header('Location: index.php');
    exit();
  }

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT p.*, c.category_name FROM posts AS p JOIN categories AS c ON p.category_id = c.id WHERE p.id = ?';

    // ステートメント用意
    $stmt = $dbh->prepare($sql);

    // プレースホルダーに値をガッチャンコ
    $stmt->bindValue(1, (int)$_GET['id'] , PDO::PARAM_INT);

    // 実行結果を連想配列として取得
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    print_r($result);

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>

ガッチャンコしたステートメントを
実行してみよう。

query()メソッドは、ステートメントには使えない。
プレースホルダーに値を埋め込んだステートメントを実行するには、execute()メソッドで実行する、

execute()メソッド ・・・ ステートメントを実行するメソッド
$ステートメントハンドル->execute()

詳細はPHPマニュアルを参照

  1. execute()メソッドで、ステートメントを実行
mini-cms/detail.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  // GETパラメータのチェック
  if ( empty($_GET['id']) ) {
    // $_GET['id'] が 空 の場合
    header('Location: index.php');
    exit();
  }

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT p.*, c.category_name FROM posts AS p JOIN categories AS c ON p.category_id = c.id WHERE p.id = ?';

    // ステートメント用意
    $stmt = $dbh->prepare($sql);

    // プレースホルダーに値をガッチャンコ
    $stmt->bindValue(1, (int)$_GET['id'] , PDO::PARAM_INT);

    // ステートメントを実行
    $stmt->execute();

    // 実行結果を連想配列として取得
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    print_r($result);

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 詳細ページのリンクをクリックし記事のレコードが取得できていることを確認
ブラウザでの表示例

RERUN

1レコードのみ取得 fetchAll()メソッドは全レコードを取得。
fetch()メソッドは1レコードずつ取得。

詳細ページのSELECT文は、記事IDと一致する1レコードのみが結果として返ってくる。
その結果をfetch()メソッドで連想配列にした場合、無駄に多次元配列となる。
結果を1レコードのみ取得するには、fetch()メソッドを使うとよい

fetch()メソッド ・・・ SQLの実行結果から1行取得するメソッド
$SQL実行結果が入った変数->fetch(配列のスタイル)

詳細はPHPマニュアルを参照

配列のスタイル
スタイル 説明
PDO::FETCH_ASSOC カラム名をキーとした連想配列として取得
PDO::FETCH_NUM カラムに「0」からの番号を付けて通常の配列として取得
PDO::FETCH_BOTH (デフォルト) 上記の連想配列と配列の両方とも取得

1レコードのみ取得してみよう。

  1. 「mini-cms」フォルダ内の「detail.php」をテキストエディタで開く
  2. fetchAll()メソッドの部分を、fetch()メソッドに変更
mini-cms/detail.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  // GETパラメータのチェック
  if ( empty($_GET['id']) ) {
    // $_GET['id'] が 空 の場合
    header('Location: index.php');
    exit();
  }

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT p.*, c.category_name FROM posts AS p JOIN categories AS c ON p.category_id = c.id WHERE p.id = ?';

    // ステートメント用意
    $stmt = $dbh->prepare($sql);

    // プレースホルダーに値をガッチャンコ
    $stmt->bindValue(1, (int)$_GET['id'] , PDO::PARAM_INT);

    // ステートメントを実行
    $stmt->execute();

    // 実行結果を1レコードのみ連想配列として取得
    $result = $stmt->fetch(PDO::FETCH_ASSOC);
    print_r($result);

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 詳細ページのリンクをクリックし1レコードのみ取得できていることを確認
ブラウザでの表示例

RERUN


取得したレコードを
HTML部分に出力してみよう。

  1. print_r関数の部分をコメントにし、
    HTML部分に取得したレコードを出力するコードを記述
mini-cms/detail.php
  // print_r($result);
mini-cms/detail.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title><?php echo h($result['title']); ?></title>
</head>
<body>
  <h1><?php echo h($result['title']); ?></h1>
  <ul>
    <li>公開日: <time datetime="<?php echo h($result['created']); ?>"><?php echo h(date('Y年m月d日', strtotime($result['created']))); ?></time></li>
    <li>カテゴリ: <?php echo h($result['category_name']); ?></li>
  </ul>
  <p>
    <?php echo h($result['content']); ?>
  </p>
</body>
</html>
  1. 上書き保存
  2. ブラウザで「mini-cms」フォルダ内の「index.php」にアクセス
    http://localhost/php-lessons/mini-cms/
  3. 詳細ページのリンクをクリックし詳細ページが表示されるかを確認
ブラウザでの表示例

RERUN

fetch()は実行する度にレコードを取得
fetch()メソッドは実行する度に、先頭から1レコードずつ連想配列に結果を取得する。
取得するレコードがなくなるとfalseを返す。 従ってwhile文を使って全レコードを取得することもできる。
mini-cms/index.php
<?php
  // ファイルの読み込み
  require_once('inc/config.php');
  require_once('inc/functions.php');

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT * FROM posts ORDER BY created DESC';

    // SQLを実行
    $stmt = $dbh->query($sql);

    // 1レコードずつ $result に連想配列して結果を取得
    while ( $result = $stmt->fetch(PDO::FETCH_ASSOC) ) {
      echo h($result['title']);
    }

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>

DB操作の基本手順 「?」を埋め込み、ガッチャンコ。
これが基本。

PHPでSELECTINSERTUPDATEDELETE文を実行する時は、以下の手順が基本となる。

  1. データベースに接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  2. SQL文の作成 (値に「?」を埋め込む)
    $sql = 'select * from users where name = ? and age >= ?';
  3. ステートメントを用意
    $stmt = $dbh->prepare($sql);
  4. ステートメントに変数をバインド
    $name = 'Tom';
    $age = 20;
    $stmt->bindValue(1, $name, PDO::PARAM_STR);
    $stmt->bindValue(2, $age, PDO::PARAM_INT);
  5. ステートメントの実行
    $stmt->execute();
  6. 【SELECT文のみ】結果セットを配列に格納
    全レコードをfatchAll()で取り出す場合
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    foreach ($result as $row) {
      echo h( $row['name'] . '('. $row['age'] .')' );
    }
    1レコードずつfatch()で取り出す場
    while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
      echo h( $result['name'] . '('. $result['age'] .')' );
    }
  7. ステートメントを閉じる
    $stmt = null;
  8. データベースの接続を閉じる (省略可)
    $dbh = null;

練習問題 今回の理解度をチェック。

完成例を参考に「tranig」フォルダ内の「index.php」に
カテゴリ一覧ページを作成して下さい。

完成イメージ

RERUN

解答例
chapter17/training/index.php
<?php
  // データベースの定数
  define('DB_NAME', 'mini_cms_app');
  define('DB_USER', 'root');
  define('DB_PASSWORD', ''); // パスワード (MAMPは「root」)
  define('DSN', 'mysql:host=localhost;dbname='. DB_NAME . ';charset=utf8');

  // XSS 対策
  function h($s) {
    return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
  }

  try {
    // データベースへ接続
    $dbh = new PDO(DSN, DB_USER, DB_PASSWORD);

    // エラー発生時に「PDOException」という例外を投げる設定に変更
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // SQL文の作成
    $sql = 'SELECT * FROM categories';

    // SQLを実行
    $stmt = $dbh->query($sql);

    // 実行結果を連想配列として取得
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

    // データベースとの接続を終了
    $dbh = null;

  } catch (PDOException $e) {
    // 例外発生時の処理
    echo 'エラー' . h($e->getMessage());
    exit();
  }
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>カテゴリ一覧</title>
</head>
<body>
  <h1>カテゴリ一覧</h1>

  <table border="1">
    <thead>
      <th>ID</th>
      <th>カテゴリ名</th>
    </thead>
    <?php foreach($result as $row) : ?>
    <tr>
      <td><?php echo h($row['id']) ?></td>
      <td><?php echo h($row['category_name']) ?></td>
    </tr>
    <?php endforeach; ?>
  </table>
</body>
</html>

解答例は全問題のチェックボックスが on になるとご覧いただけます。

まとめ SQLの実行は、
プリペアド・ステートメントで。

PHPでデータベースを操作する際は、SQLインジェクションなどの脆弱性に注意する必要がある。

  • SELECT文は結果を連想配列として取得する
  • SQLに変数を埋め込まない
  • プリペアド・ステートメントを使う