sit in a circle and chat happily

データベース 5日目

投稿日:2022-02-07
更新日:2022-03-24
db

※1・2限目は一覧表示の続き。

共通で使用する関数

<?php
// HTMLでのエスケープ処理をする関数
function h($var) {
  if (is_array($var)) {
    return array_map('h', $var);
  } else {
    return htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
  }
}

// デバック用関数
function v($val) {
  echo '<pre>';
  var_dump($val);
  echo '</pre>';
}

データベースの接続処理を関数化する(P146)

// MySQL(MariaDB)に接続する関数
function connectPractice(){
  $dbobj = mysqli_connect('localhost', 'Tanaka', 'Manager')
    or die('DBに接続できませんでした');
  mysqli_select_db($dbobj, 'practice');
  // DB領域の選択:practice領域を選択
  mysqli_set_charset($dbobj, 'utf8');
  // 文字化け対策
  return $dbobj;
}

MariaDBに接続する関数

常に同じ設定でDBへ接続するのであれば、DB接続用関数を定義しておくと便利。

mysqli_connect()関数が返す接続情報オブジェクトを$dbobj変数に管理する。

  $dbobj = mysqli_connect('localhost', 'Tanaka', 'Manager')
    or die('DBに接続できませんでした');

$dbobj変数が管理する接続情報オブジェクトは関数内にあるため、関数外からは参照できない。returnを使って、関数呼び出し部分に返し、関数外で使用できるようにする。

return $dbobj;

各ページのデータベース接続処理部分で関数を呼び出す

MariaDBに接続する関数を呼び出す場合

【変更前】
$dbobj = mysqli_connect('localhost', 'Tanaka', 'Manager')
or die('DBに接続できませんでした');
mysqli_select_db($dbobj, 'practice');

mysqli_set_charset($dbobj, 'utf8');

【変更後】
$dbobj = connectPractice();

connectPractice()関数の返り値である接続情報オブジェクトを関数外で宣言した$dbobjで受け取り関数外のプログラムで引き続き使用できるようにする。

レコードの登録

まずはシンプルなレコード登録ソースコードで動きを確認する。

<?php
require_once __DIR__ . '/functions.php';
$dbobj = connectPractice();
mysqli_query($dbobj, 'INSERT INTO stationery SET item="分度器", price=240,
stock=6, keyword="事務", maker=2') or die(mysqli_error($dbobj));
?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>レコードを登録する</title>
</head>

<body>
  <div>
    新しい商品を追加しました
  </div>
</body>
</html>

DBの接続部分はレコードの一覧表示と同じ。実行するSQL文がSELECT文からINSERT文に変わっている。今回はSQL文を実行した結果をPHP内で利用しないため、変数に管理する必要はない。

ブラウザ出力内容

新規商品入力フォームを作成

新しい商品の情報を入力するフォームを作成する。traderテーブルからメーカー名を取得してラジオボタンにしている点を確認する。

<?php
$debug = true;
// デバック領域の表示(false)非表示(true)
require_once dirname(__FILE__) . '/functions.php';
// 共通関数を呼び出す
$dbobj = connectPractice();
// いつものDB接続処理を関数で実行
// 戻り値の接続許可証を変数$dbobjに代入
$sql = 'SELECT * FROM trader';
$trSet = mysqli_query($dbobj, $sql) or die(mysqli_error($dbobj));
?>
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <link href="style.css" type="text/css" rel="stylesheet">
  <title>商品管理システム</title>
</head>

<body>
  <?php if ($dbobj) : ?>
    <div class="debug">
      <p>デバッグ用</p>
      <p>$sql : <?php print $sql; ?></p>
    </div>
  <?php endif; ?>
  <div id="container">
    <div id="head">
      <h1>新規登録</h1>
    </div>
    <div id="content">
      <form action="insert.php" method="post">
        <fieldset>
          <legend>新しい商品の情報</legend>
          <dl>
            <dt><label for="item">商品名 <span>※必須</span></label></dt>
            <dd><input name="item" type="text" id="item" size="20" maxlength="10"></dd>
            <dt><label for="price">価格</label></dt>
            <dd><input name="price" type="text" id="price" size="10" maxlength="10">円</dd>
            <dt><label for="stock">在庫</label></dt>
            <dd><input name="stock" type="text" id="stock" size="10" maxlength="10"></dd>
            <dt><label for="keyword">キーワード</label></dt>
            <dd><input name="keyword" type="text" id="keyword" size="50" maxlength="255"></dd>
          </dl>
        </fieldset>
        <fieldset>
          <legend>メーカーの情報</legend>
          <dl>
            <dt>メーカー<span>※必須</span></dt>
            <dd>
              <?php while ($trData = mysqli_fetch_assoc($trSet)) : ?>
                <label>
                  <input name="maker" type="radio" value="<?php echo h($trData['m_id']); ?>">
                  <?php echo h($trData['company']); ?>
                </label>
              <?php endwhile; ?>
            </dd>
          </dl>
        </fieldset>
        <div class="submit_btn"><input type="submit" value="登録"></div>
      </form>
      <p><a href="index.php" onclick="return confirm('一覧に戻りますか?')">一覧に戻る</a></p>
      <!--#content-->
    </div>
    <!--#container-->
  </div>
</body>

</html>

メーカー用ラジオボタン

DBから取得したtraderテーブルのリソースを使用して作成している。

$sql = 'SELECT * FROM trader';
$trSet = mysqli_query($dbobj, $sql) or die(mysqli_error($dbobj));

取得したリソースのm_idフィールドとcompanyフィールドの値を利用してinputタグを作成している。

<?php while ($trData = mysqli_fetch_assoc($trSet)) : ?>
  <label>
    <input name="maker" type="radio" value="<?php echo h($trData['m_id']); ?>">
    <?php echo h($trData['company']); ?>
  </label>
<?php endwhile; ?>

上記のPHP記述から以下のHTMLソースコードが生成される。

<label><input name="maker" type="radio" value="1">ペン工房</label>
<label><input name="maker" type="radio" value="2">小鳥文具</label>
<label><input name="maker" type="radio" value="3">黒木屋</label>

確認機能付の戻るボタン

入力フォームから他のページに移動してしまうと入力中の値がクリアされてしまう。ユーザの不用意な操作で入力した値が消えてしまわないようJavaScriptで確認画面を表示させている。

<a href="index.php" onclick="return confirm('一覧に戻りますか?')">一覧に戻る</a>

新規商品の登録

フォームから受け取った値をDBに登録するページを作成する。必須項目の入力値チェックやSQL文作成の手順を確認する。

<?php
$debug = true;
require_once dirname(__FILE__) . '/functions.php';

// POST形式で受け取った値を変数に代入
// 値を受け取った場合は「ユーザ入力値」「value値」を代入
// 値を受け取れない場合は「NULL」を代入
$item = isset($_POST['item'])       ? $_POST['item']    : NULL;
$price = isset($_POST['price'])     ? $_POST['price']   : NULL;
$stock = isset($_POST['stock'])     ? $_POST['stock']   : NULL;
$keyword = isset($_POST['keyword']) ? $_POST['keyword'] : NULL;
$maker = isset($_POST['maker'])     ? $_POST['maker']   : NULL;
// 先頭・末尾のホワイトスペースを削除
$item = trim($item);
$price = trim($price);
$stock = trim($stock);
$keyword = trim($keyword);
$maker = trim($maker);
// v($item);
// 値が届かない場合三項演算子でNULLが代入されるが
// trim関数後は空文字になる
// trim関数実行前であれば
// 「NULL」と「空文字」で
// 「値が届かなかった」か「空文字が届いた」かを判定できる。
// trim関数実行後はどちらも空文字になるので判定できない。

// ▲ ▲ここまでが値の準備▲ ▲

// 処理開始
$sql = ''; //SQL文用変数
$message = ''; //HTMLに表示するメッセージ変数
$btn = ''; //リンク用の変数
if ($item == '' or $maker == '') {
  // 必須項目が入力・選択されていない時の処理
  // NULLでも空文字でもtrueになる
  $message = '必須項目を入力してください';
  $btn = '<a href="entry.php" onclick="history.back(); return false;">
  フォームに戻る</a>';
  // ◆JS有効:onclick属性が動く
  //  history.back()を使って履歴で戻る
  //  履歴で戻るとユーザ入力値が維持される
  //   ※簡易敵なユーザ入力値維持
  //     ただし、本当はSESSIONが望ましい
  //  a要素のonclickイベントに「return false;」
  //   href属性を使ったページ遷移を停止
  // ◆JS無効:onclick属性が動かない
  //  href属性を使ってページ遷移
  // ただし、ユーザ入力値は維持されない
} else {
  // 必須項目が入力・選択されている時の処理
  // DBにレコードを登録
  $dbobj = connectPractice();
  // DBに接続して接続許可証を変数$dbobjに代入

  $item = mysqli_real_escape_string($dbobj, $item);
  // (接続許可証, 対象文字列)
  $price  = mysqli_real_escape_string($dbobj, mb_convert_kana($price, 'n'));
  $stock  = mysqli_real_escape_string($dbobj, mb_convert_kana($stock, 'n'));
  $keyword  = mysqli_real_escape_string($dbobj, $keyword);
  $maker  = mysqli_real_escape_string($dbobj, mb_convert_kana($maker, 'n'));
  // mysqli_real_escape_string関数
  // ◆セキュリティ用の関数
  // SQL文で意味のある記号を無害化してくれる
  // 文字列開始・終了を意味するクォートを悪用した攻撃を防ぐ
  //   ※クォート文字をエスケープする
  // DELETE時に詳細解説予定
  // SQLインジェクション攻撃に対応する
  // 第1引数:接続許可証(DBに意味のある記号を確認する)
  // 第2引数:対象文字列(受け取ったユーザ入力値)
  // 戻り値:無害化された文字列
  // ※関数実行時には接続許可証が必要になるので
  //   DB接続後に呼び出す
  //   DB接続前に呼び出すことはできないので注意

  // mb_convert_kana関数
  // 全角・半角等を変換する関数
  // 第1引数:変換対象の値
  // 第2引数:変換方法を意味のある文字列で指定する
  //  'n':「全角」数字を「半角」に変換

  $sql = sprintf(
    'INSERT INTO stationery SET
        item="%s", price=%d, stock=%d, keyword="%s", maker=%d',
    $item,
    $price,
    $stock,
    $keyword,
    $maker
  );
  // sprintf関数(String print formatted)
  // 書式を決めて、文字列を整える関数
  // 第1引数:フォーマット対象文字列
  //  虫食い部分には「%s」や「%d」を記述する
  //  「%s」部分には文字列型の値を挿入
  //  「%d」部分には整数型の値を挿入
  //  「あああ」や「aaa」等の数値に変換できない
  //  数値に変換できない値は「0」に変換
  // 第2引数以降:フォーマット文字列の虫食い部分に追加する値
  // 型を指定してSQL文を作成することで
  // SQLインジェクション攻撃を防ぐ
  // ユーザ入力値を使ってSQL文を作成する際は
  // 必ずsprintf関数を使用する
  // ★文字列連結や変数展開を使用しないこと!

  mysqli_query($dbobj, $sql) or die(mysqli_error($dbobj));
  $message = '新規登録しました';
  $btn = '<a href="index.php">一覧に戻る</a>';
}
?>
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <link href="style.css" type="text/css" rel="stylesheet">
  <title>商品管理システム</title>
</head>

<body>
  <?php if ($debug) : ?>
    <div class="debug">
      <p>デバッグ用</p>
      <pre>$sql : <?php var_dump($_POST); ?></pre>
    </div>
  <?php endif; ?>
  <div id="container">
    <div id="head">
      <h1>新規登録</h1>
    </div>
    <div id="content">
      <p><?php echo $message; ?></p>
      <p><?php echo $btn; ?></p>
      <!--#content-->
    </div>
    <!--#container-->
  </div>
</body>

</html>

必須項目の入力チェック

必須項目の商品名とメーカーが入力されているかチェックし処理を分岐させる。

if($item == '' OR $maker =='') {
  【必須項目の商品名、メーカーが入力されなかった時の処理】
} else {
  【必須項目が入力された時の処理】
}

必須項目が入力されなかった時

$messageに必須項目入力を促すメッセージを、$btnに入力フォームに戻るためのリンクを格納し、body要素の中で表示させる。

JavaScriptで元のページに戻る

フォームに戻るためのリンクにはhref属性とonclick属性を指定する。href属性で指定したリンク先はJavaScriptが無効の時に使用される。JavaScriptが有効な場合はonclick属性で指定したJavaScriptが実行され、一つ前の履歴に戻るようになっている。履歴を使ってもとのページに戻ることで入力済みの値を保持することができる。

<a href="entry.php" onclick="history.back(); return false;">
  フォームに戻る</a>
  // ◆JS有効:onclick属性が動く
  //  history.back()を使って履歴で戻る
  //  履歴で戻るとユーザ入力値が維持される
  //   ※簡易敵なユーザ入力値維持
  //     ただし、本当はSESSIONが望ましい
  //  a要素のonclickイベントに「return false;」
  //   href属性を使ったページ遷移を停止
  // ◆JS無効:onclick属性が動かない
  //  href属性を使ってページ遷移
  // ただし、ユーザ入力値は維持されない

今回はJavaScriptを使って入力済み値を保持しているが、確実に値を保持したい場合はPHPのセッション等を使ってロジックを組むことになる。

必須項目が入力された時

必須項目が入力されていた場合のみ、DBに登録する。登録の手順を確認する。

全角・半角を変換する関数

全角文字と半角文字を変換する際に次の関数を使う。日本語のみで使える関数。文字エンコードを指定しなかった場合は内部文字エンコードを使用する。

string mb_convert_kana(string変換文字列[,stringオプション
[,stringエンコード]])

mb_convert_kana関数に「n」オプションを付けることでフォームから受け取った文字を半角数字に変換している。

mb_convert_kana($price, 'n')
  // mb_convert_kana関数
  // 全角・半角等を変換する関数
  // 第1引数:変換対象の値
  // 第2引数:変換方法を意味のある文字列で指定する
  //  'n':「全角」数字を「半角」に変換

DBを安全に使うために

SQL文中で意味を持つ「”」「’」の特殊文字をエスケープする。

string mysqli_real_escape_string(mysqli接続情報,
stringエスケープされる文字列

エスケープしないでSQL文を実行すると悪意あるユーザにDBを破壊されたり、情報を盗み出されたりする。ユーザの入力した値をSQL文に組み込む場合はこの関数を使うようにする。

  $item = mysqli_real_escape_string($dbobj, $item);
  $price  = mysqli_real_escape_string($dbobj, mb_convert_kana($price, 'n'));
  $stock  = mysqli_real_escape_string($dbobj, mb_convert_kana($stock, 'n'));
  $keyword  = mysqli_real_escape_string($dbobj, $keyword);
  $maker  = mysqli_real_escape_string($dbobj, mb_convert_kana($maker, 'n'));
  // mysqli_real_escape_string関数
  // ◆セキュリティ用の関数
  // SQL文で意味のある記号を無害化してくれる
  // 文字列開始・終了を意味するクォートを悪用した攻撃を防ぐ
  //   ※クォート文字をエスケープする
  // DELETE時に詳細解説予定
  // SQLインジェクション攻撃に対応する
  // 第1引数:接続許可証(DBに意味のある記号を確認する)
  // 第2引数:対象文字列(受け取ったユーザ入力値)
  // 戻り値:無害化された文字列
  // ※関数実行時には接続許可証が必要になるので
  //   DB接続後に呼び出す
  //   DB接続前に呼び出すことはできないので注意

書式を整える関数

指定の書式に整えるには次の関数を使う。
sprintif(えすぷりんとえふ)

string sprintf(string変換指定子を含むフォーマット文字列
[,mixed変換指定子に当てはめる値
[,mixed変換指定子に当てはめる値
[,...]]])

フォーマット文字列内にある変換指定子(%sや%d等)に第2引数以降で指定した値を当てはめた文字列を生成する。あてはめる値は変換指定子の内容に基づき生成される。今回のプログラムではSQL文の生成に使われているので確認してみる。

$sql = sprintf('INSERT INTO stationery SET
  item="%s", price=%d, stock=%d, keyword="%s", maker=%d',
  $item, $price, $stock, $keyword, $maker);
  // sprintf関数(String print formatted)
  // 書式を決めて、文字列を整える関数
  // 第1引数:フォーマット対象文字列
  //  虫食い部分には「%s」や「%d」を記述する
  //  「%s」部分には文字列型の値を挿入
  //  「%d」部分には整数型の値を挿入
  //  「あああ」や「aaa」等の数値に変換できない
  //  数値に変換できない値は「0」に変換
   // 第2引数以降:フォーマット文字列の虫食い部分に追加する値
  // 型を指定してSQL文を作成することで
  // SQLインジェクション攻撃を防ぐ
  // ユーザ入力値を使ってSQL文を作成する際は
  // 必ずsprintf関数を使用する
  // ★文字列連結や変数展開を使用しないこと!
変換指定子内容
%s文字列として扱う
%d10進数の整数として扱う
今回使用している変換指定子

実行結果を表示する

実行結果によって内容を変えたメッセージとリンクを表示させる。

<p><?php echo $message; ?></p>
<p><?php echo $btn; ?></p>

編集用フォームへのリンクを作成する

トップページから編集用フォームへのリンクを作成する。index.phpの該当箇所を下記のように変更する。

【変更前】
<td>編集</td>
【変更後】
<td><a href="change.php?id=<?php echo h($data['id']); ?>">編集</a></td>

リンク先のURL「change.php」の後ろにクエリ文字列を付加して編集用フォームにid番号を送る。このid番号を使って編集するレコードを決める。

カテゴリー