Es importante recordar que un plugin mysqlnd
es en sí mismo una extensión PHP.
El código siguiente muestra la estructura básica de una función MINIT
utilizada en un plugin típico mysqlnd
:
/* my_php_mysqlnd_plugin.c */ static PHP_MINIT_FUNCTION(mysqlnd_plugin) { /* globales, entradas ini, recursos, clases */ /* registro del 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)(/* ... */) { /* ... */ }
Tarea de análisis: desde C hacia el espacio de usuario
class proxy extends mysqlnd_plugin_connection { public function connect($host, ...) { .. } } mysqlnd_plugin_set_conn_proxy(new proxy());
Proceso:
PHP: el usuario registra una función de devolución de llamada para el plugin
PHP: el usuario llama a un método de la API PHP MySQL para conectarse a MySQL
C: ext/*mysql* llama al método mysqlnd
C: mysqlnd termina en ext/mysqlnd_plugin
C: ext/mysqlnd_plugin
Llamada a la función de devolución de llamada del espacio de usuario
O la función original mysqlnd
, si el espacio
de usuario no ha definido una función de devolución de llamada
Debe realizar las siguientes operaciones:
Escribir una clase "mysqlnd_plugin_connection" en C
Aceptar y registrar el objeto proxy a través de "mysqlnd_plugin_set_conn_proxy()"
Llamar a los métodos de proxy del espacio de usuario desde C (optimización - zend_interfaces.h)
Los métodos del objeto del espacio de usuario pueden ser llamados
utilizando call_user_function()
,
o puede operar a un nivel por debajo del motor Zend y
utilizar zend_call_method()
.
Optimización: llamada a métodos desde C utilizando zend_call_method
El código siguiente muestra un prototipo para la función
zend_call_method
, obtenido 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 );
La API Zend soporta 2 argumentos. Puede necesitar más, por ejemplo:
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 );
Para solucionar este problema, deberá hacer una copia
de zend_call_method()
y añadir funcionalidad para añadir parámetros.
Puede lograr esto creando un conjunto de macros
MY_ZEND_CALL_METHOD_WRAPPER
.
Llamada al espacio de usuario PHP
El código siguiente muestra el método optimizado para realizar una llamada a una función del espacio de usuario desde 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) { /* llamada al proxy del espacio de usuario */ ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/); } else { /* o el método original mysqlnd = no hacer nada, ser transparente */ ret = org_methods.connect(conn, host, user, passwd, passwd_len, db, db_len, port, socket, mysql_flags TSRMLS_CC); } return ret; }
Llamada al espacio de usuario: argumentos 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); /* ... */ } /* ... */ }
Llamada al espacio de usuario: estructuras como argumentos
/* 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); /* ... */ } /* ... */ }
El primer argumento de todas las funciones mysqlnd
es un objeto C. Por ejemplo, el primer argumento de la función
connect() es un puntero hacia MYSQLND
.
La estructura MYSQLND representa un objeto de conexión
mysqlnd
.
El puntero del objeto de conexión mysqlnd
puede ser comparado a un puntero de archivo estándar I/O.
Al igual que un puntero de archivo estándar I/O, un objeto de
conexión mysqlnd
debe ser vinculado al espacio
de usuario utilizando una variable PHP de tipo recurso.
Desde C hacia el espacio de usuario, y luego de vuelta
class proxy extends mysqlnd_plugin_connection { public function connect($conn, $host, ...) { /* "pre" hook */ printf("Conexión al host = '%s'\n", $host); debug_print_backtrace(); return parent::connect($conn); } public function query($conn, $query) { /* "post" hook */ $ret = parent::query($conn, $query); printf("Consulta = '%s'\n", $query); return $ret; } } mysqlnd_plugin_set_conn_proxy(new proxy());
Los usuarios PHP deben poder llamar a la implementación del padre de un método sobrescrito.
Como resultado de una subclase, es posible redefinir únicamente los métodos seleccionados, y puede elegir tener acciones "pre" o "post".
Construcción de una clase: mysqlnd_plugin_connection::connect()
/* my_mysqlnd_plugin_classes.c */ PHP_METHOD("mysqlnd_plugin_connection", connect) { /* ... simplificado! ... */ 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, /* simplificado! */ TSRMLS_CC)) RETVAL_TRUE; else RETVAL_FALSE; }