【ドットインストール】PHPで作る投票システムの解説

完成図:投票システム

ドットインストールのレッスン、「PHPで作る投票システム (全13回)」の備忘録です。復習用にお使いください。

最後のGoogle Chart Toolsについては触れておりません。ドットインストールの方でレッスンが用意されています。覚えることでもないので、さっと確認して使えます。

Google Chart Tools入門 (全12回)

デベロッパーサイトの英語が読めるようになったら、Google Chart Toolsに関しての記事を書きたいなと思っています。

USE文とユニークキー #02

#02 データベースの設定をしよう

GRANT文

GRANT文の基本書式は次の通り。

GRANT '権限' ON 'データベース名.テーブルト名' TO 'ユーザー名@ホスト名';

そして、今回のレッスンで使われていたもの。

grant all on dotinstall_poll_php.* to dbuser@localhost identified by '********';

これはdotinstall_poll_phpテーブルに対する、すべての権限(SELECT,UPDATEなど)dbuser@localhostに与えています。そして、このユーザーのパスワードを'********'に設定しています。

権限は、ONの次で4つの有効範囲をできます。

// グローバルレベル:
GRANT 権限 ON *.* TO user;

// データベースレベル:
GRANT 権限 ON db_name.* TO user;

// テーブルレベル:
GRANT 権限 ON db_name.table_name TO user;

// カラムレベル:
GRANT 権限 (カラム1, カラム2, ...) ON db_name.table_name TO user;

こちらによいサンプルが紹介されています。

Mysqlのユーザ/権限管理

また、この記事でもGRANT文は取り上げました。

【ドットインストール】ページング機能の解説

USE文

「USE データベース名」でデータベースに接続します。一度接続すれば、FROMでテーブルだけ指定すればよくなります。データベース名を省略できるようになりますが、省略しなくてもよいです。

ユニークキー

ユニークキーは値が重複することはできません。プライマリキーとは違いNULLを格納できます。また、NULLの値だけ重複することができます。

ここで、今回のデータベースの全体図を把握しておきましょう。

カラム 内容 補足
id 自動連番
answer 回答 将来的な拡張のため文字列
remote_addr 回答者のIPアドレス
user_agent ブラウザの情報
answer_date 回答日
modified 更新日
unique_answer IP * ブラウザ * 回答日 1日に1回という制限

セッションの有効範囲を理解する #03

#03 設定ファイルを作ろう

PHPのデータベース操作についてですが、こちらで専用の記事を作っています。図を用意していますので、ご覧ください。

PDOを使ったPHPでのデータベース基本操作

この章では1つの関数だけ確認しておきましょう。

session_set_cookie_params(有効期限(秒数), 動作するパス)

この関数の引数は最大5つです。詳しくはリンク先のPHPマニュアルを御覧ください。

session_set_cookie_params(0, '/poll_php/');

今回のレッスンでは有効期限は0、つまりブラウザが閉じられるまでで、有効範囲はpoll_phpというディレクトリ以下となっています。また、パスではなくドメインで有効範囲を指定する場合は3つ目の引数で指定します。

この関数の使用注意としてはsession_start()を呼び出す前にsession_set_cookie_paramsを呼び出しておく必要があります。

余談ですが、PHPマニュアルを見ると第1引数の有効期限が英語だとlifetimeという表現になっています。「命の時間」ですか。「生きている時間」、つまり「有効期限」となっているのですね。英語はこういうところが面白い表現だなと思います。

関数と設定のファイル分け #04

#04 よく使う関数を登録しておこう

PHPのデータベース操作についてですが、こちらで専用の記事を作っています。図を用意していますので、ご覧ください。

PDOを使ったPHPでのデータベース基本操作

関数定義についてですが、h()はPHPではおなじみのhtmlspecialchars()の省略形です。そして、もう1つのconnectDb()。これはPDOでデータベース接続してPDOオブジェクトを返します。

私はdb.phpというファイルにデータベースの接続を行うまでの処理をまとめていましたが、データベース接続だけ関数定義にして、定数は定数でファイルを分けた方がいいのかなと思いました。

<?php
/*** db.php ***/

define('DB_HOST', 'localhost');
define('DB_NAME', 'データベース名');
define('DB_USER', 'ユーザー名');
define('DB_PASSWORD', 'パスワード');
// 文字化け対策
$options = array(PDO::MYSQL_ATTR_INIT_COMMAND=>"SET CHARACTER SET 'utf8'");

error_reporting(E_ALL & ~E_NOTICE);

// データベースの接続
try {
    $dbh = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASSWORD, $options);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    echo $e->getMessage();
    exit;
}
?>

idで選択されものを判別 #05

#05 投票画面を作ってみよう

画面を図解。

図:投票システムのデザイン

見栄えの変化はCSSで定義しておく #06

#06 投票結果を取得してみよう

クリックイベントハンドラの中のthisはクリックされたものを指します。今回はクラスcandidateに対してクリックイベントを設定していますので、そのクラスが付けられている画像をthisの対象となります。

今回のクリックイベントの内容は以下の通りです。

  1. candidateクラスの付いてるものからselectedクラスを取り除く。
  2. クリックされたものにselectedクラスをつける。
  3. IDanswer、つまりテキストフィールドの値を画像のid値に書き換える。

なぜ、1についての処理ですが、何故candidateクラス全体にremoveClass()を行うのかと言うと、
画像がクリックする度に前にクリックされた画像にremoveClass()を行おうとすると、クリックされた履歴を管理しないといけないですし、すべての画像のクラスを取り除いたほうが安全だからです。保証ですね。

CSRFで他のサイトからのデータ送信を防ぐ #07

#07 CSRF対策を施そう

CSRFって何?という方がいると思いますので、以前の解説記事を貼っておきます。

【ドットインストール】PHPで作る「簡易掲示版」の解説

今回はちゃんと自分の指定したformからの送信を受け付けるように設定しています。そのためにトークンを使っています。実際のサービスだと、ネット銀行やオンラインゲームとかのワンタイムパスワードみたいなものですね。

他の例をあげれば、サイトにログインするときに「画像に出ている英数字を入力してください」と出ますが、あれもCSRF対策の一種です。その場でランダムな値を発生させてそれをパスワードとしています。このパスワードがちゃんとこのサイトからデータを受診したという証明になります。CSRF対策がないと他のサイトからデータを送信され、それが悪意のあるプログラムなんかだったりしたら涙目です。例えば、リンクにプログラムを忍ばせておくとかですね。

図:CSRFの例

in_arrayで回答と正解かを判定する #08

#08 エラーチェックをしてみよう

in_array(探す値, 検索する配列)

if (!in_array($_POST['answer'], array(1, 2, 3, 4))) {}

このコードは1~4にanswerが含まれているかを判定します。そして、その否定ですから、1~4にanswerが含まれていない、つまり答えが1~4ではない時にこのif文は実行されます。

excute()の戻り値で投票制限を判断 #09-10

#09 投票結果を格納しよう (1)

#10 投票結果を格納しよう (2)

insert文で「:~」が使われていますが、これはパラメータマーカを指定しています。変数みたいなもんです。:(コロン)を使って、先に宣言だけしておいて、後から代入できるという便利なものです。

SQL文を実行する時にパラメータマーカを指定する場合は、query()ではなくprepareを使います。そして、bindParam()パラメータマーカに値をセットしてexcute()でSQLを実行します。excute()の引数でパラメータマーカを指定もできます。

excute()はパラメータマーカが異常であると、失敗したとしてfalseを返します。成功した場合はtrueを返します。なので、これを利用して1日1回の投稿制限をかけています。パラメータマーカにユニークキーが含まれているので、値が重複できません。(この場合の値とはユーザーを識別する情報)

if ($stmt->execute($params)) {
    $msg = "投票ありがとうございました!";
} else {
    $err = "投票は1日1回までです!";
}