Es importante recordar que un complemento de mysqlnd
es una extensión de PHP en sí mismo.
El siguiente código muestra la estructura básica de la función MINIT
que se usará en el típico complemento de mysqlnd
:
/* my_php_mysqlnd_plugin.c */ static PHP_MINIT_FUNCTION(mysqlnd_plugin) { /* globales, entradas ini, recursos, clases */ /* registrar el complemento 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)(/* ... */) { /* ... */ }
Análisis de tareas: de C al 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 la llamada de retorno del complemento
PHP: el usuario llama a cualquier API de MySQL de PHP para conectarse a MySQL
C: ext/*mysql* llama al método de mysqlnd
C: mysqlnd finaliza en ext/mysqlnd_plugin
C: ext/mysqlnd_plugin
Llama a la llamada de retorno del espacio de usuario
O al método original de mysqlnd
, si la llamada de retorno
del espacio de usuario no está establecida
Es necesario realizar lo siguiente:
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 del proxy del espacio de usuario desde C (optimización - zend_interfaces.h)
Se pueden llamar a los métodos del objeto del espacio de usuario usando
call_user_function()
o se puede operar a nivel
cercano al Motor Zend y usar
zend_call_method()
.
Optimización: llamar a los métodos desde C usando zend_call_method
El siguiente trozo de código muestra el prototipo para la
función zend_call_method
, tomado 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 solamente soporta dos argumentos. Se pueden 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 evitar este problema se necesitará realizar una copia de
zend_call_method()
y añadir la facilidad para
parámetros adicionales. Se puede hacer esto creando un conjunto de
macros MY_ZEND_CALL_METHOD_WRAPPER
.
Llamar al espacio de usuario de PHP
Este trozo de código muestra el método optimizado para llamar 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) { /* llamar al proxy del espacio de usuario */ ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/); } else { /* o al método original de mysqlnd = no hagas nada, sé transparente */ ret = org_methods.connect(conn, host, user, passwd, passwd_len, db, db_len, port, socket, mysql_flags TSRMLS_CC); } return ret; }
Llamar 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); /* ... */ } /* ... */ }
Llamar al espacio de usuario: estrucutras 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 la mayoría de los métodos de mysqlnd
es un "objeto"
de C. Por ejemplo, el primer argumento del método connect() es
un puntero a MYSQLND
. La estructura MYSQLND
representa un objeto de conexión de mysqlnd
.
El puntero del objeto de conexión de mysqlnd
puede ser
comparado con un gestor de ficheros de E/S estándar. Al igual que un gestor de ficheros de E/S
estándar, un objeto de conexión de mysqlnd
será vinculado
al espacio de usuario usando el tipo de variable de recurso de PHP.
Desde C al espacio de usuario y volver
class proxy extends mysqlnd_plugin_connection { public function connect($conn, $host, ...) { /* "pre" enganche */ printf("Connecting to host = '%s'\n", $host); debug_print_backtrace(); return parent::connect($conn); } public function query($conn, $query) { /* "post" enganche */ $ret = parent::query($conn, $query); printf("Query = '%s'\n", $query); return $ret; } } mysqlnd_plugin_set_conn_proxy(new proxy());
Los usuario de PHP pueden llamar a la implementación padre de un método sobrescrito.
Como resultado de la derivación, es posible refinar solamente los métodos seleccionados, y se puede optar por tener "pre" o "post" enganches.
Contruir la 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, /* simplified! */ TSRMLS_CC)) RETVAL_TRUE; else RETVAL_FALSE; }