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:
C : ext/mysqlnd_plugin
mysqlnd, si l'espace
utilisateur n'a pas défini de fonction de rappel
Vous devez effectuer les opérations suivantes :
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;
}