Dieser Abschnitt bietet einen Ăberblick ĂŒber die Plugin-Architektur von
mysqlnd.
Ăberblick ĂŒber den MySQL Native Driver
Vor der Entwicklung eines mysqlnd-Plugins ist es
nĂŒtzlich, ein wenig ĂŒber den Aufbau von mysqlnd selbst
zu wissen. Mysqlnd besteht aus den folgenden Modulen:
| Modul-Statistiken | mysqlnd_statistics.c |
|---|---|
| Datenbankverbindung | mysqlnd.c |
| Ergebnismenge | mysqlnd_result.c |
| Metadaten der Ergebnismenge | mysqlnd_result_meta.c |
| Anweisung | mysqlnd_ps.c |
| Netzwerk | mysqlnd_net.c |
| Wire-Protokoll | mysqlnd_wireprotocol.c |
Objektorientiertes Paradigma in C
Auf der Code-Ebene verwendet mysqlnd ein C-Pattern, um
die Objektorientierung zu implementieren.
In C wird ein Objekt mittels struct beschrieben. Die
Mitglieder dieser Struktur sind die Eigenschaften des Objekts. Die
Strukturmitglieder, die auf Funktionen verweisen, stellen die Methoden dar.
Im Gegensatz zu anderen Sprachen wie C++ oder Java gibt es im objektorientierten Paradigma von C keine festen Regeln fĂŒr die Vererbung. Es gibt jedoch einige Konventionen, die befolgt werden mĂŒssen und auf die spĂ€ter eingegangen wird.
Der PHP-Lebenszyklus
Der Lebenszyklus von PHP besteht aus 2 grundlegenden Zyklen:
Wenn die PHP-Engine startet, ruft sie fĂŒr jede registrierte Erweiterung die Funktion fĂŒr die Modulinitialisierung (MINIT) auf. Dies ermöglicht es jedem Modul, Variablen zu definieren und Ressourcen zuzuweisen, die wĂ€hrend der gesamten Lebensdauer des Prozesses der PHP-Engine vorhanden sind. Wenn die PHP-Engine herunterfĂ€hrt, ruft sie fĂŒr jede Erweiterung die Funktion fĂŒr das Herunterfahren des Moduls (MSHUTDOWN) auf.
WĂ€hrend der Lebensdauer der PHP-Engine erhĂ€lt sie eine Reihe von Anfragen. Jede Anfrage löst einen neuen Lebenszyklus aus. Bei jeder Anfrage ruft die PHP-Engine die Anfrage-Initialisierungsfunktion der jeweiligen Erweiterung auf. Die Erweiterung kann alle fĂŒr die Bearbeitung der Anfrage erforderlichen Variablen definieren und Ressourcen zuweisen. Am Ende des Anfragezyklus ruft die Engine fĂŒr jede Erweiterung die Funktion zum Herunterfahren der Anfrage (RSHUTDOWN) auf, damit die Erweiterung alle erforderlichen AufrĂ€umarbeiten durchfĂŒhren kann.
Wie ein Plugin funktioniert
Ein mysqlnd-Plugin funktioniert, indem es die Aufrufe an
mysqlnd abfÀngt, die von Erweiterungen stammen, die
mysqlnd verwenden. Dies wird dadurch erreicht, dass die
Funktionstabelle von mysqlnd abgerufen, gesichert und
durch eine eigene Funktionstabelle ersetzt wird, die die Funktionen des
Plugins nach Bedarf aufruft.
Der folgende Code zeigt, wie die Funktionstabelle von
mysqlnd ersetzt wird:
/* ein Ort zum Speichern der ursprĂŒnglichen Funktionstabelle */
struct st_mysqlnd_conn_methods org_methods;
void minit_register_hooks(TSRMLS_D) {
/* die aktive Funktionstabelle */
struct st_mysqlnd_conn_methods * current_methods
= mysqlnd_conn_get_methods();
/* Sicherung der ursprĂŒnglichen Funktionstabelle */
memcpy(&org_methods, current_methods,
sizeof(struct st_mysqlnd_conn_methods);
/* Installation der neuen Methoden */
current_methods->query = MYSQLND_METHOD(my_conn_class, query);
}
Die Bearbeitung der Tabelle der Verbindungsfunktionen muss wĂ€hrend der Modulinitialisierung (MINIT) erfolgen. Die Funktionstabelle ist eine gemeinsam genutzte globale Ressource. In einer Multi-Thread-Umgebung mit einem TSRM-Build fĂŒhrt die Bearbeitung einer gemeinsam genutzten globalen Ressource wĂ€hrend der Verarbeitung einer Anfrage mit ziemlicher Sicherheit zu Konflikten.
Hinweis: Bei der Bearbeitung der Funktionstabelle von
mysqlndsollte keine Logik mit fester GröĂe verwendet werden: Neue Methoden könnten am Ende der Funktionstabelle hinzugefĂŒgt werden. Die Funktionstabelle kann sich in der Zukunft jederzeit Ă€ndern.
Aufrufen von Elternmethoden
Wenn die ursprĂŒngliche Funktionstabelle gesichert wird, können die ursprĂŒnglichen EintrĂ€ge - die Elternmethoden - weiterhin aufgerufen werden.
In einigen FÀllen, z. B. bei Connection::stmt_init(), ist
es unerlÀsslich, die Elternmethode aufzurufen, bevor weitere Aktionen in
der abgeleiteten Methode erfolgen.
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 */
}
Erweitern von Eigenschaften
Ein mysqlnd-Objekt wird durch eine C-Struktur (struct)
dargestellt. Es ist nicht möglich, einer C-Struktur zur Laufzeit ein
Mitglied hinzuzufĂŒgen. Benutzer von mysqlnd-Objekten
können nicht einfach Eigenschaften zu den Objekten hinzufĂŒgen.
Beliebige Daten (Eigenschaften) können zu einem
mysqlnd-Objekt hinzugefĂŒgt werden, indem eine geeignete
Funktion der
mysqlnd_plugin_get_plugin_<object>_data()-Familie
verwendet wird. Wenn ein Objekt zugewiesen wird, reserviert
mysqlnd am Ende des Objekts Speicherplatz fĂŒr einen
void *-Zeiger auf beliebige Daten.
mysqlnd reserviert den Platz fĂŒr einen
void *-Zeiger pro Plugin.
Die folgende Tabelle zeigt, wie die Position des Zeigers fĂŒr ein bestimmtes Plugin berechnet werden kann:
| Speicheradresse | Inhalt |
|---|---|
| 0 | Beginn der C-Struktur des mysqlnd-Objekts |
| n | Ende der C-Struktur des mysqlnd-Objekts |
| n + (m x sizeof(void*)) | void* zu den Objektdaten des m-ten Plugins |
Wenn geplant ist, Unterklassen fĂŒr einen der Konstruktoren des
mysqlnd-Objekts zu erstellen, was erlaubt ist, muss dies
unbedingt bedacht werden!
Der folgende Code zeigt, wie Eigenschaften erweitert werden:
/* alle Daten, die zugeordnet werden sollen */
typedef struct my_conn_properties {
unsigned long query_counter;
} MY_CONN_PROPERTIES;
/* die Plugin-ID */
unsigned int my_plugin_id;
void minit_register_hooks(TSRMLS_D) {
/* erhalten einer eindeutigen Plugin-ID */
my_plugin_id = mysqlnd_plugin_register();
/* snip - siehe 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;
}
Der Plugin-Entwickler ist fĂŒr die Verwaltung des Speichers verantwortlich, der fĂŒr die Plugin-Daten verwendet wird.
Es wird empfohlen, fĂŒr die Daten von Plugins den Speicherallokator von
mysqlnd zu verwenden. Diese Funktionen werden nach dem
Schema mnd_*loc() benannt. Der
mysqlnd-Allokator hat einige nĂŒtzliche Eigenschaften,
zum Beispiel die Möglichkeit, einen Debug-Allokator in einem
Nicht-Debug-Build zu benutzen.
| Â | Wann soll die Unterklasse erstellt werden? | Hat jede Instanz ihre eine eigene Funktionstabelle? | Wie wird die Unterklasse erstellt? |
|---|---|---|---|
| Verbindung (MYSQLND) | MINIT | Nein | mysqlnd_conn_get_methods() |
| Ergebnismenge (MYSQLND_RES) | MINIT oder spÀter | Ja | mysqlnd_result_get_methods() oder Manipulation der Funktionstabelle der Objektmethoden |
| Metadaten der Ergebnismenge (MYSQLND_RES_METADATA) | MINIT | Nein | mysqlnd_result_metadata_get_methods() |
| Anweisung (MYSQLND_STMT) | MINIT | Nein | mysqlnd_stmt_get_methods() |
| Netzwerk (MYSQLND_NET) | MINIT oder spÀter | Ja | mysqlnd_net_get_methods() oder Manipulation der Funktionstabelle der Objektmethoden |
| Wire-Protokoll (MYSQLND_PROTOCOL) | MINIT oder spÀter | Ja | mysqlnd_protocol_get_methods() oder Manipulation der Funktionstabelle der Objektmethoden |
Nach MINIT darf die Funktionstabelle nicht mehr manipuliert werden, wenn es nach der obigen Tabelle nicht erlaubt ist.
Einige Klassen enthalten einen Zeiger auf die Funktionstabelle der Methoden. Alle Instanzen einer solchen Klasse teilen sich dieselbe Funktionstabelle. Um Chaos zu vermeiden, insbesondere in Umgebungen mit Threads, dĂŒrfen solche Funktionstabellen nur wĂ€hrend MINIT manipuliert werden.
Andere Klassen verwenden Kopien einer gemeinsam genutzten globalen Funktionstabelle. Die Kopie der Funktionstabelle der Klasse wird zusammen mit dem Objekt erstellt. Jedes Objekt verwendet seine eigene Funktionstabelle. Dadurch ergeben sich zwei Möglichkeiten: Zum einen kann die Standard-Funktionstabelle eines Objekts wÀhrend MINIT manipuliert werden, zum anderen können die Methoden eines Objekts zusÀtzlich angepasst werden, ohne dass sich dies auf andere Instanzen derselben Klasse auswirkt.
Der Vorteil der gemeinsam genutzten Funktionstabellen ist die verbesserte Leistung. Das liegt daran, dass es nicht nötig ist, eine Funktionstabelle fĂŒr jedes einzelne Objekt zu kopieren.
| Typ | Zuweisung, Konstruktion, ZurĂŒcksetzen | Kann geĂ€ndert werden? | Aufgerufen von |
|---|---|---|---|
| Verbindung (MYSQLND) | mysqlnd_init() | Nein | mysqlnd_connect() |
| Ergebnismenge (MYSQLND_RES) |
Zuweisung:
|
Ja, aber die Elternmethode aufrufen! |
|
| Metadaten der Ergebnismenge (MYSQLND_RES_METADATA) | Connection::result_meta_init() | Ja, aber die Elternmethode aufrufen! | Result::read_result_metadata() |
| Anweisung (MYSQLND_STMT) | Connection::stmt_init() | Ja, aber die Elternmethode aufrufen! | Connection::stmt_init() |
| Netzwerk (MYSQLND_NET) | mysqlnd_net_init() | Nein | Connection::init() |
| Wire-Protokoll (MYSQLND_PROTOCOL) | mysqlnd_protocol_init() | Nein | Connection::init() |
Es wird dringend empfohlen, einen Konstruktor nicht vollstÀndig zu
ersetzen. Die Konstruktoren fĂŒhren Speicherzuweisungen durch, die fĂŒr die
API des mysqlnd-Plugins und die Objektlogik von
mysqlnd unerlÀsslich sind. Wenn ein Entwickler die
Warnungen ignoriert und darauf besteht, die Konstruktoren einzuhÀngen,
sollte er zumindest den ĂŒbergeordneten Konstruktor aufrufen, bevor er etwas
mit dem Konstruktor tut.
Ungeachtet aller Warnungen kann es nĂŒtzlich sein, Konstruktoren zu vererben. Konstruktoren sind der perfekte Ort, um die Funktionstabellen von Objekten zu Ă€ndern, die nicht gemeinsam genutzte Objekttabellen (z. B. Ergebnismenge, Netzwerk, Wire-Protokoll) haben.
| Typ | Muss die abgeleitete Methode die Elternmethode aufrufen? | Destruktor |
|---|---|---|
| Verbindung | ja, nachdem die Methode ausgefĂŒhrt wurde | free_contents(), end_psession() |
| Ergebnismenge | ja, nachdem die Methode ausgefĂŒhrt wurde | free_result() |
| Metadaten der Ergebnismenge | ja, nachdem die Methode ausgefĂŒhrt wurde | free() |
| Anweisung | ja, nachdem die Methode ausgefĂŒhrt wurde | dtor(), free_stmt_content() |
| Netzwerk | ja, nachdem die Methode ausgefĂŒhrt wurde | free() |
| Wire-Protokoll | ja, nachdem die Methode ausgefĂŒhrt wurde | free() |
Der Destruktor ist der geeignete Ort, um Ressourcen freizugeben, die von
mysqlnd_plugin_get_plugin_<object>_data()-Eigenschaften
belegt sind.
Die aufgefĂŒhrten Destruktoren entsprechen nicht unbedingt der eigentlichen
mysqlnd-Methode, die das Objekt selbst freigibt. Dennoch
sind sie der bestmögliche Ort, um die Plugin-Daten einzuhÀngen und
freizugeben. Wie bei den Konstruktoren können die Methoden vollstÀndig
ersetzt werden, was jedoch nicht empfohlen wird. Wenn in der obigen Tabelle
mehrere Methoden aufgefĂŒhrt sind, mĂŒssen alle aufgefĂŒhrten Methoden
eingehÀngt und die Plugin-Daten in der Methode freigegeben werden, die von
mysqlnd zuerst aufgerufen wird.
Die empfohlene Methode fĂŒr Plugins ist, einfach die Methoden einzuhĂ€ngen, den Speicher freizugeben und unmittelbar danach die ĂŒbergeordnete Implementierung aufzurufen.