Esta sección proporciona una visión general de la arquitectura del plugin
mysqlnd
.
Visión general del controlador nativo MySQL
Antes de desarrollar plugins mysqlnd
,
es útil tener un conocimiento mínimo sobre la organización
de mysqlnd
. Mysqlnd
está compuesto
por los siguientes módulos:
Módulos de estadísticas | mysqlnd_statistics.c |
---|---|
Conexión | mysqlnd.c |
Juego de resultados | mysqlnd_result.c |
Datos méta del juego de resultados | mysqlnd_result_meta.c |
Consulta | mysqlnd_ps.c |
Red | mysqlnd_net.c |
Capa física | mysqlnd_wireprotocol.c |
Objeto C orientado a paradigma
A nivel de código, mysqlnd
utiliza una máscara C
para implementar la orientación al objeto.
En C, se utiliza una estructura (struct
)
para representar un objeto. Los miembros de esta estructura
representan las propiedades del objeto. Los miembros de la
estructura que apuntan hacia funciones representan los métodos.
A diferencia de otros lenguajes como C++ o Java, no hay reglas fijas sobre la herencia en los objetos C orientados a paradigma. Sin embargo, hay algunas convenciones que deben seguirse que serán abordadas posteriormente.
El ciclo de vida PHP
El ciclo de vida de PHP consta de 2 ciclos básicos:
El ciclo de inicio y parada del motor PHP
El ciclo de una petición
Cuando el motor PHP se inicia, llama a la función de inicialización del módulo (MINIT) de cada extensión registrada. Esto permite a cada módulo definir las variables y asignar los recursos que deben existir durante la vida del proceso correspondiente al motor PHP. Cuando el motor PHP se detiene, llama a la función de parada del módulo (MSHUTDOWN) para cada extensión.
Durante la vida del motor PHP, recibirá peticiones. Cada petición constituye otro ciclo de vida. Para cada petición, el motor PHP llamará a la función de inicialización de cada extensión. La extensión puede realizar todas las definiciones de variables así como las asignaciones de recursos necesarias para procesar la petición. Cuando el ciclo de la petición termina, el motor llama a la función de parada (RSHUTDOWN) para cada extensión, por lo que la extensión puede lanzar toda la limpieza necesaria.
Cómo funciona un plugin
Un plugin mysqlnd
funciona interceptando las llamadas
realizadas a mysqlnd
por las extensiones que utilizan
mysqlnd
. Esto es posible obteniendo la tabla
de función mysqlnd
, guardándola, y reemplazándola por una tabla
de función personalizada, que llama a las funciones del plugin.
El código siguiente muestra cómo se reemplaza la tabla de función
mysqlnd
:
/* un lugar para almacenar la tabla de función original */ struct st_mysqlnd_conn_methods org_methods; void minit_register_hooks(TSRMLS_D) { /* tabla de función activa */ struct st_mysqlnd_conn_methods * current_methods = mysqlnd_conn_get_methods(); /* guardar la tabla de función original */ memcpy(&org_methods, current_methods, sizeof(struct st_mysqlnd_conn_methods)); /* instalación de las nuevas métodos */ current_methods->query = MYSQLND_METHOD(my_conn_class, query); }
Las manipulaciones de la tabla de función de conexión deben realizarse durante la inicialización del módulo (MINIT). La tabla de función es un recurso global compartido. En un entorno multihilo, con una compilación TSRM, la manipulación de un recurso global compartido durante un proceso de petición generalmente resultará en conflictos.
Nota:
No utilice ninguna lógica de tamaño fijo al manipular la tabla de función
mysqlnd
: las nuevas funciones pueden ser añadidas al final de la tabla de función. La tabla de función puede ser modificada en cualquier momento después.
Llamada a métodos padres
Si la tabla de función original se guarda, siempre es posible llamar a las entradas de la tabla de función original - los métodos padres.
En este caso, al igual que Connection::stmt_init()
,
es vital llamar al método padre antes de 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 estructura C.
No es posible añadir un miembro a una estructura C en tiempo de ejecución.
Los usuarios de objetos mysqlnd
no pueden simplemente añadir propiedades a los objetos.
Datos arbitrarios (propiedades) pueden ser añadidos a los objetos
mysqlnd
utilizando una función apropiada de la
familia mysqlnd_plugin_get_plugin_<object>_data()
.
Durante la asignación de un objeto, mysqlnd
reserva
un espacio al final del objeto para alojar un puntero
void *
hacia datos arbitrarios.
mysqlnd
reserva un espacio para un puntero
void *
por plugin.
La tabla siguiente muestra cómo calcular la posición de un puntero para un plugin específico:
Dirección de memoria | Contenido |
---|---|
0 | Inicio de la estructura C del objeto mysqlnd |
n | Fin de la estructura C del objeto mysqlnd |
n + (m x sizeof(void*)) | void* hacia los datos del objeto del plugin m-ésimo |
Si planea hacer subclases de los constructores
de los objetos mysqlnd
, lo cual está permitido,
debe tener esto en cuenta.
El código siguiente muestra cómo se extienden propiedades:
/* todos los datos que queremos asociar */ typedef struct my_conn_properties { unsigned long query_counter; } MY_CONN_PROPERTIES; /* id del plugin */ unsigned int my_plugin_id; void minit_register_hooks(TSRMLS_D) { /* obtenemos un ID único para el plugin */ my_plugin_id = mysqlnd_plugin_register(); /* snip - ver la extensión de la conexión: 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 plugin es responsable de la gestión de la memoria asociada a los datos del plugin.
Se recomienda el uso del asignador de memoria mysqlnd
para los datos del plugin. Estas funciones son nombradas
utilizando la siguiente convención: mnd_*loc()
.
El asignador mysqlnd
tiene algunas características muy útiles,
como la posibilidad de utilizar un asignador de depuración en una compilación
no depurada.
¿Cuándo hacer una subclase? | ¿Cada instancia tiene su propia tabla de funciones privada? | ¿Cómo hacer una subclase? | |
---|---|---|---|
Conexión (MYSQLND) | MINIT | No | mysqlnd_conn_get_methods() |
Juego de resultados (MYSQLND_RES) | MINIT o después | Sí | mysqlnd_result_get_methods() o método del objeto de manipulación de la tabla de funciones |
Meta del juego de resultados (MYSQLND_RES_METADATA) | MINIT | No | mysqlnd_result_metadata_get_methods() |
Consulta (MYSQLND_STMT) | MINIT | No | mysqlnd_stmt_get_methods() |
Red (MYSQLND_NET) | MINIT o después | Sí | mysqlnd_net_get_methods() o método del objeto de manipulación de la tabla de funciones |
Capa física (MYSQLND_PROTOCOL) | MINIT o después | Sí | mysqlnd_protocol_get_methods() o método del objeto de manipulación de la tabla de funciones |
No debe manipular las tablas de funciones después de MINIT si no está permitido según la tabla anterior.
Algunas clases contienen un puntero hacia un método de la tabla de funciones. Todas las instancias de una clase de este tipo compartirán la misma tabla de funciones. Para evitar el caos, en particular en entornos multihilo, este tipo de tablas de funciones solo debe ser manipulado durante el MINIT.
Las otras clases utilizan una copia de la tabla de funciones global compartida. Esta copia se crea al mismo tiempo que el objeto. Cada objeto utiliza su propia tabla de funciones. Esto le da 2 opciones: puede manipular la tabla de funciones por defecto de un objeto durante el MINIT, y también puede afinar métodos de un objeto sin afectar a las otras instancias de la misma clase.
La ventaja del enfoque con una tabla de funciones compartida es el rendimiento. No es necesario copiar una tabla de funciones para cada objeto.
Tipo | Asignación, construcción, reinicialización | ¿Puede ser modificado? | Llamante |
---|---|---|---|
Conexión (MYSQLND) | mysqlnd_init() | No | mysqlnd_connect() |
Juego de resultados (MYSQLND_RES) | Asignación:
Reinicio y reinicialización durante:
|
Sí, pero llamada al padre. |
|
Meta del juego de resultados (MYSQLND_RES_METADATA) | Connection::result_meta_init() | Sí, pero llamada al padre. | Result::read_result_metadata() |
Consulta (MYSQLND_STMT) | Connection::stmt_init() | Sí, pero llamada al padre. | Connection::stmt_init() |
Red (MYSQLND_NET) | mysqlnd_net_init() | No | Connection::init() |
Capa física (MYSQLND_PROTOCOL) | mysqlnd_protocol_init() | No | Connection::init() |
Se recomienda encarecidamente no reemplazar completamente un constructor.
Los constructores realizan asignaciones de memoria. Las asignaciones
de memoria son vitales para la API del plugin mysqlnd
así como para la lógica del objeto mysqlnd
. Si
no se preocupa por las advertencias e insiste en
reemplazar los constructores, debería al menos llamar
al constructor padre antes de hacer cualquier cosa en su
constructor.
A nivel de todas las advertencias, puede ser útil hacer subclases de los constructores. Los constructores son los lugares perfectos para modificar las tablas de funciones de los objetos con tablas de objetos no compartidas, como los juegos de resultados, la red o la capa física.
Tipo | ¿El método derivado debe llamar al padre? | Destructor |
---|---|---|
Conexión | Sí, después de la ejecución del método | free_contents(), end_psession() |
Juego de resultados | Sí, después de la ejecución del método | free_result() |
Meta del juego de resultados | Sí, después de la ejecución del método | free() |
Consulta | 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() |
Capa física | Sí, después de la ejecución del método | free() |
Los destructores son los lugares perfectos para liberar propiedades,
mysqlnd_plugin_get_plugin_<object>_data()
.
Los destructores listados pueden no ser los equivalentes a
los métodos actuales mysqlnd
que liberan el objeto mismo.
Sin embargo, son los mejores lugares para liberar
los datos de su plugin. Al igual que los constructores, puede reemplazar
métodos completos pero no se recomienda. Si varios métodos están listados
en la tabla anterior, debe modificar todos los métodos listados y liberar
los datos de su plugin en el método llamado primero por mysqlnd
.
El método recomendado para los plugins es simplemente modificar los métodos, liberar su memoria y llamar a la implementación del padre inmediatamente después.