Il est important de se souvenir qu'un plugin mysqlnd
est lui-même une extension PHP.
Le code suivant montre la structure basique d'une fonction MINIT
utilisée dans un plugin typique mysqlnd
:
/* my_php_mysqlnd_plugin.c */ static PHP_MINIT_FUNCTION(mysqlnd_plugin) { /* globales, entrées ini, ressources, classes */ /* enregistrement du plugin mysqlnd */ 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)(/* ... */) { /* ... */ }
Tâche d'analyse : depuis C vers l'espace utilisateur
class proxy extends mysqlnd_plugin_connection { public function connect($host, ...) { .. } } mysqlnd_plugin_set_conn_proxy(new proxy());
Process:
PHP : l'utilisateur enregistre une fonction de rappel pour le plugin
PHP : l'utilisateur appelle une méthode de l'API PHP MySQL pour se connecter à MySQL
C : ext/*mysql* appelle la méthode mysqlnd
C : mysqlnd se termine dans ext/mysqlnd_plugin
C : ext/mysqlnd_plugin
Appel de la fonction de rappel de l'espace utilisateur
Ou la méthode originale mysqlnd
, si l'espace
utilisateur n'a pas défini de fonction de rappel
Vous devez effectuer les opérations suivantes :
Écrire une classe "mysqlnd_plugin_connection" en C
Accepter et enregistrer l'objet proxy via "mysqlnd_plugin_set_conn_proxy()"
Appeler les méthodes de proxy de l'espace utilisateur depuis C (optimisation - zend_interfaces.h)
Les méthodes de l'objet de l'espace utilisateur peuvent soit être
appelées en utilisant call_user_function()
,
soit vous pouvez opérer à un niveau en dessous du moteur Zend et
utiliser zend_call_method()
.
Optimisation : appel des méthodes depuis C en utilisant zend_call_method
Le code suivant montre un prototype pour la fonction
zend_call_method
, issue de
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 );
L'API Zend supporte 2 arguments. Vous pouvez en avoir besoin de plus, par exemple :
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 );
Pour contourner ce problème, vous devrez faire une copie
de zend_call_method()
et ajouter une
fonctionnalité pour ajouter des paramètres. Vous pouvez
réaliser ceci en créant un jeu de macros
MY_ZEND_CALL_METHOD_WRAPPER
.
Appel de l'espace utilisateur PHP
Le code ci-dessous montre la méthode optimisée pour effectuer un appel à une fonction de l'espace utilisateur depuis C :
/* 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) { /* appel du proxy de l'espace utilisateur */ ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/); } else { /* ou la méthode originale mysqlnd = ne rien faire, être transparent */ ret = org_methods.connect(conn, host, user, passwd, passwd_len, db, db_len, port, socket, mysql_flags TSRMLS_CC); } return ret; }
Appel de l'espace utilisateur: arguments simples
/* 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); /* ... */ } /* ... */ }
Appel de l'espace utilisateur : structures comme arguments
/* 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); /* ... */ } /* ... */ }
Le premier argument de toutes les méthodes mysqlnd
est un objet C. Par exemple, le premier argument de la méthode
connect() est un pointeur vers MYSQLND
.
La structure MYSQLND représente un objet de connexion
mysqlnd
.
Le pointeur de l'objet de connexion mysqlnd
peut être comparé à un pointeur de fichier standard I/O.
Tout comme un pointeur de fichier standard I/O, un objet de
connexion mysqlnd
doit être lié à l'espace
utilisateur en utilisant une variable PHP de type ressource.
Depuis C vers l'espace utilisateur, puis, retour
class proxy extends mysqlnd_plugin_connection { public function connect($conn, $host, ...) { /* "pre" hook */ printf("Connexion à l'hôte = '%s'\n", $host); debug_print_backtrace(); return parent::connect($conn); } public function query($conn, $query) { /* "post" hook */ $ret = parent::query($conn, $query); printf("Requête = '%s'\n", $query); return $ret; } } mysqlnd_plugin_set_conn_proxy(new proxy());
Les utilisateurs PHP doivent pouvoir appeler l'implémentation du parent d'une méthode écrasée.
Comme résultat d'un sous-classement, il est possible de redéfinir uniquement les méthodes sélectionnées, et vous pouvez choisir d'avoir des actions "pre" ou "post".
Construction d'une classe : mysqlnd_plugin_connection::connect()
/* my_mysqlnd_plugin_classes.c */ PHP_METHOD("mysqlnd_plugin_connection", connect) { /* ... simplifié ! ... */ 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 Connection", le_mysqlnd_plugin_conn); if (PASS == org_methods.connect(conn, host, /* simplifié! */ TSRMLS_CC)) RETVAL_TRUE; else RETVAL_FALSE; }