one's way blog

ワクワクを生み出せるWebエンジニアを目指して。

【39:PDO 】ログインシステム - PHPでDBに接続

f:id:seintoseiya:20151002165853p:plain
プロジェクトNo.39:PDO - DEMO

PHPの変数やループなどの基本的な書き方はわかってきたら、次のステップはPHPからのDB接続。
単にDB接続と言っても色んな書き方があったり、セキュリティを気にしなくてはいけなかったり、結局どういう風に書けば良いの?
ということで調べてまとめてみました。

PHPからDB操作をする

PHP5.5.0の現在、PHPからDB操作をする仕組み(データアクセス抽象化レイヤ)は、公式でPDO(PHP Data Objects)またはMySQLiを推奨されています。

今回は比較的よく使われているPDOを使ってみたいと思います。

下準備

とその前に操作する対象となるDBの下準備をしておきます。
DBはMySQLを使用します。
ローカルの開発環境はMAMPApacheMySQLPHPも用意されています。(いやー便利)

「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;

全ソースはこちら

github.com