class proxy extends mysqlnd_plugin_connection {
public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());
Process:
PHP: user registers plugin callback
PHP: user calls any PHP MySQL API to connect to MySQL
C: ext/*mysql* calls mysqlnd method
C: mysqlnd ends up in ext/mysqlnd_plugin
C: ext/mysqlnd_plugin
Calls userspace callback
Or orginal mysqlnd method, if userspace
callback not set
You need to carry out the following:
Write a class "mysqlnd_plugin_connection" in C
Accept and register proxy object through
"mysqlnd_plugin_set_conn_proxy()"
Call userspace proxy methods from C (optimization -
zend_interfaces.h)
Userspace object methods can either be called using
call_user_function() or you can operate at a level
closer to the Zend Engine and use
zend_call_method().
Optimization: calling methods from C using
zend_call_method
The following code snippet shows the prototype for the
zend_call_method function, taken from
zend_interfaces.h.
Zend API supports only two arguments. You may need more, for example:
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
);
To get around this problem you will need to make a copy of
zend_call_method() and add a facility for
additional parameters. You can do this by creating a set of
MY_ZEND_CALL_METHOD_WRAPPER macros.
Calling PHP userspace
This code snippet shows the optimized method for calling a userspace
function from 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) {
/* call userspace proxy */
ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
} else {
/* or original mysqlnd method = do nothing, be transparent */
ret = org_methods.connect(conn, host, user, passwd,
passwd_len, db, db_len, port,
socket, mysql_flags TSRMLS_CC);
}
return ret;
}
The first argument of many mysqlnd methods is a C
"object". For example, the first argument of the connect() method is
a pointer to MYSQLND. The struct MYSQLND
represents a mysqlnd connection object.
The mysqlnd connection object pointer can be
compared to a standard I/O file handle. Like a standard I/O file
handle a mysqlnd connection object shall be linked
to the userspace using the PHP resource variable type.
From C to userspace and back
class proxy extends mysqlnd_plugin_connection {
public function connect($conn, $host, ...) {
/* "pre" hook */
printf("Connecting to host = '%s'\n", $host);
debug_print_backtrace();
return parent::connect($conn);
}
public function query($conn, $query) {
/* "post" hook */
$ret = parent::query($conn, $query);
printf("Query = '%s'\n", $query);
return $ret;
}
}
mysqlnd_plugin_set_conn_proxy(new proxy());
PHP users must be able to call the parent implementation of an
overwritten method.
As a result of subclassing it is possible to refine only selected
methods and you can choose to have "pre" or "post" hooks.