PHP 8.5.0 Alpha 1 available for testing

Cómo comenzar a compilar un plugin mysqlnd

Es importante recordar que un plugin mysqlnd es en sí mismo una extensión PHP.

El código siguiente muestra la estructura básica de una función MINIT utilizada en un plugin típico mysqlnd:

/* my_php_mysqlnd_plugin.c */

 static PHP_MINIT_FUNCTION(mysqlnd_plugin) {
  /* globales, entradas ini, recursos, clases */

  /* registro del plugin 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)(/* ... */) {
  /* ... */
}

Tarea de análisis: desde C hacia el espacio de usuario

 class proxy extends mysqlnd_plugin_connection {
  public function connect($host, ...) { .. }
}
mysqlnd_plugin_set_conn_proxy(new proxy());

Proceso:

  1. PHP: el usuario registra una función de devolución de llamada para el plugin

  2. PHP: el usuario llama a un método de la API PHP MySQL para conectarse a MySQL

  3. C: ext/*mysql* llama al método mysqlnd

  4. C: mysqlnd termina en ext/mysqlnd_plugin

  5. C: ext/mysqlnd_plugin

    1. Llamada a la función de devolución de llamada del espacio de usuario

    2. O la función original mysqlnd, si el espacio de usuario no ha definido una función de devolución de llamada

Debe realizar las siguientes operaciones:

  1. Escribir una clase "mysqlnd_plugin_connection" en C

  2. Aceptar y registrar el objeto proxy a través de "mysqlnd_plugin_set_conn_proxy()"

  3. Llamar a los métodos de proxy del espacio de usuario desde C (optimización - zend_interfaces.h)

Los métodos del objeto del espacio de usuario pueden ser llamados utilizando call_user_function(), o puede operar a un nivel por debajo del motor Zend y utilizar zend_call_method().

Optimización: llamada a métodos desde C utilizando zend_call_method

El código siguiente muestra un prototipo para la función zend_call_method, obtenido 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 soporta 2 argumentos. Puede 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 solucionar este problema, deberá hacer una copia de zend_call_method() y añadir funcionalidad para añadir parámetros. Puede lograr esto creando un conjunto de macros MY_ZEND_CALL_METHOD_WRAPPER.

Llamada al espacio de usuario PHP

El código siguiente muestra el método optimizado para realizar una llamada 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) {
    /* llamada al proxy del espacio de usuario */
    ret = MY_ZEND_CALL_METHOD_WRAPPER(global_user_conn_proxy, host, /*...*/);
  } else {
    /* o el método original mysqlnd = no hacer nada, ser transparente */
    ret = org_methods.connect(conn, host, user, passwd,
          passwd_len, db, db_len, port,
          socket, mysql_flags TSRMLS_CC);
  }
  return ret;
}

Llamada 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);
    /* ... */
  }
  /* ... */
}

Llamada al espacio de usuario: estructuras 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 todas las funciones mysqlnd es un objeto C. Por ejemplo, el primer argumento de la función connect() es un puntero hacia MYSQLND. La estructura MYSQLND representa un objeto de conexión mysqlnd.

El puntero del objeto de conexión mysqlnd puede ser comparado a un puntero de archivo estándar I/O. Al igual que un puntero de archivo estándar I/O, un objeto de conexión mysqlnd debe ser vinculado al espacio de usuario utilizando una variable PHP de tipo recurso.

Desde C hacia el espacio de usuario, y luego de vuelta

 class proxy extends mysqlnd_plugin_connection {
  public function connect($conn, $host, ...) {
    /* "pre" hook */
    printf("Conexión al host = '%s'\n", $host);
    debug_print_backtrace();
    return parent::connect($conn);
  }

  public function query($conn, $query) {
    /* "post" hook */
    $ret = parent::query($conn, $query);
    printf("Consulta = '%s'\n", $query);
    return $ret;
  }
}
mysqlnd_plugin_set_conn_proxy(new proxy());

Los usuarios PHP deben poder llamar a la implementación del padre de un método sobrescrito.

Como resultado de una subclase, es posible redefinir únicamente los métodos seleccionados, y puede elegir tener acciones "pre" o "post".

Construcción de una 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, /* simplificado! */ TSRMLS_CC))
    RETVAL_TRUE;
  else
    RETVAL_FALSE;
}
add a note

User Contributed Notes

There are no user contributed notes for this page.
To Top