Arquitectura de los Complementos del Controlador Nativo de MySQL

Esta sección proporciona información general de la arquitectura de los complementos de mysqlnd.

Información general del Controlador Nativo de MySQL

Antes de desarrollar complementos de mysqlnd, es útil conocer un poco cómo mysqlnd está organizado. Mysqlnd consiste en los siguientes módulos:

La gráfica de organización de mysqlnd, por módulo
Módulos de estadísticas mysqlnd_statistics.c
Conexión mysqlnd.c
Conjunto de resultados mysqlnd_result.c
Metadatos de conjuntos de resultados mysqlnd_result_meta.c
Sentencia mysqlnd_ps.c
Red mysqlnd_net.c
Protocolo de cable mysqlnd_wireprotocol.c

Paradigma de la orientación a objetos de C

A nivel de código, mysqlnd utiliza un patrón de C para implementar la orientación a objetos.

En C se utiliza una estrucutra (struct) para representar un objeto. Los miembros de la estructura representan las propiedades del objeto. Los miembros de la estrucutra que apuntan a funciones representan los métodos.

A diferencia de otros lenguajes como C++ o Java, no existen reglas fijas sobre herencia en el paradigma de la orientación a objetos de C. Sin embargo, existen algunas convenciones que se han de seguir y serán discutidas más tarde.

El ciclo de vida de PHP

Al considerar el ciclo de vida de PHP existen dos ciclos básicos:

  • El ciclo de inicio y cierre del motor de PHP

  • El ciclo de peticiones

Cuando se inicia el motor de PHP, éste llamará a la función de inicialización de módulos (MINIT) para cada extensión registrada. Esto permite a cada módulo establecer variables y asignar recursos que existirán durante el tiempo de vida del proceso del motor de PHP. Al cerrar el motor de PHP, éste llamará a las funciones de cierre de módulos (MSHUTDOWN) para cada extensión.

Durante el tiempo de vida del motor de PHP, éste recibirá varias peticiones. Cada petición constituye otro ciclo de vida. En cada petición, el motor de PHP llamará a la función de inicialización de peticiones para cada extensión. La extensión puede realizar cualquier establecimiento de variables y asignación de recursos necesarios para el proceso de petición. Al finalizar el ciclo de peticiones, el motor llama a la función de cierre de peticiones (RSHUTDOWN) de cada extensión, por lo que la extensión puede realizar cualquier limpieza necesaria.

Cómo funciona un complemento

Un complemento de mysqlnd funciona interceptando llamada realizadas a mysqlnd por extensiones que utilizan mysqlnd. Esto se lleva a cabo obteniendo la tabla de funciones de mysqlnd, haciendo una copia de seguridad de ella, y reemplazándola por una tabla de funciones personalizada, la cual llamará a las funciones del complemento cuando sea necesario.

El siguiente código muestra cómo se reemplaza la tabla de funciones de mysqlnd:

/* un lugar para almacenar la tabla de funciones original */
struct st_mysqlnd_conn_methods org_methods;

void minit_register_hooks(TSRMLS_D) {
  /* activar la tabla de funciones */
  struct st_mysqlnd_conn_methods * current_methods
    = mysqlnd_conn_get_methods();

  /* copiar la tabla de funciones original */
  memcpy(&org_methods, current_methods,
    sizeof(struct st_mysqlnd_conn_methods);

  /* instalar nuevos métodos */
  current_methods->query = MYSQLND_METHOD(my_conn_class, query);
}

La manipulación de la tabla de funciones de conexión debe hacerse durante la Inicialización de Módulos (MINIT). La tabla de funciones es un recurso global compartido. En un entorno multihilo, con un TSRM construido, la manipulación de un recurso global compartido durante el proceso de peticiones resultará en conflictos casi con toda seguridad.

Nota:

No utilice cualquier lógica de tamaña fijo al manipular la tablas de funciones de mysqlnd: los métodos nuevos se pueden añadir al final de la tabla de funciones. La tabla de funciones puede cambiar en cualquier momento en el futuro.

Llamar a métodos padre

Si las entradas de la tabla de funciones original está copiada, aún es posible llamar a las entradas de la tabla de funciones original - los métodos padre.

En algunos casos, como en Connection::stmt_init(), es vital llamar al método padre antes de realizar cualquier otra actividad en el método derivado.

MYSQLND_METHOD(my_conn_class, query)(MYSQLND *conn,
  const char *query, unsigned int query_len TSRMLS_DC) {

  php_printf("my_conn_class::query(query = %s)\n", query);

  query = "SELECT 'query rewritten' FROM DUAL";
  query_len = strlen(query);

  return org_methods.query(conn, query, query_len); /* retorno con llamada al padre */
}

Extender propiedades

Un objeto mysqlnd está representado por una estrucutra de C. No es posible añadir un miembro a una estructura de C en tiempo de ejecución. Los usuarios de objetos mysqlnd no pueden simplemente añadir propiedades a los objetos.

Se pueden añadir datos arbitrarios (propiedades) a objetos mysqlnd usando la función apropiada de la familia de mysqlnd_plugin_get_plugin_<object>_data(). Cuando se asigna un objeto, mysqlnd reserva espacio al final del objeto para que contenga un puntero void * a datos arbitrarios. mysqlnd reserva espacio para un puntero void * por complemento.

La siguiente tabla muestra cómo calcular la posición del puntero para un complemento específico:

Cálculos de puntero para mysqlnd
Dirección de memoria Contenido
0 Inicio de la estructura de C del objeto mysqlnd
n Fin de la estructura de C del objeto mysqlnd
n + (m x sizeof(void*)) void* a los datos del objeot del complemento m-ésimo

Si se planea usar cualquier constructor de objetos mysqlnd en subclases, lo cual está permitido, ¡se debe tener esto en cuenta!

El siguiente código muestra la extensión de propiedades:

/* cualquier dato que queramos asociar */
typedef struct my_conn_properties {
  unsigned long query_counter;
} MY_CONN_PROPERTIES;

/* id del complemento */
unsigned int my_plugin_id;

void minit_register_hooks(TSRMLS_D) {
  /* obtener un ID único para el complemento */
  my_plugin_id = mysqlnd_plugin_register();
  /* recorte - véase Extender conexiones: métodos */
}

static MY_CONN_PROPERTIES** get_conn_properties(const MYSQLND *conn TSRMLS_DC) {
  MY_CONN_PROPERTIES** props;
  props = (MY_CONN_PROPERTIES**)mysqlnd_plugin_get_plugin_connection_data(
    conn, my_plugin_id);
  if (!props || !(*props)) {
    *props = mnd_pecalloc(1, sizeof(MY_CONN_PROPERTIES), conn->persistent);
    (*props)->query_counter = 0;
  }
  return props;
}

El desarrollador del complemento es responsable de la gestión de memoria de lo datos del complemento.

Se recomienda el uso del asignador de memoria de mysqlnd para los datos del complemento. Estas funciones son nombradas usando la convención: mnd_*loc(). El asignador de mysqlnd tiene algunas características útiles, como la capacidad de usar un asignador de depuración en una construcción de no depuración.

Cuándo y cómo usar subclases
  ¿Cuándo usar subclases? ¿Cada instancia tiene su propia tabla de funciones privada? ¿Cómo usar subclases?
Conexión (MYSQLND) MINIT No mysqlnd_conn_get_methods()
Conjunto de resultados (MYSQLND_RES) MINIT o después mysqlnd_result_get_methods() o manipulación de la tabla de funciones de métodos de objetos
Metadatos de conjunto de resultados (MYSQLND_RES_METADATA) MINIT No mysqlnd_result_metadata_get_methods()
Sentencia (MYSQLND_STMT) MINIT No mysqlnd_stmt_get_methods()
Red (MYSQLND_NET) MINIT o después mysqlnd_net_get_methods() o manipulación de la tabla de funciones de métodos de objetos
Protocolo de cable (MYSQLND_PROTOCOL) MINIT o después mysqlnd_protocol_get_methods() o manipulación de la tabla de funciones de métodos de objetos

No se deben manipular las tablas de funciones en ningún momento posterior a MINIT si no está permitido según la tabla de arriba.

Algunas clases contienen un puntero a la tabla de funciones de métodos. Todas las instancias de esas clases compartirán la misma tabla de funciones. Para evitar el caos, en particular en entornos de hilos, tales tablas de funciones deben ser manipuladas solamente durante MINIT.

Otras clases usan copias de una tabla de funciones compartida globalmente. La copia de la tabla de funciones de la clase se crea junto con el objeto. Cada objeto usa su propia tabla de funciones. Esto proporciona dos opciones: se puede manipular la tabla de funciones predetermiada de un objeto durante MINIT, y además se pueden perfeccionar métodos de un objeto sin impactar otras instancias de la misma clase.

La ventaja del enfoque de la tabla de funciones compartida es el rendimiento. No hay necesidad de copiar una tabla de funciones para cada objeto.

Estado del constructor
  Asignación, construcción, reinicio ¿Se puede modificar? Llamador
Conexión (MYSQLND) mysqlnd_init() No mysqlnd_connect()
Conjunto de resultados (MYSQLND_RES)

Asignación:

  • Connection::result_init()

Reinicio y reinicialización durante:

  • Result::use_result()

  • Result::store_result

Sí, ¡pero se ha de llamar al padre!
  • Connection::list_fields()

  • Statement::get_result()

  • Statement::prepare() (Solamente metadatos)

  • Statement::resultMetaData()

Metadatos de conjunto de resultados (MYSQLND_RES_METADATA) Connection::result_meta_init() Sí, ¡pero se ha de llamar al padre! Result::read_result_metadata()
Sentecia (MYSQLND_STMT) Connection::stmt_init() Sí, ¡pero se ha de llamar al padre! Connection::stmt_init()
Red (MYSQLND_NET) mysqlnd_net_init() No Connection::init()
Protocolo de cable (MYSQLND_PROTOCOL) mysqlnd_protocol_init() No Connection::init()

Se recomienda encarecidamente que no se reemplace completamente un constructor. Los constructores realizan asignaciones de memoria. Las asignaciones de memoria son vitales para la API de complementos de mysqlnd y la lógida de objetos de mysqlnd. Si no se tiene cuidado con las advertencias y se insiste en enganchar los constructores, se debería, al menos, llamar al constructor padre antes de hacer nada en el constructor derivado.

A pesar de todas las advertencias, puede ser útil usar subclases para los constructores. Éstos son el lugar perfecto para modificar las tablas de funciones de objetos con tablas de objetos no compartidas, como Conjunto de resultados, Red, Protocolo de cable.

Estado de la destrucción
  ¿Debe el método derivado llamar al padre? Destructor
Conexión sí, después de la ejecución del método free_contents(), end_psession()
Conjunto de resultados sí, después de la ejecución del método free_result()
Metadatos del conjunto de resultados sí, después de la ejecución del método free()
Sentencia sí, después de la ejecución del método dtor(), free_stmt_content()
Red sí, después de la ejecución del método free()
Protocolo de cable sí, después de la ejecución del método free()

Los destructores son los lugares apropiados para liberar propiedades mysqlnd_plugin_get_plugin_<objeto>_data().

Los destructores listados podrían no ser equivalentes a los métodos reales de mysqlnd que liberan el objeto en sí. Sin embargo, son el mejor lugar posible para enganchar y liberar los datos de los complementos. Como sucede con los constructores se pueden reemplazar los métodos completamente, pero no se recomienda. Si se listan múltiles métodos en la tabla de arriba, se necesitará enganchar todos esos métodos y liberar los datos del complemento en el método correspondiente que sea llamado primero por mysqlnd.

El método recomendado para los complementos es simplemente enganchar los métodos, liberar la memoria y llamar a la implementación padre inmediatamente después de esto.

Precaución

Debido a un error en las versiones de PHP 5.3.0 hasta 5.3.3, los complementos no asocian los datos de los complementos con una conexión persistente. Esto es debido a que ext/mysql y ext/mysqli no desencadenan las llamadas necesarias al método end_psession() de mysqlnd, y el complemento podría, por lo tanto, perder memoria. Esto ha sido corregido en PHP 5.3.4.