Plugin-Architektur des MySQL Native Drivers

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:

Das Organisationsdiagramm von mysqlnd, pro Modul
Modul-Statistikenmysqlnd_statistics.c
Datenbankverbindungmysqlnd.c
Ergebnismengemysqlnd_result.c
Metadaten der Ergebnismengemysqlnd_result_meta.c
Anweisungmysqlnd_ps.c
Netzwerkmysqlnd_net.c
Wire-Protokollmysqlnd_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:

  • Der Start- und Shutdown-Zyklus der PHP-Engine

  • Der Zyklus einer Anfrage

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:

 struct st_mysqlnd_conn_methods org_methods; void minit_register_hooks(TSRMLS_D) { struct st_mysqlnd_conn_methods * current_methods = mysqlnd_conn_get_methods(); memcpy(&org_methods, current_methods, sizeof(struct st_mysqlnd_conn_methods); 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 mysqlnd sollte 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); } 

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:

Berechnen eines Zeigers für mysqlnd
SpeicheradresseInhalt
0Beginn der C-Struktur des mysqlnd-Objekts
nEnde 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:

 typedef struct my_conn_properties { unsigned long query_counter; } MY_CONN_PROPERTIES; unsigned int my_plugin_id; void minit_register_hooks(TSRMLS_D) { my_plugin_id = mysqlnd_plugin_register(); } 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 und wie eine Unterklasse erstellt wird
 Wann soll die Unterklasse erstellt werden?Hat jede Instanz ihre eine eigene Funktionstabelle?Wie wird die Unterklasse erstellt?
Verbindung (MYSQLND)MINITNeinmysqlnd_conn_get_methods()
Ergebnismenge (MYSQLND_RES)MINIT oder späterJa mysqlnd_result_get_methods() oder Manipulation der Funktionstabelle der Objektmethoden
Metadaten der Ergebnismenge (MYSQLND_RES_METADATA)MINITNeinmysqlnd_result_metadata_get_methods()
Anweisung (MYSQLND_STMT)MINITNeinmysqlnd_stmt_get_methods()
Netzwerk (MYSQLND_NET)MINIT oder späterJa mysqlnd_net_get_methods() oder Manipulation der Funktionstabelle der Objektmethoden
Wire-Protokoll (MYSQLND_PROTOCOL)MINIT oder späterJa 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.

Status des Konstruktors
TypZuweisung, Konstruktion, ZurücksetzenKann geändert werden?Aufgerufen von
Verbindung (MYSQLND)mysqlnd_init()Neinmysqlnd_connect()
Ergebnismenge (MYSQLND_RES)

Zuweisung:

  • Connection::result_init()

Zurücksetzen und Neuinitialisierung während:

  • Result::use_result()

  • Result::store_result

Ja, aber die Elternmethode aufrufen!
  • Connection::list_fields()

  • Statement::get_result()

  • Statement::prepare() (nur Metadaten)

  • Statement::resultMetaData()

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()NeinConnection::init()
Wire-Protokoll (MYSQLND_PROTOCOL)mysqlnd_protocol_init()NeinConnection::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.

Status des Destruktors
TypMuss die abgeleitete Methode die Elternmethode aufrufen?Destruktor
Verbindungja, nachdem die Methode ausgeführt wurdefree_contents(), end_psession()
Ergebnismengeja, nachdem die Methode ausgeführt wurdefree_result()
Metadaten der Ergebnismengeja, nachdem die Methode ausgeführt wurdefree()
Anweisungja, nachdem die Methode ausgeführt wurdedtor(), free_stmt_content()
Netzwerkja, nachdem die Methode ausgeführt wurdefree()
Wire-Protokollja, nachdem die Methode ausgeführt wurdefree()

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.

To Top