## Начинаем разработку плагина 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
    );

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;
    }

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open