Важно помнить, что плагин mysqlnd сам по себе
является модулем PHP.
Следующий пример показывает базовую структуру функции MINIT,
использующуюся в типичном плагине mysqlnd:
/* my_php_mysqlnd_plugin.c */
static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
/* глобальные переменные, ini-настройки, ресурсы, классы */
/* регистрируем плагин 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)(/* ... */) {
/* ... */
}
Анализ задачи: от C до пользовательского пространства
class proxy extends mysqlnd_plugin_connection {
public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());
Процесс:
PHP: пользователь регистрирует callback-функцию плагина
PHP: пользователь вызывает PHP MySQL API для соединения с MySQL
C: ext/*mysql* вызывает метод mysqlnd
C: mysqlnd обрывается в ext/mysqlnd_plugin
C: ext/mysqlnd_plugin
Вызывает пользовательскую callback-функцию
Или оригинальный метод mysqlnd, если она не задана
Вам необходимо выполнить следующие действия:
Создайте в С класс "mysqlnd_plugin_connection"
Примите и зарегистрируйте прокси объект с помощью "mysqlnd_plugin_set_conn_proxy()"
Вызовите прокси методы пространства пользователя из C (оптимизация - zend_interfaces.h)
Методы объекта пространства пользователя должны быть вызваны с помощью
call_user_function() или на уровень ниже, с помощью
zend_call_method().
Оптимизация: вызывайте методы из С с помощью zend_call_method
Следующий кусок кода демонстрирует прототип функции
zend_call_method, взятый из
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 );
API движка Zend поддерживает только два аргумента. Иногда требуется больше. Например:
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 );
Для обхода этой проблемы вам необходимо сделать копию
zend_call_method() и добавить необходимые параметры.
Вы можете сделать это создав набор макросов
MY_ZEND_CALL_METHOD_WRAPPER.
Обращение к пространству пользователя PHP
Этот кусок кода демонстрирует оптимизированный метод вызова функций пространства пользователя из С:
/* 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)
{
/* Вызов прокси пространства пользователя */
ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
}
else
{
/* или оригинальный метод mysqlnd = ничего не делать, быть прозрачным */
ret = org_methods.connect(conn, host, user, passwd,
passwd_len, db, db_len, port,
socket, mysql_flags TSRMLS_CC);
}
return ret;
}
Обращение к пространству пользователя: простые аргументы
/* 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);
/* ... */
}
/* ... */
}
Обращение к пространству пользователя: структуры как аргументы
/* 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);
/* ... */
}
/* ... */
}
первый аргумент многих методов mysqlnd — «объекты» С.
Например, первый аргумент метода connect() — указатель
на MYSQLND. Структура MYSQLND —
объект соединения mysqlnd.
Указатель на объект соединения mysqlnd сравнивают со стандартным
обработчиком файлового ввода-вывода. Так же как и он, объект соединения
mysqlnd связывают с пространством пользователя
через PHP-тип resource.
Из C в пространство пользователя и обратно
<?php
class proxy extends mysqlnd_plugin_connection
{
public function connect($conn, $host, ...)
{
/* До внедрения */
printf("Подключение к = '%s'\n", $host);
debug_print_backtrace();
return parent::connect($conn);
}
public function query($conn, $query)
{
/* После внедрения */
$ret = parent::query($conn, $query);
printf("Запрос = '%s'\n", $query);
return $ret;
}
}
mysqlnd_plugin_set_conn_proxy(new proxy());
Пользователи PHP должны иметь возможность вызывать родительские реализации переопределённых методов.
В результате наследования возможно «уточнить» только выбранные методы и вы можете выбрать, когда выполнять ваш код, до или после родительского.
Встроенный класс: mysqlnd_plugin_connection::connect()
/* my_mysqlnd_plugin_classes.c */
PHP_METHOD("mysqlnd_plugin_connection", connect)
{
/* ... упрощённый! ... */
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;
}