フリーPHPスクリプト配布サイト。
PEAR(The PHP Extension and Application Repository)とは、PHP標準のクラスライブラリ群のことです。世界中のPHPユーザーが作成した便利なクラスが登録されています。
PEARはオープンソースで開発が進められているため、誰でも利用することができます。
PEARの詳細な解説は、以下の公式ページに記載されています。
以下では、PEARに登録されている、PEAR::DBの基本的な使用方法を紹介します。
PEAR::DBは、色々な種類のデータベースに統一した記述でアクセスできるクラスライブラリです。
PHPは標準でMySQLやPostgreSQLやSQLiteなど、色々なデータベースに接続するための命令が用意されています。データベースの種類によって条件分岐させて命令を呼び出せば、プログラムを複数のデータベースに対応させることもできます。
ですがPEAR::DBを使用していれば、同じ命令で複数のデータベースに接続ができるようになるので、さらに開発が容易になります。(PEAR::DB内部が差異を吸収してくれます。)
PEAR::DBをインストールするには、コマンドプロンプトから pear install DB
を実行します。(あらかじめPEARをインストールしておく必要があります。)インストールができれば、PHPプログラムから require_once('DB.php');
とすれば呼び出すことができるようになります。
なお、PHP5.1.0以降はPDOと呼ばれるデータベース接続クラスを標準で利用できます。PEAR::DBよりも高速に動作するため、「PEAR::DBを利用したプログラムに機能追加する」「PDOの設定がされていない環境で作成する」のような特別な理由がなければ、PDOを利用する方がいいでしょう。
以下はPEAR::DBでMySQLを使用したサンプルプログラムです。アドレス帳のデータを保存したテーブルの内容を、順に表示しています。(テーブルはMySQLの基本的な操作で作成した address
テーブルです。)
<?php
require_once 'DB.php';
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>サンプル</title>
</head>
<body>
<?php
$dbh = DB::connect('mysql://root:1234@127.0.0.1/phpdb');
if (DB::isError($dbh)) {
exit($dbh->getMessage());
}
$dbh->query('SET NAMES utf8');
if (DB::isError($dbh)) {
exit($dbh->getMessage());
}
$sth = $dbh->query('SELECT * FROM address WHERE no >= 10 AND no <= 20');
if (DB::isError($sth)) {
exit($sth->getMessage());
}
while ($data = $sth->fetchRow(DB_FETCHMODE_ASSOC)) {
echo '<p>' . $data['no'] . ':' . $data['name'] . "</p>\n";
}
$dbh->disconnect();
?>
</body>
</html>
次から、それぞれの処理を詳しく見ていきます。
PHPからMySQLを操作する際には mysql_connect
や mysql_query
といった関数が用意されていましたが、これらはMySQL専用の関数です。PostgreSQLやSQLiteを操作するためには、別途専用の関数を使用します。
ですがPEAR::DBなら、同じ命令で色々なデータベースに接続することができます。
以下はMySQL用の関数との比較です。
処理内容 | MySQL操作関数 | PEAR::DB |
---|---|---|
MySQLに接続&データベース選択 | mysql_connect ~ mysql_select_db |
DB::connect |
SQL実行 | mysql_query ~ mysql_fetch_array |
query ~ fetchRow |
接続を切断 | mysql_close |
disconnect |
PEAR::DBでデータベースに接続するには、DB::connect
メソッドを使用します。接続に成功するとオブジェクトが返されます。
オブジェクト = DB::connect(
'データベースの種類://ユーザー名:パスワード@接続先アドレス/データベース名'
);
MySQLに接続する場合、データベースの種類は mysql
を指定します。もしPostgreSQLに接続したければ pgsql
、SQLiteに接続したければ sqlite
を指定します。他にも、色々な種類のデータベースに接続することができます。
DB::connect
を実行後、実際に接続ができたかどうかチェックします。チェックは「返されたオブジェクトがエラーオブジェクトかどうか?」で判断しています。これは DB::isError
を使用すれば調べることができます。
また、エラーの内容は $dbh->getMessage()
で取得することができます。
PEAR::DBで実際にデータベースにコマンドを送るには query
メソッドを使用します。引数には実行したいコマンドを指定します。
オブジェクト = $dbh->query('実行するSQL文');
最初に文字コードを指定するSQLを実行しています。SET NAMES
に続けて文字コードを指定すると、データベースで扱う文字コードを明示できます。(SET NAMES
を使わなくても文字化けしない場合、この処理は不要です。)
レコードを取得するコマンドを指定した場合、オブジェクトに対して fetchRow
メソッドを使用すれば、レコードを一件ずつ取得する事ができます。引数に DB_FETCHMODE_ASSOC
を指定すると、連想配列の形式で取得します
オブジェクト = $sth->fetchRow(DB_FETCHMODE_ASSOC);
取得したレコードすべてを順に取得して表示する場合、while
と組み合わせて以下のように書きます。
while ($data = $sth->fetchRow(DB_FETCHMODE_ASSOC)) {
echo '<p>' . $data['no'] . ':' . $data['name'] . "</p>\n";
}
これで「データを1件取り出すことができれば」という条件で繰り返し処理を行うため、データが存在する間はずっと echo
が実行されます。
また、DB_FETCHMODE_ASSOC
の代わりに DB_FETCHMODE_ORDERED
を使用すれば、レコードを一件ずつ配列の形式で取得する事ができます。具体的には以下のように使用します。
while ($data = $sth->fetchRow(DB_FETCHMODE_ORDERED)) {
echo '<p>' . $data[0] . ':' . $data[1] . "</p>\n";
}
$data[0]
には先頭の列である no
が、$data[1]
には次の列である name
が格納されますので、実行結果は同じです。
PEAR::DBでデータベースとの接続を切断するには、disconnect
メソッドを使用します。
query
メソッドを実行すると、与えられたSQL文を解析し、その後実行します。ですが検索条件のみ変化するSQL文を何度も実行するような場合、毎回SQL文全体を解析するのは無駄が多いです。
このような場合はプレースホルダを利用すれば、SQL文の解析は最初に一度だけ行い、その後は変化する部分のみを解析&実行することができます。
以下はプレースホルダを使用したサンプルプログラムです。
<?php
require_once 'DB.php';
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>サンプル</title>
</head>
<body>
<?php
$dbh = DB::connect('mysql://root:1234@127.0.0.1/phpdb');
if (DB::isError($dbh)) {
exit($dbh->getMessage());
}
$dbh->query('SET NAMES utf8');
if (DB::isError($dbh)) {
exit($dbh->getMessage());
}
$sth = $dbh->prepare('SELECT * FROM address WHERE no >= ? AND no <= ?');
$sth = $dbh->execute($sth, array(1, 5));
if (DB::isError($sth)) {
exit($sth->getMessage());
}
while ($data = $sth->fetchRow(DB_FETCHMODE_ASSOC)) {
echo '<p>' . $data['no'] . ':' . $data['name'] . "</p>\n";
}
$dbh->disconnect();
?>
</body>
</html>
プレースホルダを利用しているのは以下の部分です。
$sth = $dbh->prepare('SELECT * FROM address WHERE no >= ? AND no <= ?');
プレースホルダを利用する場合、SQL文の指定は query
ではなく prepare
で行います。また、検索条件など変化する部分は ?
と書いておきます。
次に
$sth = $dbh->execute($sth, array(1, 5));
という処理がありますが、このようにすると「最初の ?
には 1
を割り当てる」となり「次の ?
には 5
を割り当てる」という指定になります。つまり今回の場合、SELECT * FROM address WHERE no >= 1 AND no <= 5
を指定したときと同じ結果が得られます。
このように、query
メソッドだけですぐに実行するのではなく、
という段階を踏みます。これがプレースホルダの基本的な使い方です。
前回の例は検索を1度実行するだけだったので、あまりプレースホルダを使用する意味はありません。ですが、似た処理を何度も実行する場合はプレースホルダが有効に働きます。
<?php
require_once 'DB.php';
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>サンプル</title>
</head>
<body>
<?php
$dbh = DB::connect('mysql://root:1234@127.0.0.1/phpdb');
if (DB::isError($dbh)) {
exit($dbh->getMessage());
}
$dbh->query('SET NAMES utf8');
if (DB::isError($dbh)) {
exit($dbh->getMessage());
}
$sth = $dbh->prepare('SELECT * FROM address WHERE no >= ? AND no <= ?');
echo "<p>noが1~5のデータを表示します。</p>\n";
$result = $dbh->execute($sth, array(1, 5));
if (DB::isError($result)) {
exit($sth->getMessage());
}
while ($data = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
echo '<p>' . $data['no'] . ':' . $data['name'] . "</p>\n";
}
echo "<p>noが6~10のデータを表示します。</p>\n";
$result = $dbh->execute($sth, array(6, 10));
if (DB::isError($result)) {
exit($sth->getMessage());
}
while ($data = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
echo '<p>' . $data['no'] . ':' . $data['name'] . "</p>\n";
}
echo "<p>noが11~15のデータを表示します。</p>\n";
$result = $dbh->execute($sth, array(11, 15));
if (DB::isError($result)) {
exit($sth->getMessage());
}
while ($data = $result->fetchRow(DB_FETCHMODE_ASSOC)) {
echo '<p>' . $data['no'] . ':' . $data['name'] . "</p>\n";
}
$dbh->disconnect();
?>
</body>
</html>
少し長いですが、同じような検索処理を3回繰り返しています。
実行するSQLは、以下の一箇所でのみ定義しています。
$sth = $dbh->prepare('SELECT * FROM address WHERE no >= ? AND no <= ?');
その後、execute
で値をセットしつつ実行して結果を表示…という処理を3回行っています。
このように処理すると、データベースがSQLを一から解釈する必要がなくなるため、実行速度を早くすることができます。今回は検索処理を3回実行するだけなので大差はありませんが、「データベースに10万件のデータを一括登録する」のような処理の場合、実行速度に差が現れます。
以上のように、プレースホルダはSQLを効率よく実行するための仕組みなのですが、プログラムに対する攻撃の防止にもなります。例えば
$sth = $dbh->query('DELETE FROM address WHERE no = ' . $_POST['no']);
このようなプログラムで、ユーザーに指定された番号でデータを削除するとします。($_POST['no']
は、フォームから入力した値が格納されます。)もしユーザーが 10
を指定した場合、
DELETE FROM address WHERE no = 10
が実行され、番号が10のデータのみ削除されます。ですがもしユーザーが 1 OR 1 = 1
という値を指定した場合、
DELETE FROM address WHERE no = 1 OR 1 = 1
が実行され、address
テーブル内のデータがすべて削除されてしまいます。また、
$sth = $dbh->query('DELETE FROM address WHERE name = "' . $_POST['name'] . '"');
このようなプログラムで、ユーザーに指定された名前でデータを削除するとします。($_POST['name']
は、フォームから入力した値が格納されます。)もしユーザーが 山田太郎
を指定した場合、
DELETE FROM address WHERE name = "山田太郎"
が実行され、名前が山田太郎のデータのみ削除されます。ですがもしユーザーが 山田太郎" OR "1" = "1
という値を指定した場合、
DELETE FROM address WHERE name = "山田太郎" OR "1" = "1"
が実行され、やはり address
テーブル内のデータがすべて削除されてしまいます。
これを防ぐには addslashes
で '
や "
をエスケープしたり、intval
関数で強制的に数値に変換すれば防ぐことができます。ですが沢山のデータベース操作処理を書いていると、どこかで書き忘れのミスが発生する可能性があります。
この対策に、プレースホルダを使用していれば、
$sth = $dbh->prepare('SELECT * FROM address WHERE no >= ? AND no <= ?');
$sth = $dbh->execute($sth, array(1, 5));
と、何も考えずに指定するだけで対策を行ってくれます。テーブルの設定から文字列を入力するべきか数値を入力するべきかを自動判別し、適切なエスケープ処理を自動で行います。
以上の理由から、ユーザーからの入力をもとにSQL文を作成する場合、常にプレースホルダを利用した方が良いでしょう。