こんにちは、
運動後のタンパク質摂取に夢中なサトウです。
さて、本題に入りたいと思います。
phpやMySQLの学習を進めると「プレースホルダ」という言葉を聞いたことがある方も多いのではないでしょうか。
「プレースホルダ」はセキュリティ上非常に重要な考え方ですが、具体的な処理の方法について理解できない方も少なくないのでは、、、。と思い、今回は「プレースホルダ」について分かりやすく説明していきます。
phpやMySQLを学び始めた方や、「プレースホルダ」について分からない方は、是非読んでみてくださいね。
目次
プレースホルダとは
SQLインジェクションとは
プレースホルダを解説する前に前提知識として必要になる「SQLインジェクション」について解説していきます。SQLインジェクションとは、開発者の想定をしていないSQLを組み立てて、データベースに対して実行させようとする攻撃方法です。
例えば以下のようなSQLを実行するphpプログラムあったとします。
「users」というテーブルにある「username」カラムとフォームの入力情報($username)が一致しているか検索するという内容です。
サンプルコード
1 |
$sql = 'SELECT * FROM users where username = $username'; |
例:入力フォーム
入力フォームに「ウェブカツ太郎」と入力した場合のSQLはこうなります。
サンプルコード
1 |
$sql = "SELECT * FROM users where username = 'ウェブカツ太郎'"; |
これが開発者の想定している通常のSQL文です。
次にこの入力フォームに以下のように入力するとどうなるでしょうか。
(※このプログラムは絶対に実行しないようにしてください。)
サンプルコード
1 |
$sql = "SELECT * FROM users WHERE username='';DELETE FROM users--"; |
このSQLは「;」によってSQL文を終了させて、usersテーブルから全テーブルを削除する(DELETE FROM users)といった内容になります。
入力フォームにこのようなSQL文を入力されてしまうと、開発者の全く想定しないデータベース操作が実行できてしまいます。
これがSQLインジェクション(不正なSQLを「injection:注入」すること)です。ユーザー入力が伴う箇所は、SQL文の中に直接phpの変数を書かずにデータベースに登録する仕組みが必要となります。
プレースホルダの概要
そこで利用するのがプレースホルダという仕組みです。
プレースホルダは「変数のように値が変動する箇所」と「データベースへ登録する処理」の間に挟むことで変動する箇所を「値」として処理するしくみです。入力フォームに万が一不正な値(SQL文)が入力されても、SQL命令に関わるような殊文字」は無効化されるため、SQL文として実行されることはありません。
プレースホルダはSQL文の変数部分を「:」で始まる文字列で指定して書くことができます。
サンプルコード
1 |
$sql = 'SELECT * FROM users where username = :username'; |
変数部分($username)がプレースホルダ(:username)になっていますね。
プレースフォルダでを使ってフォームからデータ登録してみよう!
ここからは実際にプレースホルダを使ってフォームから入力した情報を登録してみましょう。
入力フォームから情報を入力してデーベースに登録してみます。
入力フォーム
サンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
<?php // ログの表示 ini_set('log_errors','On'); ini_set('error_log','php.log'); // (2)post送信されているかemptyでチェック if(!empty($_POST['username'])){ //(3)post情報を変数に入れる $username = $_POST['username']; // try{ // (4)DBへ接続の準備 $dsn = 'mysql:dbname=mysql_test;host=localhost;charset=utf8'; $user = 'root'; $password = パスワード; $options = array( // SQL実行失敗時には例外をスローしてくれる PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // カラム名をキーとする連想配列で取得する.これが一番ポピュラーな設定 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // バッファードクエリを使う(一度に結果セットをすべて取得し、サーバー負荷を軽減) // SELECTで得た結果に対してもrowCountメソッドを使えるようにする PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,); // (5)PDOオブジェクトを生成してDBへ接続 $dbh = new PDO($dsn,$user,$password,$options); // (6)SQL文の作成:usersテーブルに名前情報を登録する。 $sql = 'INSERT INTO users(user_name,create_date) VALUES(:user_name,:create_date)'; // (7)prepareメソッドを使ってSQLへ接続準備 $stmt = $dbh->prepare($sql); // (8)SQLの実行:executeメソッドでプレースホルダに値をセット $stmt->execute(array( ':user_name' => $username, ':create_date' => date('Y-m-d H:i:s') )); }catch(Exception $e){ error_log('エラーが発生しました:' .$e->getMessage()); } } ?> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="style.css"> <title>php:プレースホルダ</title> </head> <body> <section class="main"> <form action="" method="post" class="form-container"> <!-- (1)formタグからpost送信 --> <label for="" class="label-username">お名前:</label> <input type="text" name="username" class="input-username" placeholder="お名前"> <input type="submit" class="btn" value="送信する"> </form> </section> </body> </html> |
処理の流れ
(2)post送信されているかemptyでチェック
(3)post情報を変数に入れる
(4)DBへ接続の準備
(5)PDOオブジェクトを生成してDBへ接続
(6)SQL文の作成:usersテーブルに名前情報を登録する
(7)prepareメソッドを使ってSQLの実行準備
(8)SQLの実行:executeメソッドでプレースホルダに値をセット
※プレースホルダを使用してSQLを実行しているのが(6)(8)になります。
formタグからpost送信
まずformタグの中からusernameをキーにしてpost送信します。
サンプルコード(該当部分)
1 2 3 4 5 |
<!-- (1)formタグからpost送信 --> <form action="" method="post" class="form-container"> <label for="" class="label-username">お名前:</label> <input type="text" name="username" class="input-username" placeholder="お名前"> <input type="submit" class="btn" value="送信する"> |
post送信されているかemptyでチェック
post送信しているかどうかは!emptyで判定しています。
サンプルコード(該当部分)
1 2 3 |
// (2)post送信されているかemptyでチェック if(!empty($_POST['username'])){ } |
post情報を変数に入れる
postされていれば、その情報を$usernameという変数に入れます。
サンプルコード(該当部分)
1 2 |
//(3)post情報を変数に入れる $username = $_POST['username']; |
DBへ接続の準備
例外処理(try-catch)を書いて、データベースへの接続準備を開始します。
サンプルコード(該当部分)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
try{ // (4)DBへ接続の準備 $dsn = 'mysql:dbname=mysql_test;host=localhost;charset=utf8'; $user = 'root'; $password = パスワード; $options = array( // SQL実行失敗時には例外をスローしてくれる PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // カラム名をキーとする連想配列で取得する.これが一番ポピュラーな設定 PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // バッファードクエリを使う(一度に結果セットをすべて取得し、サーバー負荷を軽減) // SELECTで得た結果に対してもrowCountメソッドを使えるようにする PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,); }catch(Exception $e){ error_log('エラーが発生しました:' .$e->getMessage()); } |
PDOオブジェクトを生成してDBへ接続
この例ではPDOオブジェクトでDB接続します。ここでは(4)で設定した情報を引数に使用します。
サンプルコード(該当部分)
1 2 |
// (5)PDOオブジェクトを生成してDBへ接続 $dbh = new PDO($dsn,$user,$password,$options); |
SQL文の作成:usersテーブルに名前情報を登録する
ここでプレースホルダを使用します。入力フォーム情報を入れた$usernameをそのまま使用せず、プレースホルダで「:user_name」と記載しています。テーブルに情報登録する際はINSERT INTOというSQL文を使用します。
サンプルコード(該当部分)
1 2 |
// (6)SQL文の作成:usersテーブルに名前情報を登録する。 $sql = 'INSERT INTO users(user_name,create_date) VALUES(:user_name,:create_date)'; |
prepareメソッドを使ってSQLの実行接続準備
SQL実行前の準備処理でprepareメソッドを使用します。
サンプルコード (該当部分)
1 2 |
// (7)prepareメソッドを使ってSQLの実行準備 $stmt = $dbh->prepare($sql); |
SQLの実行:executeメソッドでプレースホルダに値をセット
(6)で設定したプレースホルダ使ってデータベースに登録処理をexecuteメソッドを使って実施しています。
プレースホルダで入力情報を値に無害化したことで、安全にデータベースに登録することが可能になります。
サンプルコード(該当部分)
1 2 3 4 5 |
// (8)SQLの実行:executeメソッドでプレースホルダに値をセット $stmt->execute(array( ':user_name' => $username, ':create_date' => date('Y-m-d H:i:s') )); |
まとめ
いかがでしたか?今回は、「プレースホルダ」について説明しました。
phpやMySQLを学び始めた頃は、「プレースホルダ」について難しく感じることあると思います。ですがまずは簡単な処理(データベースに情報を登録する処理等)からコードを書くようにしていけば、「プレースホルダ」について体系的に理解できる日が来ると思います。焦らずにゆっくりと理解を深めていってくださいね。
この記事が、phpの学習に役立つと嬉しいです。