2016年9月3日

再入荷お知らせメールの話3


前回からの引き続きで、再入荷お知らせメールの話3。
今回は入力フォームのサンプルを作ってみました。

「初めてのPHP5(オライリー本)」のコードを参照しました(解説はオライリー本参照)。
PHP入門書には、この手の入力フォームのコードが載っていますので、読み解きやすいものを選ばれると良いでしょう。

入力画面→入力項目のチェック→登録
↑     ↓エラー
↑←←←←←↓


私が行ったテストはソースコードレビューのみで、他はほとんど行っていません。
本番環境に実装する場合には、ご自身の責任でしっかりとテストを行ってください(ソフトウェアテストについて理解しておく必要があります)。


1. コーディング(人柱版)

<?php

if (isset($_POST['_submit_check']) && $_POST['_submit_check']) {
  if ($form_errors = validate_form()) {
    show_form($form_errors);
  } else {
    process_form();
  }
} else {
  if(! isset($_GET['pid'])) {
    $_POST['pid'] = '';
  } else {
    $_POST['pid'] = $_GET['pid'];
  }
  if(! isset($_POST['mail'])) {
    $_POST['mail'] = '';
  }
  show_form();//最初の画面表示
}


function show_form($errors = '') {
  if ($errors) {
    $error_text = implode('<br/>',$errors);
  } else {
    $error_text ='';
  }

?>

<!--入力画面-->
<form method="POST" action="<?php print $_SERVER['SCRIPT_NAME']; ?>">
  <input type="text" name="mail" value="<?php print htmlentities($_POST['mail']) ?>" />
  <input type="hidden" name="pid" value="<?php print htmlentities($_POST['pid']) ?>" />
  <input type="hidden" name="_submit_check" value="1" />
  <input type="submit" value="登録する" />
</form>

<?php

  print $error_text;
}


function validate_form() {
  $errors = array();
  if (! strlen(trim($_POST['mail']))) {
    $errors[] = 'メールアドレス未入力エラー';
  } elseif (! preg_match('/^[^@\s]+@([-a-z0-9]+\.)+[a-z]{2,}$/i', $_POST['mail'])) {
    $errors[] = 'メールアドレス構文エラー';
  }
  if (! strlen(trim($_POST['pid']))) {
    //正規のリンクから呼び出されていないのでトップに飛ばすなりする
    $errors[] = '商品ID未入力エラー';
  } elseif(! is_numeric($_POST['pid'])) {
    //正規のリンクから呼び出されていないのでトップに飛ばすなりする
    $errors[] = '商品ID数字エラー';
  }
  //商品IDがカラーミーショップに存在しているか
  //pidとmailの組み合わせが、既に登録されていないか
  //在庫はゼロかなどをチェック

  return $errors;
}


function process_form() {
  //データベース登録
  $dsn = 'mysql:dbname=mydb;host=localhost';
  $user = 'root';
  $password = '';

  try{
    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    $sql = 'INSERT INTO restock SET pid=?, mail=?';
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array($_POST['pid'], $_POST['mail']));
  } catch (PDOException $e){
    //print('Error:'.$e->getMessage());
    die();
  }

  //登録完了画面
print <<<_HTML_
登録ありがとうございました。<br/>
_HTML_;



  exit();
}
?>

2. 簡単な説明


ウィンドウ(or モーダルウィンドウ)で入力フォームを開くと良さげ、閉じるボタンも付けて。その他、デザイン的な体裁を整える必要があります。

表示させたい項目があれば、カラーミーショップAPIで取ってくる。

isset()がないと、最初の画面表示でNoticeが出るので。

メールアドレス構文チェックの正規表現も「初めてのPHP5(オライリー本)」から。検索すると、たくさん例文が見つかりますが、どれがよいのやら。

pidのエラーは明らかにオカシイので、入力画面に戻さず、トップページに飛ばすなりする。

htmlentities箇所は定番の、htmlspecialchars($str, ENT_QUOTES, 'UTF-8')でよいかと。

CSRF対策をしていません(IPAの「安全なウェブサイトの作り方」参照)。
ログイン不要の入力フォームの場合はもともといたずらし放題なので、トークンのやりとりをしないアプリケーションも見かけます(焼け石に水という考え方だと思います)。
今回サンプルとして作ったコードも、他人のメールアドレスを入力して登録できる仕組みなので、考え方も弱点も似たようなものです。
ただ、本番環境で動かすなら、わずかな問題でも対応するのがよい開発者。検索すると、トークンのやりとりで対策するサンプルが見つかります。

いたずら対策として、カラーミーショップのアカウントにログインするよう促して、そこからメールアドレスを取ってくるか(アカウント登録が必須になる)。
あと、CAPTCHAつけたり。

登録メール宛に内容を送信し、リンクをクリックしたのちに登録するような仕組みにするか(登録に手間がかかって嫌かもしれないが)。認証コードを送信して、入力してもらうか。


3. おわりに 


省略箇所もありますので、のこりを自分で仕上げられる人用です

この手のウェブアプリケーションでもっとも気を遣う点は、セキュリティだと思います(知識をインプットしておかないと、なにをどこまでがわからない)。
IPAの「安全なウェブサイトの作り方」が読みやすくて、入門編として勉強になりました(同ページ内の「安全なSQLの呼び出し方」もチェック)。
引き続き、ウェブや書籍でインプットしていきたいと思います。

【追記】
時折見かけますが、クエリパラメータをディレクトリ風にして渡すこともできます。興味あれば、検索してみてください。

【追記2】
徳丸浩さん著の「体系的に学ぶ安全なWebアプリケーションの作り方」を読みましたが、大変分かりやすかったです。これは手元に置いておきたい。