【39:PDO 】ログインシステム - PHPでDBに接続
プロジェクトNo.39:PDO - DEMO
PHPの変数やループなどの基本的な書き方はわかってきたら、次のステップはPHPからのDB接続。
単にDB接続と言っても色んな書き方があったり、セキュリティを気にしなくてはいけなかったり、結局どういう風に書けば良いの?
ということで調べてまとめてみました。
PHPからDB操作をする
PHP5.5.0の現在、PHPからDB操作をする仕組み(データアクセス抽象化レイヤ)は、公式でPDO(PHP Data Objects)またはMySQLiを推奨されています。
今回は比較的よく使われているPDOを使ってみたいと思います。
下準備
とその前に操作する対象となるDBの下準備をしておきます。
DBはMySQLを使用します。
ローカルの開発環境はMAMPでApacheもMySQLもPHPも用意されています。(いやー便利)
「lesson1」データベースを作り、「ow_login」テーブルも作ります。
フィールドは簡単に「user」「pass」だけ作っておきます。
レコードも適当にそれぞれ「user01」「abcde」と入れておきます。
ソースコード
超簡素なログインシステムのソースコードは以下の通り。
次の項で解説します。
HTML
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <h1>Login form</h1> <form method="post" action="login_result.php"> user:<br> <input type="text" name="user"> <br> password:<br> <input type="text" name="pass"> <input type="submit" value="Login"> </form> </body> </html>
PHP
<?php // 接続情報 $servername = "localhost"; $username = "root"; $password = "root"; $dbname = "lesson1"; // インプット値 $i_user = (string)filter_input(INPUT_POST, 'user'); $i_pass = (string)filter_input(INPUT_POST, 'pass'); try { // DB接続 $pdo = new PDO("mysql:host=$servername;dbname=$dbname;charset=utf8", $username, $password); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // SQL発行 $stmt = $pdo->prepare("SELECT * FROM ow_login WHERE user = ? AND pass = ?"); $stmt->bindValue(1, $i_user); $stmt->bindValue(2, $i_pass); $stmt->execute(); // 結果の取得 if($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo $row['user'] . " is login."; }else{ echo "no user or no password"; } } catch(PDOException $e) { echo "Connection failed: " . $e->getMessage(); } // DB切断 $pdo = null; ?>
PDOを使って見る
DBに接続
$pdo = new PDO("mysql:host=$servername;dbname=$dbname;charset=utf8", $username, $password); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
PDOインスタンスを生成する
1行目は、DBに接続しに行き、PDOのインスタンスを生成しています。
第1引数で接続するためのDSNを指定します。
DSN(Data Source Name)とはデータベースに接続するために必要な情報です。
第2引数と第3引数はDBのユーザとパスワードです。
文字セット
それから、もし日本語の様なマルチバイト文字を扱うなら、文字セットが必要です。
SET NAMES sjisという風にSQLを発行するのではなくて、DSN上でセットするべきです。(DNS上のセットだとドライバ側にも対応するため)
SQL実行時のExceptionを取得する
2行目はSQL実行時のエラーをExceptionで取得するためのものです。
デフォルトでSQLのExceptionを取得してもいい気はするが・・・まぁ仕様は仕様なので。
エミュレーションをOFFにする
3行目はエミュレーションという機能をOFFにしています。
この機能についての理解するのに一番時間がかかった気がします。。。
エミュレーションとは、プリペアドステートメント(後述)がサポートされていないデータベースに対して擬似的に利用できる様にするもので、
デフォルトでONになっています。
エミューレーションがONだと、
パフォーマンスが良くなるのと、複数の同名プレースホルダ(バインドする先の名前)が使えます。
その代わりに、プリペアドステートメントで値をバインドする際のキャストと、文字セットに気を付けなければいけません。
パフォーマンスがどれだけ変わるかわからないし、同名プレースホルダも必要になる場面がどれだけあるか・・・
安全性やバグ予防からしてもOFFにしておいた方が良いかと思います。(それでもパフォーマンスを上げたい!とかの場合は気をつけて書く)
SQLの発行
SQLの発行方法は以下の3パターン。
- $pdo->query() (select用、セキュリティ無し、1回用)
- $pdo->exec() (insert、update、delete用、セキュリティ無し、1回用)
- $pdo_statement->execute() (セキュリティ有り、複数回に対応)
今回はユーザからの入力値を扱うので、3つ目のプリペアドステートメントというものを使います。
$stmt = $pdo->prepare("SELECT * FROM ow_login WHERE user = ? AND pass = ?"); $stmt->bindValue(1, $i_user); $stmt->bindValue(2, $i_pass); $stmt->execute();
プリペアドステートメントは一旦先にSQLの予約文を書いた後に、値を割り当てる(バインドする)方法です。
この割り当てる際にユーザの入力値のエスケープ処理が自動的に行われる様です。
(もし、query()を使ってユーザの入力値を直書きするとSQLインジェクションを受けてしまう可能性が生じます)
あと、入力されてきた値が定義されているか、または型が適切かどうか判断するために$_POST['name']ではなく以下の様に書きます。
$i_user = (string)filter_input(INPUT_POST, 'user');
結果レコードの参照
fetch()メソッドを使って結果レコードを取得します。
PDO::FETCH_ASSOCはカラム名をキーとする連想配列で取得するためモードです。
if($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo $row['user'] . " is login."; }else{ echo "no user or no password"; }
ちなみに今回のプログラムには関係ないが、
もし、複数件の結果がある場合はforeachでグルグル回しながら取得します。
foreach($stmt as $results){ echo "$results[user] $results[pass]\n"; }
DBの切断
一応書いたが、スクリプト終了時に起こる変数のガベージコレクションが行われるため、自動で切断されるため不要。
データベース処理が終わった後、HTMLレンダリングの他にまだやることがあり、それに膨大な時間を要するときのみ必要。
$pdo = null;