Do You PHP?    
Search Engine Optimization  php5 powerd  Valid XHTML 1.0!  Valid CSS!  このサイトのはてなブックマーク数 



last updated
2005/12/15

counter hits
since 1999/11/06


PDO(PHP Data Objects)を試してみる - PDO_OCIは今どんな感じ?

cautionPDO1.0以降、定数が「PDO_xxx」から「PDO::xxx」に変更になっていますので、それ以前のバージョンを使用していた方は注意が必要です。

memo2005/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.ini
extension=pdo.so
extension=pdo_oci.so

Apacheを再起動し、phpinfoを表示させると以下のような項目が追加されているはずです。

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
/**
 * DBへの接続
 */
function getConnection()
{
    $db = null;
    /**
     * Oracle Instant Clientの場合の例
     *
     * $dsn = 'oci:dbname=dbserver:1521/orcl';
     */
//    $dsn = 'oci:dbname=orcl';
    $dsn = 'oci:orcl';
    $user = 'scott';
    $password = 'tiger';
    try {
        $db = new PDO($dsn, $user, $password);
        // カラム名を小文字で取得する
        $db->setAttribute(PDO_ATTR_CASE, PDO_CASE_LOWER);
        // 自動コミットをOff
        $db->setAttribute(PDO_ATTR_AUTOCOMMIT, 0);
        // エラー時にExceptionをthrowさせる
        $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 ');

    /**
     * bindParamにリテラルを記述すると、
     * 「Fatal error: Cannot pass parameter 2 by reference」
     * となる
     */
    $param = '%';
    try {
        /**
         * 以下のようにプレースホルダ名の代わりに順序を指定してもOK
         *
         * $stmt->bindParam(1, $param, PDO_PARAM_STR, 10);
         */
//      $stmt->bindParam(1, $param);
        $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>';
    }
    /**
     * 作用した件数を取得する
     * ただし、SELECT文では必ず0が返る。。。
     */
    echo '取得した行数=' . $stmt->rowCount() . '<br>';

    /**
     * カラム数を取得する
     * SELECT文以外の場合は0が返される
     */
    echo 'カラムの数=' . $stmt->columnCount() . '<br>';

    /**
     * カラムのメタデータを取得する
     *
     * PDO_OCIドライバではサポートされていないため、PDOExceptionが
     * throwされる。catchしない場合はFatal Errorとなる
     */
    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 (?, ?) ');
    /**
     * bindParamにリテラルを記述すると、
     * 「Fatal error: Cannot pass parameter 2 by reference」
     * となる
     */
    $no = 1234;
    $name = 'テストさん';
    /**
     * プレースホルダ名の代わりに順序を指定してもOK
     */
    $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>";
    }
    /**
     * 最後にINSERTされたデータのIDを取得する
     *
     * PDO_OCIドライバではサポートされていないため、PDOExceptionが
     * throwされる。catchしない場合はFatal Errorとなる
     */
    try {
        echo $db->lastInsertId();
    }
    catch (Exception $e) {
        echo "PDOException : " . $e->getMessage() . "<br>";
    }
    // ステートメントオブジェクトの開放
    $stmt = null;
    echo '<hr>';
    // 追加後のデータを表示
    doShowAll($db);
}
?>

ここでバインドパラメータの書き方について少々。。。バインドパラメータの書き方はPHPマニュアルのPDOStatement::bindParamPDOStatement::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.sql
CREATE 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)
{
    // やっていることは、Oracle関数・OCI8関数とあまり変わらない
    $stmt = $db->prepare('DECLARE BEGIN p_test(:id, :name); END; ');
    /**
     * bindParamにリテラルを記述すると、
     * 「Fatal error: Cannot pass parameter 2 by reference」
     * となる
     */
    $id = 1;
    $nm = 'ほげほげ';
    try {
        /**
         * プレースホルダ名の代わりに順序を指定してもOK
         */
        $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からも標準で付随するようになっていますので大いに期待しましょう。



About This Site |  Privacy Policy |  Contact
Copyright © 1999 - 2005 by Hideyuki SHIMOOKA all rights reserved.