2024 年日本 PHP 研討會

MySQL 原生驅動程式架構

本節概述 mysqlnd 插件架構。

MySQL 原生驅動程式概述

在開發 mysqlnd 插件之前,了解 mysqlnd 本身的組織方式會很有幫助。Mysqlnd 由以下模組組成:

依模組劃分的 mysqlnd 組織圖
模組統計 mysqlnd_statistics.c
連線 mysqlnd.c
結果集 mysqlnd_result.c
結果集詮釋資料 mysqlnd_result_meta.c
陳述式 (Statement) mysqlnd_ps.c
網路 mysqlnd_net.c
線路協定 mysqlnd_wireprotocol.c

C 物件導向範式

在程式碼層級,mysqlnd 使用一種 C 語言的模式來實現物件導向。

在 C 語言中,您使用 struct 來表示物件。結構的成員代表物件的屬性。指向函式的結構成員則代表方法。

與 C++ 或 Java 等其他語言不同,C 物件導向範式中沒有關於繼承的固定規則。然而,有一些慣例需要遵循,我們稍後會討論。

PHP 生命週期

考慮 PHP 生命週期時,有兩個基本週期:

  • PHP 引擎啟動和關閉週期

  • 請求週期

當 PHP 引擎啟動時,它會呼叫每個已註冊擴充的模組初始化 (MINIT) 函式。這允許每個模組設定變數並配置在 PHP 引擎程序生命週期內存在的資源。當 PHP 引擎關閉時,它會呼叫每個擴充的模組關閉 (MSHUTDOWN) 函式。

在 PHP 引擎的生命週期中,它會收到許多請求。每個請求都構成另一個生命週期。在每個請求中,PHP 引擎會呼叫每個擴充的請求初始化函式。擴充可以執行請求處理所需的任何變數設定和資源配置。當請求週期結束時,引擎會呼叫每個擴充的請求關閉 (RSHUTDOWN) 函式,以便擴充可以執行所需的任何清理工作。

插件如何運作

一個 mysqlnd 插件的運作方式是攔截使用 mysqlnd 的擴充對 mysqlnd 進行的呼叫。這是透過取得 mysqlnd 函式表、備份它,並將其替換為自訂函式表來實現的,該自訂函式表會根據需要呼叫插件的函式。

以下程式碼顯示如何替換 mysqlnd 函式表

/* a place to store original function table */
struct st_mysqlnd_conn_methods org_methods;

void minit_register_hooks(TSRMLS_D) {
  /* active function table */
  struct st_mysqlnd_conn_methods * current_methods
    = mysqlnd_conn_get_methods();

  /* backup original function table */
  memcpy(&org_methods, current_methods,
    sizeof(struct st_mysqlnd_conn_methods);

  /* install new methods */
  current_methods->query = MYSQLND_METHOD(my_conn_class, query);
}

連線函式表的操控必須在模組初始化 (MINIT) 期間完成。函式表是一個全域共用資源。在多執行緒環境中,使用 TSRM 建置時,在請求處理期間操控全域共用資源幾乎肯定會導致衝突。

注意:

在操控 mysqlnd 函式表時,請勿使用任何固定大小的邏輯:新的方法可能會新增到函式表的末尾。函式表將來可能隨時會更改。

呼叫父方法

如果原始函式表項目已備份,則仍然可以呼叫原始函式表項目 — 父方法。

在某些情況下,例如 Connection::stmt_init(),在衍生方法中的任何其他活動之前呼叫父方法至關重要。

MYSQLND_METHOD(my_conn_class, query)(MYSQLND *conn,
  const char *query, unsigned int query_len TSRMLS_DC) {

  php_printf("my_conn_class::query(query = %s)\n", query);

  query = "SELECT 'query rewritten' FROM DUAL";
  query_len = strlen(query);

  return org_methods.query(conn, query, query_len); /* return with call to parent */
}

擴充屬性

mysqlnd 物件由 C 結構表示。無法在執行時期將成員新增到 C 結構。 mysqlnd 物件的使用者無法簡單地將屬性新增到物件。

可以使用 mysqlnd_plugin_get_plugin_<object>_data() 系列的適當函式將任意資料(屬性)新增到 mysqlnd 物件。配置物件時,mysqlnd 會在物件末尾保留空間以存放指向任意資料的 void * 指標。 mysqlnd 為每個插件保留一個 void * 指標的空間。

下表顯示如何計算特定插件的指標位置

mysqlnd 的指標計算
記憶體位址 內容
0 mysqlnd 物件 C 結構的開頭
n mysqlnd 物件 C 結構的結尾
n + (m x sizeof(void*)) 指向第 m 個插件物件資料的 void* 指標

如果您打算繼承任何 `mysqlnd` 物件建構子(這是允許的),您必須牢記這一點!

以下程式碼顯示如何擴展屬性

/* any data we want to associate */
typedef struct my_conn_properties {
  unsigned long query_counter;
} MY_CONN_PROPERTIES;

/* plugin id */
unsigned int my_plugin_id;

void minit_register_hooks(TSRMLS_D) {
  /* obtain unique plugin ID */
  my_plugin_id = mysqlnd_plugin_register();
  /* snip - see Extending Connection: methods */
}

static MY_CONN_PROPERTIES** get_conn_properties(const MYSQLND *conn TSRMLS_DC) {
  MY_CONN_PROPERTIES** props;
  props = (MY_CONN_PROPERTIES**)mysqlnd_plugin_get_plugin_connection_data(
    conn, my_plugin_id);
  if (!props || !(*props)) {
    *props = mnd_pecalloc(1, sizeof(MY_CONN_PROPERTIES), conn->persistent);
    (*props)->query_counter = 0;
  }
  return props;
}

插件開發者負責管理插件資料的記憶體。

建議使用 `mysqlnd` 記憶體分配器來處理插件資料。這些函式的命名慣例為:`mnd_*loc()`。 `mysqlnd` 分配器有一些有用的功能,例如在非偵錯建置中使用偵錯分配器的能力。

何時以及如何繼承
  何時繼承? 每個實例都有自己的私有函式表? 如何繼承?
連線 (MYSQLND) MINIT mysqlnd_conn_get_methods()
結果集 (MYSQLND_RES) MINIT 或之後 mysqlnd_result_get_methods() 或物件方法函式表操作
結果集元資料 (MYSQLND_RES_METADATA) MINIT mysqlnd_result_metadata_get_methods()
語句 (MYSQLND_STMT) MINIT mysqlnd_stmt_get_methods()
網路 (MYSQLND_NET) MINIT 或之後 mysqlnd_net_get_methods() 或物件方法函式表操作
網路協定 (MYSQLND_PROTOCOL) MINIT 或之後 mysqlnd_protocol_get_methods() 或物件方法函式表操作

如果上表不允許,您絕不能在 MINIT 之後的任何時間操作函式表。

某些類別包含指向方法函式表的指標。此類別的所有實例將共用相同的函式表。為了避免混亂,尤其是在多執行緒環境中,此類函式表只能在 MINIT 期間操作。

其他類別使用全域共用函式表的副本。類別函式表副本會與物件一起建立。每個物件都使用自己的函式表。這為您提供了兩個選項:您可以在 MINIT 時操作物件的預設函式表,並且您可以額外調整物件的方法,而不會影響同一類別的其他實例。

共用函式表方法的優點是效能。不需要為每個物件複製函式表。

建構子狀態
類型 配置、建構、重置 可以修改嗎? 呼叫者
連線 (MYSQLND) mysqlnd_init() mysqlnd_connect()
結果集 (MYSQLND_RES)

配置

  • Connection::result_init()

重置並重新初始化於

  • Result::use_result()

  • Result::store_result

可以,但要呼叫父類別!
  • Connection::list_fields()

  • Statement::get_result()

  • Statement::prepare() (僅限元資料)

  • Statement::resultMetaData()

結果集元資料 (MYSQLND_RES_METADATA) Connection::result_meta_init() 可以,但要呼叫父類別! Result::read_result_metadata()
語句 (MYSQLND_STMT) Connection::stmt_init() 可以,但要呼叫父類別! Connection::stmt_init()
網路 (MYSQLND_NET) mysqlnd_net_init() Connection::init()
網路協定 (MYSQLND_PROTOCOL) mysqlnd_protocol_init() Connection::init()

強烈建議您不要完全取代建構子。建構子會執行記憶體配置。記憶體配置對於 `mysqlnd` 插件 API 和 `mysqlnd` 的物件邏輯至關重要。如果您不關心警告並堅持要掛鉤建構子,則至少應在建構子中執行任何操作之前呼叫父建構子。

儘管有所有警告,但繼承建構子可能很有用。建構子是修改具有非共用物件表(例如結果集、網路、網路協定)的物件的函式表的理想位置。

解構子狀態
類型 衍生方法必須呼叫父類別? 解構子
連線 是,在方法執行後 free_contents(), end_psession()
結果集 是,在方法執行後 free_result()
結果集元資料 是,在方法執行後 free()
陳述式 (Statement) 是,在方法執行後 dtor(), free_stmt_content()
網路 是,在方法執行後 free()
線路協定 是,在方法執行後 free()

解構子是釋放屬性 `mysqlnd_plugin_get_plugin_<object>_data()` 的適當位置。

列表中的解構器可能不等同於實際上釋放物件本身的 mysqlnd 方法。然而,它們是您介入並釋放插件數據的最佳位置。與建構器一樣,您可以完全替換這些方法,但不建議這樣做。如果上表中列出了多個方法,您需要掛載所有列出的方法,並在 mysqlnd 首先呼叫的任何方法中釋放您的插件數據。

建議的插件方法是簡單地掛載這些方法,釋放您的記憶體,並在之後立即呼叫父類別的實作。

新增註解

使用者貢獻的註解

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