[SQLite][PHP] PHPのPEAR::DBはSQLite3に未対応

対象

この記事で対象となっているOSはUbuntu 6.10, PHPは5.1.6です。

問題点

PHPからSQLite3を使おうとして、

<?php
$dsn = 'sqlite:///パス';
$db =& DB::connect($dsn);
?>

としたのですが、Extention not Foundとなり、接続できません。

原因

PEAR::DBは、SQLite 3.xには対応していません。

対処方法

PDOを使用して下さい。

原因の特定方法

このエラーの原因を探るため、/usr/share/php/DB.phpのconnectメソッドの中身を見てみました。

    function &connect($dsn, $options = array())
    {
        $dsninfo = DB::parseDSN($dsn);
        $type = $dsninfo['phptype'];

        if (!is_array($options)) {
            /*
             * For backwards compatibility.  $options used to be boolean,
             * indicating whether the connection should be persistent.
             */
            $options = array('persistent' => $options);
        }

        if (isset($options['debug']) && $options['debug'] >= 2) {
            // expose php errors with sufficient debug level
            include_once "DB/${type}.php";
        } else {
            @include_once "DB/${type}.php";
        }

        $classname = "DB_${type}";
        if (!class_exists($classname)) {
            $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
                                    "Unable to include the DB/{$type}.php"
                                    . " file for '$dsn'",
                                    'DB_Error', true);
            return $tmp;
        }

        @$obj =& new $classname;

        foreach ($options as $option => $value) {
            $test = $obj->setOption($option, $value);
            if (DB::isError($test)) {
                return $test;
            }
        }

        $err = $obj->connect($dsninfo, $obj->getOption('persistent'));

connectメソッドの中では、まず

        $dsninfo = DB::parseDSN($dsn);

があり、おそらく接続情報の文字列をパースし、その結果を$dsninfoに格納しているのだろうと推測されます。そして、$dsninfoは、

        $type = $dsninfo['phptype'];

と使われているので、連想配列になっているはずです。この$dsninfo['phptype']というのは、connectメソッドに渡した文字列の先頭にあるデータベースの種別ではないかとあたりをつけることができます。すなわち、上の例の場合だと、$typeは"sqlite"になります。で、その後で、

            @include_once "DB/${type}.php";

がくるので、/usr/share/php/DB/sqlite.phpをインクルードします。確認しましたが、このファイルは存在しました。次に、

        $classname = "DB_${type}";
(中略)
        @$obj =& new $classname;

があります。$typeは先ほどのように"sqlite"だと思われるので、$classnameは"DB_sqlite"となり、$objはDB_sqliteインスタンス、ということになります。で、このインスタンスを、

        $err = $obj->connect($dsninfo, $obj->getOption('persistent'));

のようにして使って、データベースに接続しています。

そこで、次に/usr/share/php/DB/sqlite.phpのDB_sqliteクラスのconnectメソッドをみてみます。

    function connect($dsn, $persistent = false)
    {
        if (!PEAR::loadExtension('sqlite')) {
            return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
        }

先頭でPEAR::loadExtensionなるメソッドを呼び出し、それが失敗したらDB_ERROR_EXTENSION_NOT_FOUNDというエラーにしろ、と読めます。どうやら、件のエラーはここで発生しているようです。

ではPEAR::loadExtensionを調べるため、/usr/share/php/PEAR.phpをみます(はてなの文法を回避するため、一部に余計な空白が追加されています)。

    function loadExtension($ext)
    {
        if (!extension_loaded($ext)) {
            // if either returns true dl() will produce a FATAL error, stop that
            if ( (ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
                return false;
            }
            if (OS_WINDOWS) {
                $suffix = '.dll';
            } elseif (PHP_OS == 'HP-UX') {
                $suffix = '.sl';
            } elseif (PHP_OS == 'AIX') {
                $suffix = '.a';
            } elseif (PHP_OS == 'OSX') {
                $suffix = '.bundle';
            } else {
                $suffix = '.so';
            }
            return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
        }
        return true;
    }

先頭の

        if (!extension_loaded($ext)) {

は、$ext(この場合'sqlite')がロードされていなかったら以下を実行する、というように読めます。で、いまは初めてロードする場合なので、このif文の内部を見てみます。まず始めに、

            if ( (ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
                return false;
            }

があります(はてなの文法を回避するため、一部に余計な空白が追加されています)。ini_getメソッドというのは、名前から推察するにphp.iniの指定された値を読み取るメソッドと思われます。で、enable_dlというのが1ではない場合、またはsafe_modeが1である場合にエラーとなります。ここでエラーになっているのでしょうか? 調べてみました。

$ grep 'enable_dl\|safe_mode' /etc/php5/apache2/php.ini
safe_mode = Off
; then turn on safe_mode_gid.
safe_mode_gid = Off
; When safe_mode is on, UID/GID checks are bypassed when
safe_mode_include_dir =
; When safe_mode is on, only executables located in the safe_mode_exec_dir
safe_mode_exec_dir =
safe_mode_allowed_env_vars = PHP_
; protected even if safe_mode_allowed_env_vars is set to allow to change them.
safe_mode_protected_env_vars = LD_LIBRARY_PATH
enable_dl = On
sql.safe_mode = Off

safe_modeは"Off", enable_dlは"On"になっています。確証はありませんが、常識的に考えて"Off"は0, "On"は1になりそうです。ならば、上のif文ではエラーになりません。では次を見てみます。

            if (OS_WINDOWS) {
                $suffix = '.dll';
            } elseif (PHP_OS == 'HP-UX') {
                $suffix = '.sl';
            } elseif (PHP_OS == 'AIX') {
                $suffix = '.a';
            } elseif (PHP_OS == 'OSX') {
                $suffix = '.bundle';
            } else {
                $suffix = '.so';
            }
            return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);

OSの種類で処理をわけています。使用しているOSはUbuntu Linuxなので、そのとき実行される文を抜き出してみると、

                $suffix = '.so';
            return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);

と、なると思います。確信はありませんが、最後に呼び出しているdlというメソッドが拡張モジュールをロードする本体ではないでしょうか。このdlメソッドに渡したモジュールが読み込まれるに違いありません。さらに、このdlメソッドは2回呼び出され(る可能性があり)、最初の読み込みに失敗したら、次のを読み込んで、それでもダメならエラーになりそうです。では、なにを読みこませようとしているのでしょうか? 変数を展開すると、最初に渡しているのは、'php_sqlite.so'です。調べてみましたが、こんなファイルはないようです。で次に渡しているのは、'sqlite.so'です。存在するかどうか調べてみたら、sqlite.soはありませんでしたが、/usr/lib/php5/20051025/sqlite3.soというのがありました。dpkgによると、

$ dpkg --search /usr/lib/php5/20051025/sqlite3.so 
php5-sqlite3: /usr/lib/php5/20051025/sqlite3.so

となり、このファイルはphp5-sqlite3パッケージをインストールしたときに入ったもののようです。確かに、私はPHPからSQLite3を使うため、このパッケージをインストールしました。

しかし困りました。じゃあこのsqlite3.soを読み込ませるために、DB::connectメソッドに渡すDSNを、"sqlite3:///..."にしたらいいのでしょうか? しかし、それだとPEAR::DBのconnectメソッドの、

        $dsninfo = DB::parseDSN($dsn);
        $type = $dsninfo['phptype'];
(中略)
            @include_once "DB/${type}.php";

というところで、DB/sqlite3.phpをインクルードするようになってしまいます。/usr/share/php/DB/sqlite3.phpというファイルはありません。

ここで検索エンジンで適当なキーワードで探してみたところ、http://php-sqlite3.sourceforge.net/pmwiki/pmwiki.phpに着きました。ここでDB/sqlite3.phpを配布しているようです。しかし、以下のような記述があります。

php-sqlite3 is a PHP extension that lets you access SQLite3 databases (see : http://www.sqlite3.org) within your scripts.

PHP 4 and PHP 5 have already built-in support for this RDBM, but this is limited to the 2.x releases. This extension adds support for SQLite 3.x release.

Please note that this project is still alpha-quality. Please test and report if it works for you and how it can be enhanced.

すなわち、

php-sqlite3は、スクリプトからSQLite3データベース(http://www.sqlite3.orgをみよ)にアクセスするためのPHPの拡張です。

PHP 4とPHP 5はすでに、RDBMに対応していますが、これは2.xに限定されています。この拡張は、SQLite 3.xへの対応を追加します。

ただし、このプロジェクトは依然アルファ品質です。これが動いたかどうか、そしてどのように変えればいいか、テストして教えてください。

です。つまり、PHPPEAR::DBはSQLite3にアクセスできないようです。

私はSQLite3をあきらめ、2.xを使うことにしました。