てきとうなさいと べぇたばん

PDO::__constructで取得したインスタンスから、$dsnを取得できるか

PDO::__constructで:memory;部分を抜き出すソース。

PDO

php-src上の実装では、ext/pdo/pdo_dbh.c 840行目。 (commit id: e2ef4e9e2fa8cdb749ce4c2afdd3499765ffb721)

static PHP_METHOD(PDO, getAttribute)

PDOは、データベースごとに実装が違うため、それを抽象化するという概念。だから、データベースごとにpdoウンタラカンタラとある。

例えばいかのような感じ。

  • pdo_mysql
  • pdo_pgsql
  • pdo_sqlite

DSNを取得できるか

DSNを取得できるだろうか。まずは、DSNを変数として確保しているかどうか。コンストラクタの実装は 195行目。

static PHP_METHOD(PDO, dbh_constructor)

211行目で、データソース(dsn)を取得している。

ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 4)
    Z_PARAM_STRING(data_source, data_source_len)

なんやかんやあって接続。356行目

if (driver->db_handle_factory(dbh, options)) {

db_handle_factoryは、ext/pdo/php_pdo_driver.hの242行目。pdo_driver_tという構造体。

int (*db_handle_factory)(pdo_dbh_t *dbh, zval *driver_options);

SQLiteの場合はどうか。 ext/pdo_sqlite/php_pdo_sqliteint.hの69行目にある

extern const pdo_driver_t pdo_sqlite_driver;

pdo_sqlite_driver構造体は、ext/pdo_sqlite/sqlitedriver.cの846行目

const pdo_driver_t pdo_sqlite_driver = {
    PDO_DRIVER_HEADER(sqlite),
    pdo_sqlite_handle_factory
};

pdo_sqlite_handle_factoryは788行目にある。

static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options)

MySQLの場合、ext/pdo_mysql/mysqldriver.c。560行目。

static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options)

接続するのはここ。821行目。

if (mysqlnd_connect(H->server, host, dbh->username, dbh->password, password_len, dbn

パスワードを引数に取らないと認証できないから、dbhにはpasswordというメンバがある。

抜き出すように改造してみる

興味が湧いたので、dbhという変数から、dsnを取得してみるように改造しよう。なんとなく改造して楽しむだけなので、品質は保証しない。プロダクションに入れるべきではない。

  • バージョンは、PHP 7.3系
  • 手間の関係で、SQLiteだけにする
  • 定数PDO::ATTR_CALL_DSNを新たに作成する
  • PDO::getAttributeにPDO::ATTR_CALL_DSNを受け取れるようにして、返り値をDSNにする

これで取得できるはず。

patch

たぶん、こうすればできると思う。patchを作成しよう

$ git diff > sqlite_call_dsn.patch

作成したpatchは以下

diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index 36bb2a17ac..17003d628c 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -1441,6 +1441,7 @@ void pdo_dbh_init(void)
        REGISTER_PDO_CLASS_CONST_LONG("ATTR_CLIENT_VERSION",    (zend_long)PDO_ATTR_CLIENT_VERSION);
        REGISTER_PDO_CLASS_CONST_LONG("ATTR_SERVER_INFO",               (zend_long)PDO_ATTR_SERVER_INFO);
        REGISTER_PDO_CLASS_CONST_LONG("ATTR_CONNECTION_STATUS",         (zend_long)PDO_ATTR_CONNECTION_STATUS);
+       REGISTER_PDO_CLASS_CONST_LONG("ATTR_CALL_DSN",  (zend_long)PDO_ATTR_CALL_DSN);
        REGISTER_PDO_CLASS_CONST_LONG("ATTR_CASE",                      (zend_long)PDO_ATTR_CASE);
        REGISTER_PDO_CLASS_CONST_LONG("ATTR_CURSOR_NAME",       (zend_long)PDO_ATTR_CURSOR_NAME);
        REGISTER_PDO_CLASS_CONST_LONG("ATTR_CURSOR",            (zend_long)PDO_ATTR_CURSOR);
diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h
index 3350211a5e..9ba4b6d4ac 100644
--- a/ext/pdo/php_pdo_driver.h
+++ b/ext/pdo/php_pdo_driver.h
@@ -147,6 +147,7 @@ enum pdo_attribute_type {
        PDO_ATTR_DEFAULT_FETCH_MODE, /* Set the default fetch mode */
        PDO_ATTR_EMULATE_PREPARES,  /* use query emulation rather than native */
        PDO_ATTR_DEFAULT_STR_PARAM, /* set the default string parameter type (see the PDO::PARAM_STR_* magic flags) */
+       PDO_ATTR_CALL_DSN,

        /* this defines the start of the range for driver specific options.
         * Drivers should define their own attribute constants beginning with this
diff --git a/ext/pdo_sqlite/sqlite_driver.c b/ext/pdo_sqlite/sqlite_driver.c
index 2cc7f72475..75f64d6ca3 100644
--- a/ext/pdo_sqlite/sqlite_driver.c
+++ b/ext/pdo_sqlite/sqlite_driver.c
@@ -289,7 +289,9 @@ static int pdo_sqlite_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return
                case PDO_ATTR_SERVER_VERSION:
                        ZVAL_STRING(return_value, (char *)sqlite3_libversion());
                        break;
-
+               case PDO_ATTR_CALL_DSN:
+                       ZVAL_STRING(return_value, dbh->data_source);
+                       break;
                default:
                        return 0;
        }

patchコマンドを使って反映できる。

$ patch -p1 < sqlite_call_dsn.patch

これでmakeすれば反映できる

$ make 

実行してみる

$ sapi/cli/php -r '$p = new PDO("sqlite::memory;"); var_dump($p->getAttribute(PDO::ATTR_CALL_DSN));'
string(8) ":memory;" 

sqlite:memory;から、:memory;を取り出すことができた。sqlite:が抜けたのは想定外だった。

感想

改造するのは楽しいですね。

参考

元ネタ

Slack phpusers-ja