PHP 8.4.1 Released!

Начинаем разработку плагина mysqlnd

Важно помнить, что плагин 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());

Процесс:

  1. PHP: пользователь регистрирует callback-функцию плагина

  2. PHP: пользователь вызывает PHP MySQL API для соединения с MySQL

  3. C: ext/*mysql* вызывает метод mysqlnd

  4. C: mysqlnd обрывается в ext/mysqlnd_plugin

  5. C: ext/mysqlnd_plugin

    1. Вызывает пользовательскую callback-функцию

    2. Или оригинальный метод mysqlnd, если она не задана

Вам необходимо выполнить следующие действия:

  1. Создайте в С класс "mysqlnd_plugin_connection"

  2. Примите и зарегистрируйте прокси объект с помощью "mysqlnd_plugin_set_conn_proxy()"

  3. Вызовите прокси методы пространства пользователя из 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
);

Zend API поддерживает только два аргумента. Вам может понадобиться больше. К примеру:

 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;
}
Добавить

Примечания пользователей

Пользователи ещё не добавляли примечания для страницы
To Top