本節概述 mysqlnd
插件架構。
MySQL 原生驅動程式概述
在開發 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 *
指標的空間。
下表顯示如何計算特定插件的指標位置
記憶體位址 | 內容 |
---|---|
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) | 配置
重置並重新初始化於
|
可以,但要呼叫父類別! |
|
結果集元資料 (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
首先呼叫的任何方法中釋放您的插件數據。
建議的插件方法是簡單地掛載這些方法,釋放您的記憶體,並在之後立即呼叫父類別的實作。