PDO(PHP Data Objects)を試してみる - PDO_OCIは今どんな感じ?
PDO1.0以降、定数が「PDO_xxx」から「PDO::xxx」に変更になっていますので、それ以前のバージョンを使用していた方は注意が必要です。
2005/06/10付けでPHP5.1.0β1がリリースされましたが、標準の拡張モジュールとしてPDOが含まれており、configure時に「--with-pdo-oci」を付けてbuildするようになります。
PHPでDBを扱う際、最も一般的な方法としてOCI8やpgsql、mysqlなどの拡張モジュールを使用する方法が上げられますが、DBによってAPIが異なってしまいます。これを抽象化するため、データ抽象化レイヤ(Data Abstraction Layer)に相当する各種ライブラリが多くで回っています。現時点で出回っているライブラリとしては、おそらくデファクトスタンダードであると思われるPEAR::DBや拡張モジュールのdbx、最近流行(?)のO/Rマッピングを行うPEAR::DB_DataObjectなど色々ありますが、今回はPHP5から導入されたPDO(PHP Data Objects)を試してみました。
PDOはREADMEファイルにあるような以下の4つのコンセプトが挙げられています。
- ライトウェイト
- 標準的なDB操作のための共通APIを提供
- 良いパフォーマンス
- PDOコアモジュールとドライバモジュールの分離
コンセプトの4番目にもあるとおり、コアモジュールとドライバが別々になっていますのでPDOのみをインストールしても使えません。対応しているDBのドライバを別途インストールする必要があります。なんだかJDBC APIと各DB用ドライバの関係のようですね。対応しているDBについては、PHPマニュアルの「PDO Drivers」を参照してください。
現時点でPDOに関する情報はまだまだ少ないですが、以下のサイト・書籍が参考になります。
インストール手順
まずはインストールした環境ですが、PHPは以下のようなconfigureオプションを付けたPHP5.0.4です。
●PHP5.0.4のconfigureオプション
--with-apxs2=/usr/local/apache2/bin/apxs \
--prefix=/usr/local/lib/php5 \
--with-pear=/usr/local/lib/php5/pear \
--with-config-file-path=/usr/local/lib/php5/ini/5.0.4 \
--with-config-file-scan-dir=/usr/local/lib/php5/ini.d \
--enable-zend-multibyte \
--enable-mbstring \
--enable-mbregex \
--with-dom \
--with-gd=shared \
--with-jpeg-dir \
--with-png-dir \
--with-zlib-dir \
--with-ttf \
--with-freetype-dir \
--enable-gd-jis-conv \
--with-java=shared,/usr/local/jdk \
--with-xsl \
--with-oci8=/u01/app/oracle/product/10.1.0 \
--without-mysql \
--enable-soap
「--prefix」オプションを付けているため、phpコマンドのパスがデフォルトとは変わっていますが適宜読み替えてください。
さて実際のインストールですが、2005/08/01現在の最新バージョン0.9であればPHPマニュアルにあるようなpearコマンドでのインストールが簡単です。まずPDO本体をインストールします
●pearコマンドでのPDOインストール# /usr/local/lib/php5/bin/pear config-set preferred_state beta
# /usr/local/lib/php5/bin/pear install pdo
:
#
続けてPDO_OCIをインストールする。。。と行きたいところですが、PDO_OCIのインストール前にphp.iniのextensionディレクティブにpdo.soを追加しておく必要があります。さもないと以下のようになります。
●pdo.soを追加しないままでのPD_OCIOインストール# /usr/local/lib/php5/bin/pear install pdo_oci
downloading PDO_OCI-0.9.tgz ...
Starting to download PDO_OCI-0.9.tgz (11,914 bytes)
.....done: 11,914 bytes
'pdo' PHP extension is not installed
PDO_OCI: Dependencies failed
#
また、php.iniに
extension = pdo.so
を追加した後、環境変数$ORACLE_HOMEを設定しておく必要があります。手動インストールの場合はconfigureで指定できるのですが、pearコマンドでインストールする場合は指定できないためです。この場合、以下のようになります。
●pdo.soを追加しないままでのPD_OCIOインストール# /usr/local/lib/php5/bin/pear install pdo_oci
:
checking Oracle OCI support for PDO... yes, shared
checking Oracle Install-Dir... :yes:
checking if that is sane... configure: error:
You need to tell me where to find your oracle SDK, or set ORACLE_HOME.
#
php.iniの設定、環境変数$ORACLE_HOMEの設定を行ったら、PDO_OCIをインストールします。
●pearコマンドでのPDO_OCIインストール# export ORACLE_HOME=/path/to/oracle/home
# /usr/local/lib/php5/bin/pear install pdo_oci
#
インストールが終わったら、php.iniにextensionの記述を追加します。
●php.iniextension=pdo.so
extension=pdo_oci.so
Apacheを再起動し、phpinfoを表示させると以下のような項目が追加されているはずです。
ちなみに、手動でのインストールの場合は、PECLのページからPDO、PDO_OCIの各アーカイブをダウンロードし、以下の手順でインストールします。
●手動でのPDO/PDO_OCIインストール例# tar zxf PDO-0.x.x.tgz
# cd PDO-0.x.x/
# /usr/local/lib/php5/bin/phpize
# ./configure --enable-pdo \
--with-php-config=/usr/local/lib/php5/bin/
# make
# make install
# cd ../
# tar zxf PDO_OCI-0.x.tgz
# cd PDO_OCI-0.x/
# /usr/local/lib/php5/bin/phpize
# ./configure --with-pdo-oci=/export/u01/app/oracle/product/9.2.0 \
--with-php-config=/usr/local/lib/php5/bin/php-config
# make
# make install
#
また、PDO_OCI0.2からOracle Instant Clientがサポートされています。Oracle Instant ClientについてはPHPインストールのページにも若干情報があります。Oracle Instant Clientを使ったPDO_OCIのインストール手順は以下のようになります。
●Oracle Instant Client+PDO_OCIインストール# /usr/local/lib/php5/bin/phpize
# ./configure --with-pdo-oci=instantclient,/usr,10.1.0.3 \
--with-php-config=/usr/local/lib/php5/bin/php-config
# make
# make install
#
簡単なサンプル
さて、実際のコードですがPDOはPHP5から導入されたということもあって当然オブジェクト指向色が強いです。JDBCを使ったことがある方ならかなり分かりやすいのではないでしょうか。中心となるクラスはPHPマニュアルにもあるように、PDOクラスとPDOStatementクラスになります。それぞれの詳細なドキュメントはrakutoネットさんが分かりやすいと思います。
以下のサンプルでは基本的な流れを見ていただくため、かなりベタベタに書いてあります(^-^;
まずはDB接続の例です。DSN(PDOのコンストラクタの第一引数)としては「oci:[接続識別子]」あるいは「oci:dbname=[接続識別子]」を指定します。以下の例では、PDO_setAttributeメソッドを使って3つほどオプションを指定しています。3つ目でPDO_ATTR_ERRMODEとしてPDO_ERRMODE_EXCEPTIONを指定しているため、これ以降のPDOエラーは全て例外がthrowされるようになります。
●DB接続の例
<?php
function getConnection()
{
$db = null;
$dsn = 'oci:orcl';
$user = 'scott';
$password = 'tiger';
try {
$db = new PDO($dsn, $user, $password);
$db->setAttribute(PDO_ATTR_CASE, PDO_CASE_LOWER);
$db->setAttribute(PDO_ATTR_AUTOCOMMIT, 0);
$db->setAttribute(PDO_ATTR_ERRMODE, PDO_ERRMODE_EXCEPTION);
}
catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
echo ' error code: ' . $e->getCode();
exit;
}
return $db;
}
?>
続いてSELECT文の例です。ここではパラメータをPDO_bindParamメソッドを使って指定しています。バインドするパラメータは一度変数に置く必要があります。また、PDOStatement::rowCount、PDOStatement::columnCount、PDOStatement::getColumnMetaの各メソッドはまだ実装されていないようです。また、PDOStatement::fetchに指定するパラメータによって取得する結果を配列・連想配列・オブジェクトなどに変更することができます。
●SELECT文の例
<?php
function doShowAll($db)
{
$stmt = $db->prepare('SELECT * FROM emp WHERE ename LIKE :name ');
$param = '%';
try {
$stmt->bindParam(':name', $param);
$stmt->execute();
}
catch (PDOException $e) {
echo 'データを取得できませんでした<br>';
echo 'execute failed: ' . $e->getMessage() . '<br>';
echo ' error code: ' . $e->getCode() . '<br>';
echo ' ORA error info: ' . $e->errorInfo[1] . '<br>';
}
echo '取得した行数=' . $stmt->rowCount() . '<br>';
echo 'カラムの数=' . $stmt->columnCount() . '<br>';
try {
var_dump($stmt->getColumnMeta(0));
}
catch (PDOException $e) {
echo 'getColumnMeta failed: ' . $e->getMessage();
echo ' error code: ' . $e->getCode();
}
$cnt = 1;
$row = array();
echo '<table border="1">';
while ($row = $stmt->fetch(PDO_FETCH_ASSOC)) {
echo '<tr>';
echo '<th>' . $cnt++ . '</th>';
foreach ($row as $column_name => $column_value) {
echo '<td>' . $column_value . '</td>';
}
echo '</tr>';
}
echo '</table>';
echo '<hr>';
$stmt = null;
}
?>
次はINSERT文の例です。Oracle関数やOCI8関数と異なるのは、トランザクション開始前にPDO_beginTransactionメソッドをコールする必要があることです。また、PDO_lastInsertIDメソッドはサポートされていないようです。UPDATE文・DELETE文についても基本的には同じ書き方になります。
●INSERT文の例
<?php
function doInsert($db)
{
$stmt = $db->prepare('INSERT INTO emp (empno, ename) VALUES (?, ?) ');
$no = 1234;
$name = 'テストさん';
$stmt->bindParam(1, $no);
$stmt->bindParam(2, $name, PDO_PARAM_STR, 10);
try {
$db->beginTransaction();
$stmt->execute();
$db->commit();
echo 'データを追加しました';
}
catch (PDOException $e) {
echo 'データを追加できませんでした';
echo 'transaction failed: ' . $e->getMessage();
echo ' error code: ' . $e->getCode();
echo ' ORA error info: ' . $e->errorInfo[1];
$db->rollback();
}
try {
echo $stmt->rowCount() . '件のデータを登録しました<br>';
}
catch (Exception $e) {
echo "PDOException : " . $e->getMessage() . "<br>";
}
try {
echo $db->lastInsertId();
}
catch (Exception $e) {
echo "PDOException : " . $e->getMessage() . "<br>";
}
$stmt = null;
echo '<hr>';
doShowAll($db);
}
?>
ここでバインドパラメータの書き方について少々。。。バインドパラメータの書き方はPHPマニュアルのPDOStatement::bindParamやPDOStatement::executeにもあるように2通りあります。以下の1番目の書き方でパラメータが複数ある場合、順序を入れ替えても動作します。2番目の書き方はJDBCと同じですね。この場合、順序(何番目か)を入れ替える事はできません。
●PDOStatement::bindParamの書き方(1)
<?php
:
$stmt = $db->prepare('SELECT * FROM pdo_test WHERE ename like :name ');
$param = 'ほげほげ';
try {
$stmt->bindParam(':name', $param);
$stmt->execute();
}
catch (PDOException $e) {
:
}
:
?>
●PDOStatement::bindParamの書き方(2)
<?php
:
$stmt = $db->prepare('SELECT * FROM pdo_test WHERE ename like ? ');
$param = 'ほげほげ';
$stmt->bindParam(1, $param);
try {
$stmt->execute();
}
catch (PDOException $e) {
:
}
:
?>
また、ここにはサンプルとして掲載していませんがDML文の実行や、以下のようなプロシージャ実行も可能です。
●p_test.sqlCREATE OR REPLACE PROCEDURE p_test (
no IN INTEGER,
nm IN VARCHAR2
)
IS
BEGIN
INSERT INTO emp (empno, ename)
VALUES (no, nm);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
END;
/
SHOW ERROR
●プロシージャの呼び出し例
<?php
function doCallProcedure($db)
{
$stmt = $db->prepare('DECLARE BEGIN p_test(:id, :name); END; ');
$id = 1;
$nm = 'ほげほげ';
try {
$stmt->bindParam(':name', $nm);
$stmt->bindParam(':id', $id);
$stmt->execute();
}
catch (PDOException $e) {
echo 'プロシージャを呼び出せませんでした';
echo 'execute failed: ' . $e->getMessage();
echo ' error code: ' . $e->getCode();
echo ' ORA error info: ' . $e->errorInfo[1];
}
$stmt = null;
}
?>
まとめ
リリース間近のバージョン0.9になって、細かい部分がだいぶFIXされてきた感じがします。また、パフォーマンス面でもまるごとPHP!〈Vol.1〉によるとPEAR:DBを遙かに凌いでおり、ほぼネイティブドライバと変わらないようです。また、PHP5.1からも標準で付随するようになっていますので大いに期待しましょう。
|