PEAR MDB2を使ってみよう
PEAR MDB2を試しましょう。MDB2はMySQL以外のデータベースにも対応しています。標準的なSQLだけを使うようにしておけば、簡単にデータベースを切り替えられます(たとえば、以下の例で最初に実行するTRUNCATE文は、DB2では動かないので避けるべきです。本稿では使いませんが、結果の数を制限するLIMIT句もMySQL独自のものなのでなるべく使わない方がいいでしょう)。
コードはUTF-8で保存して実行してください。なお、ファイル名を「MDB2.php」などとしないように注意してください。ライブラリのファイル名と同じ名前を付けてしまうと、ライブラリが読み込まれません。
接続
次のようにMySQLに接続します。MDB2::connect
の引数は、「'mysqli://ユーザ名:パスワード@ホスト/データベース名?charset=文字コード'
」です(接続に失敗するとエラーが表示されます)。
require_once 'MDB2.php';//ライブラリのロード $db=MDB2::connect('mysqli://test:pass@localhost/mydb?charset=utf8'); if(PEAR::isError($db)) die("<p>{$db->getMessage()}</p>");//エラー処理
少し細かい話ですが、ここではconnect
として接続しましたが、他にfactory
やsingleton
として接続することもできます。connect
とすると、データベースにすぐに接続します。factory
とすると、接続のためのオブジェクトは生成しますが、実際にデータベースに接続するのは、それが本当に必要になった時です。singleton
としたときも、最初の接続のタイミングはfactory
の場合と同じです。factory
の場合と違うのは、このメソッドをもう一度呼んだときの動作です。factory
としたときは、接続のためのオブジェクトがもう一つ生成されます(connect
の場合も同様)。それに対してsingleton
としたときは、最初に生成されたものと同じオブジェクトが返されます(デザインパターンでのsingleton
パターン)。
エラーハンドラ
いちいちエラーをチェックするのは面倒なので、エラーハンドラ(エラーが発生したときに実行される関数)を用意しておきましょう。
//エラーが発生したときに実行される関数 function errorHandler($error){ echo "<p>エラー発生 {$error->getMessage()}</p>"; } //errorHandlerを使うように設定 PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'errorHandler');
setErrorHandlingはPEARのパッケージによって生成されるエラーの処理方法を指定するメソッドです。このメソッドには、処理モードとそれに応じたオプションを引数として与えます。ここでは処理モードをPEAR_ERROR_CALLBACK
をしているので、エラーが発生するとオプションで指定した関数が呼び出されます(詳細はマニュアルのPEAR::setErrorHandling()の項を参照)。
SQL文の実行
結果の返らないクエリ
データベースへのクエリは、SELECTのように結果の返るものと、INSERTやUPDATE、DELETEのように結果の返らないものに分けることができます。結果の返らないクエリはexec
メソッドで実行します。
$db->exec('TRUNCATE TABLE books'); $db->exec("INSERT INTO books VALUES (1,'The Art of Computer Programming 1', 'アスキー',2004,10290)"); $db->exec("INSERT INTO books VALUES (2,'フリーソフトウェアと自由な社会', 'アスキー',2003,3360)"); $db->exec("INSERT INTO books VALUES (3,'計算機プログラムの構造と解釈', 'ピアソンエデュケーション',2000,4830)"); $db->exec("INSERT INTO books VALUES (4,'プログラミング作法','アスキー', 2000,2940)"); $db->exec("INSERT INTO books (id,title,publisher,year,price) VALUES (5,'Webアプリケーション構築入門','森北出版',2007,3360)");
内容が実行中に決まる場合
次のような新しいデータを挿入することを考えましょう。
$title='プログラミングPHP'; $publisher="O'Reilly";
次のように実行したいと思うかもしれませんが、これは失敗する悪い例なので、まねしないでください。
//悪い例 $sql="INSERT INTO books (title,publisher) VALUES ('{$title}','{$publisher}')"; echo "<p>$sql</p>"; //INSERT INTO books (title,publisher) VALUES ('プログラミングPHP','O'Reilly') $db->exec($sql);
表示されるINSERT文を見ると、「O'Reilly」の「'」のために、文法的に間違ったものになっていることがわかります。
プリペアードステートメント
変数を使ってSQL文を作るときは、変数の部分をプレースホルダとしてあらかじめ決めておいて、あとで変数の内容を埋め込みます。この仕掛けを「プリペアードステートメント」と呼びます(ちなみに、プリペアードステートメントには、同じSQL文を効率よく複数回実行するという利点もあるはずなのですが、筆者の環境ではかえって遅くなりました)。
$stmt=$db->prepare('INSERT INTO books (title,publisher) VALUES (?,?)'); $stmt->execute(array($title,$publisher));
まず、prepare
メソッドで実行したいSQL文のひな形を作ります。「?
」というのは、あとで変数を埋め込むという意味です。次にexecute
メソッドで実行します。その際に、「?
」の部分に埋め込むデータを配列で与えます。すでにひな形ができているので、「O'Reilly」のようなデータでもエラーにはなりません。
ちなみにこの例ではidを省略していますが、この列はAUTO_INCREMENTと指定してあるので、MySQLによって自動的にidが生成されます。生成されたidは次のように確認することができます。
echo "<p>挿入されたデータのidは{$db->lastInsertId('books','id')}.</p>";//6
空文字列の挿入
次のように空文字列を挿入しようとするとエラーになります。
$stmt=$db->prepare('INSERT INTO books (title,publisher,year,price) '. 'VALUES (?,?,?,?)'); $stmt->execute(array('',"O'%Reilly",2008,1995)); //MDB2 Error: null value violates not-null constraint
空文字列はNULLに変換してからデータベースに格納されます。列titleは「NOT NULL」に設定してあるため、その際にエラーが発生するのです。
この空文字列のNULLへの変換は、Oracleの仕様にあわせたものなのですが、勝手にこんなことをされては困るので、この自動変換はオフにしておきましょう。可搬性のためのすべての機能(MDB2_PORTABILITY_ALL)のうち、自動変換(MDB2_PORTABILITY_EMPTY_TO_NULL)だけをオフにします(ちなみに、単にMDB2_PORTABILITY_NONEとすると、すべての機能が無効になります)。
可搬性のための設定には、ここで確認したMDB2_PORTABILITY_EMPTY_TO_NULLの他に、データの右側にある空白を取り除くMDB2_PORTABILITY_RTRIMやテーブル名やフィールド名を大文字あるいは小文字に変換するMDB2_PORTABILITY_FIX_CASEなどがあります。詳細はマニュアルの可搬性を参照してください。
これで先ほどのINSERT文が実行できるようになります。
$db->setOption('portability',
MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL);
クォート
「O'Reilly」というデータをデータベースに登録しようとしてエラーになる問題を、プレースホルダを用いて解決してきました。
手でSQLを書く場合には、O'Reilly
ではなくO\'Reilly
と書けばよいのですが、これを実現するのがquote
メソッドです。このメソッドで処理すれば、基本的にはどのような文字列でもSQL文に挿入可能になります(使えない場面は後述)。
$title='初めてのPHP'; $publisher="O'Reilly"; $sql="INSERT INTO books (title,publisher) VALUES ". "({$db->quote($title)},{$db->quote($publisher)})"; echo "<p>$sql</p>"; //INSERT INTO books (title,publisher) VALUES ('初めてのPHP','O\'Reilly') $db->exec($sql);
しかし、この方法はお勧めできません。quote
メソッドとプリペアードステートメントを一緒に使うと、余計な「'」が入ってしまうのです。
$stmt=$db->prepare('INSERT INTO books (title,publisher) VALUES (?,?)'); $stmt->execute(array($db->quote($title),$db->quote($publisher))); //「'初めてのPHP'」と「'O\'Reilly'」が入る
- SQLを文字列で作って実行するという直観的にわかりやすい方法が入門書等で紹介される。
- 初心者がそれをそのまま真似る。
- SQLインジェクション攻撃に対して脆弱なウェブアプリが量産される。
- PHPの開発者が「Magic quotes」というマジックを導入する。Magic quotesによって、ブラウザから送られた「O'Reilly」という文字列は、自動的に「O\'Reilly」に変換されようになる(脆弱性回避)。
- この「O\'Reilly」という文字列は、プリペアードステートメントでは使えない(余計なバックスラッシュが入ってしまう)。
- プリペアードステートメントは使われなくなる。
magic_quotes_gpc = Off
」としておきましょう。