Es ist wichtig zu bedenken, dass ein mysqlnd
-Plugin
selbst eine PHP-Erweiterung ist.
Der folgende Code zeigt die grundlegende Struktur der MINIT-Funktion, die
in einem typischen mysqlnd
-Plugin verwendet wird:
/* my_php_mysqlnd_plugin.c */ static PHP_MINIT_FUNCTION(mysqlnd_plugin) { /* Globals, ini-Einträge, Ressourcen, Klassen */ /* Registrieren des mysqlnd-Plugins */ 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)(/* ... */) { /* ... */ }
Analyse der Aufgaben: von C zum Userspace
class proxy extends mysqlnd_plugin_connection { public function connect($host, ...) { .. } } mysqlnd_plugin_set_conn_proxy(new proxy());
Ablauf:
PHP: Benutzer registriert den Plugin-Callback
PHP: Benutzer ruft eine beliebige PHP-MySQL-API auf, um sich mit MySQL zu verbinden
C: ext/*mysql* ruft die mysqlnd-Methode auf
C: mysqlnd endet in ext/mysqlnd_plugin
C: ext/mysqlnd_plugin
Ruft einen benutzerdefinierten Callback auf
oder die ursprüngliche mysqlnd
-Methode, wenn der
Userspace-Callback nicht definiert ist
Folgende Aufgaben sind dafür auszuführen:
In C eine Klasse namens "mysqlnd_plugin_connection" erstellen
Ein Proxy-Objekt mittels "mysqlnd_plugin_set_conn_proxy()" annehmen und registrieren
Userspace-Proxy-Methoden aus C aufrufen (Optimierung - zend_interfaces.h)
Die Methoden von Userspace-Objekten können entweder mittels
call_user_function()
aufgerufen werden oder auf einer
Ebene näher an der Zend Engine mittels
zend_call_method()
.
Optimierung: Aufruf von Methoden aus C mit zend_call_method
Der folgende Codeschnipsel zeigt den Prototyp für die Funktion
zend_call_method
und stammt aus
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 );
Die Zend-API erlaubt nur zwei Parameter. Eventuell werden mehr benötigt, zum Beispiel:
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 );
Um dieses Problem zu umgehen, muss eine Kopie von
zend_call_method()
erzeugt werden, die mit zusätzlichen
Parametern versehen werden kann. Dafür kann eine Reihe von
MY_ZEND_CALL_METHOD_WRAPPER
-Makros erstellt werden.
Aufruf des PHP-Userspaces
Dieser Codeschnipsel zeigt die optimierte Methode für den Aufruf einer Userspace-Funktion von C aus:
/* 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) { /* Aufrufen eines Userspace-Proxys */ ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/); } else { /* oder die ursprüngliche mysqlnd-Methode = nichts tun, transparent sein */ ret = org_methods.connect(conn, host, user, passwd, passwd_len, db, db_len, port, socket, mysql_flags TSRMLS_CC); } return ret; }
Aufruf des Userspaces: einfache Argumente
/* 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); /* ... */ } /* ... */ }
Aufruf des Userspaces: Strukturen als Argumente
/* 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); /* ... */ } /* ... */ }
Bei vielen mysqlnd
-Methoden ist das erste Argument ein
C-"Objekt". Zum Beispiel ist das erste Argument der Methode connect() ein
Zeiger auf MYSQLND
. Die Struktur MYSQLND stellt ein
mysqlnd
-Verbindungsobjekt dar.
Der Zeiger auf das mysqlnd
-Verbindungsobjekt kann mit
einem Standard-I/O-Dateihandle verglichen werden. Genau wie ein
Standard-I/O-Dateihandle muss ein
mysqlnd
-Verbindungsobjekt mit Hilfe einer PHP-Variablen
vom Typ Ressource mit dem Userspace verbunden werden.
Von C zum Userspace und zurück
class proxy extends mysqlnd_plugin_connection { public function connect($conn, $host, ...) { /* vor der Implementierung ("pre"-Hook) */ printf("Verbinden mit dem Host '%s'\n", $host); debug_print_backtrace(); return parent::connect($conn); } public function query($conn, $query) { /* nach der Implementierung ("post"-Hook) */ $ret = parent::query($conn, $query); printf("Abfrage = '%s'\n", $query); return $ret; } } mysqlnd_plugin_set_conn_proxy(new proxy());
PHP-Benutzer müssen die Möglichkeit haben, die übergeordnete Implementierung einer überschriebenen Methode aufzurufen.
Durch Vererbung ist es möglich, nur ausgewählte Methoden zu "verfeinern", und man kann wählen, wann der eigene Code ausgeführt werden soll, vor oder nach der übergeordneten Methode ("pre"- oder "post"-Hook).
Erstellen der Klasse: mysqlnd_plugin_connection::connect()
/* my_mysqlnd_plugin_classes.c */ PHP_METHOD("mysqlnd_plugin_connection", connect) { /* ... vereinfacht! ... */ 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-Verbindung", le_mysqlnd_plugin_conn); if (PASS == org_methods.connect(conn, host, /* simplified! */ TSRMLS_CC)) RETVAL_TRUE; else RETVAL_FALSE; }