2024 年 PHP Conference Japan

mysqlnd 插件開發入門

重要的是要記住,`mysqlnd` 插件本身就是一個 PHP 擴充套件。

以下程式碼顯示了典型的 `mysqlnd` 插件中將使用的 MINIT 函式的基本結構

/* my_php_mysqlnd_plugin.c */

 static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
  /* globals, ini entries, resources, classes */

  /* register mysqlnd plugin */
  mysqlnd_plugin_id = mysqlnd_plugin_register();

  conn_m = mysqlnd_get_conn_methods();
  memcpy(org_conn_m, conn_m,
    sizeof(struct st_mysqlnd_conn_methods));

  conn_m->query = MYSQLND_METHOD(mysqlnd_plugin_conn, query);
  conn_m->connect = MYSQLND_METHOD(mysqlnd_plugin_conn, connect);
}
/* my_mysqlnd_plugin.c */

 enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, query)(/* ... */) {
  /* ... */
}
enum_func_status MYSQLND_METHOD(mysqlnd_plugin_conn, connect)(/* ... */) {
  /* ... */
}

任務分析:從 C 到使用者空間

 class proxy extends mysqlnd_plugin_connection {
  public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());

流程

  1. PHP:使用者註冊插件回呼

  2. PHP:使用者呼叫任何 PHP MySQL API 來連線到 MySQL

  3. C:ext/*mysql* 呼叫 mysqlnd 方法

  4. C:mysqlnd 最終進入 ext/mysqlnd_plugin

  5. C:ext/mysqlnd_plugin

    1. 呼叫使用者空間回呼

    2. 或者,如果未設定使用者空間回呼,則使用原始的 `mysqlnd` 方法

您需要執行以下操作

  1. 以 C 語言撰寫一個名為 "mysqlnd_plugin_connection" 的類別

  2. 透過 "mysqlnd_plugin_set_conn_proxy()" 接受並註冊代理物件

  3. 從 C 呼叫使用者空間代理方法(最佳化 - zend_interfaces.h)

使用者空間物件方法可以透過 call_user_function() 呼叫,或者您可以在更接近 Zend 引擎的層級操作,並使用 zend_call_method()

最佳化:使用 zend_call_method 從 C 呼叫方法

以下程式碼片段顯示了 zend_call_method 函式的原型,取自 zend_interfaces.h

 ZEND_API zval* zend_call_method(
  zval **object_pp, zend_class_entry *obj_ce,
  zend_function **fn_proxy, char *function_name,
  int function_name_len, zval **retval_ptr_ptr,
  int param_count, zval* arg1, zval* arg2 TSRMLS_DC
);

Zend API 只支援兩個參數。您可能需要更多,例如

 enum_func_status (*func_mysqlnd_conn__connect)(
  MYSQLND *conn, const char *host,
  const char * user, const char * passwd,
  unsigned int passwd_len, const char * db,
  unsigned int db_len, unsigned int port,
  const char * socket, unsigned int mysql_flags TSRMLS_DC
);

要解決此問題,您需要複製 zend_call_method() 並新增額外參數的功能。您可以透過建立一組 MY_ZEND_CALL_METHOD_WRAPPER 巨集來完成此操作。

呼叫 PHP 使用者空間

此程式碼片段顯示了從 C 呼叫使用者空間函式的最佳化方法

 
/* my_mysqlnd_plugin.c */

MYSQLND_METHOD(my_conn_class,connect)(
  MYSQLND *conn, const char *host /* ... */ TSRMLS_DC) {
  enum_func_status ret = FAIL;
  zval * global_user_conn_proxy = fetch_userspace_proxy();
  if (global_user_conn_proxy) {
    /* call userspace proxy */
    ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
  } else {
    /* or original mysqlnd method = do nothing, be transparent */
    ret = org_methods.connect(conn, host, user, passwd,
          passwd_len, db, db_len, port,
          socket, mysql_flags TSRMLS_CC);
  }
  return ret;
}

呼叫使用者空間:簡單參數

/* my_mysqlnd_plugin.c */

 MYSQLND_METHOD(my_conn_class,connect)(
  /* ... */, const char *host, /* ...*/) {
  /* ... */
  if (global_user_conn_proxy) {
    /* ... */
    zval* zv_host;
    MAKE_STD_ZVAL(zv_host);
    ZVAL_STRING(zv_host, host, 1);
    MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_host /*, ...*/);
    zval_ptr_dtor(&zv_host);
    /* ... */
  }
  /* ... */
}

呼叫使用者空間:結構作為參數

/* my_mysqlnd_plugin.c */

MYSQLND_METHOD(my_conn_class, connect)(
  MYSQLND *conn, /* ...*/) {
  /* ... */
  if (global_user_conn_proxy) {
    /* ... */
    zval* zv_conn;
    ZEND_REGISTER_RESOURCE(zv_conn, (void *)conn, le_mysqlnd_plugin_conn);
    MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, zv_retval, zv_conn, zv_host /*, ...*/);
    zval_ptr_dtor(&zv_conn);
    /* ... */
  }
  /* ... */
}

許多 mysqlnd 方法的第一個參數是一個 C「物件」。例如,connect() 方法的第一個參數是指向 MYSQLND 的指標。 結構 MYSQLND 代表一個 mysqlnd 連線物件。

mysqlnd 連線物件指標可以與標準 I/O 檔案控制代碼進行比較。如同標準 I/O 檔案控制代碼,mysqlnd 連線物件應使用 PHP 資源變數類型連結到使用者空間。

從 C 到使用者空間,再返回

 class proxy extends mysqlnd_plugin_connection {
  public function connect($conn, $host, ...) {
    /* "pre" hook */
    printf("Connecting to host = '%s'\n", $host);
    debug_print_backtrace();
    return parent::connect($conn);
  }

  public function query($conn, $query) {
    /* "post" hook */
    $ret = parent::query($conn, $query);
    printf("Query = '%s'\n", $query);
    return $ret;
  }
}
mysqlnd_plugin_set_conn_proxy(new proxy());

PHP 使用者必須能夠呼叫覆寫方法的父類別實作。

透過子類別化,可以僅精煉選定的方法,並且您可以選擇使用「前置」或「後置」鉤子。

內建類別:mysqlnd_plugin_connection::connect()

/*  my_mysqlnd_plugin_classes.c */

 PHP_METHOD("mysqlnd_plugin_connection", connect) {
  /* ... simplified! ... */
  zval* mysqlnd_rsrc;
  MYSQLND* conn;
  char* host; int host_len;
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",
    &mysqlnd_rsrc, &host, &host_len) == FAILURE) {
    RETURN_NULL();
  }
  ZEND_FETCH_RESOURCE(conn, MYSQLND* conn, &mysqlnd_rsrc, -1,
    "Mysqlnd Connection", le_mysqlnd_plugin_conn);
  if (PASS == org_methods.connect(conn, host, /* simplified! */ TSRMLS_CC))
    RETVAL_TRUE;
  else
    RETVAL_FALSE;
}
新增註釋

使用者貢獻的註釋

此頁面沒有使用者貢獻的註釋。
To Top