PHP Conference China 2020

Empezar a construir un complemento de mysqlnd

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

El siguiente código muestra la estructura básica de la función MINIT que se usará en el típico complemento de mysqlnd:

/* my_php_mysqlnd_plugin.c */

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

  /* registrar el complemento 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)(/* ... */) {
  /* ... */
}

Análisis de tareas: de C al 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 la llamada de retorno del complemento

  2. PHP: el usuario llama a cualquier API de MySQL de PHP para conectarse a MySQL

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

  4. C: mysqlnd finaliza en ext/mysqlnd_plugin

  5. C: ext/mysqlnd_plugin

    1. Llama a la llamada de retorno del espacio de usuario

    2. O al método original de mysqlnd, si la llamada de retorno del espacio de usuario no está establecida

Es necesario realizar lo siguiente:

  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 del proxy del espacio de usuario desde C (optimización - zend_interfaces.h)

Se pueden llamar a los métodos del objeto del espacio de usuario usando call_user_function() o se puede operar a nivel cercano al Motor Zend y usar zend_call_method().

Optimización: llamar a los métodos desde C usando zend_call_method

El siguiente trozo de código muestra el prototipo para la función zend_call_method, tomado 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 solamente soporta dos argumentos. Se pueden 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 evitar este problema se necesitará realizar una copia de zend_call_method() y añadir la facilidad para parámetros adicionales. Se puede hacer esto creando un conjunto de macros MY_ZEND_CALL_METHOD_WRAPPER.

Llamar al espacio de usuario de PHP

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

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

Llamar al espacio de usuario: estrucutras 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 la mayoría de los métodos de mysqlnd es un "objeto" de C. Por ejemplo, el primer argumento del método connect() es un puntero a MYSQLND. La estructura MYSQLND representa un objeto de conexión de mysqlnd.

El puntero del objeto de conexión de mysqlnd puede ser comparado con un gestor de ficheros de E/S estándar. Al igual que un gestor de ficheros de E/S estándar, un objeto de conexión de mysqlnd será vinculado al espacio de usuario usando el tipo de variable de recurso de PHP.

Desde C al espacio de usuario y volver

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

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

Los usuario de PHP pueden llamar a la implementación padre de un método sobrescrito.

Como resultado de la derivación, es posible refinar solamente los métodos seleccionados, y se puede optar por tener "pre" o "post" enganches.

Contruir la 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, /* simplified! */ TSRMLS_CC))
    RETVAL_TRUE;
  else
    RETVAL_FALSE;
}
add a note add a note

User Contributed Notes

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