Arquitetura do Plugin do Driver Nativo MySQL

Esta seção fornece uma visão geral da arquitetura do plugin mysqlnd.

Visão Geral do Driver Nativo MySQL

Antes de desenvolver plugins mysqlnd, é útil saber um pouco de como o próprio mysqlnd é organizado. Mysqlnd consiste nos seguintes módulos:

O organograma mysqlnd, por módulo
Estatísticas de Módulos mysqlnd_statistics.c
Conexão mysqlnd.c
Conjunto de resultados mysqlnd_result.c
Metadados do conjunto de resultados mysqlnd_result_meta.c
Declaração mysqlnd_ps.c
Rede mysqlnd_net.c
Protocolo de fio mysqlnd_wireprotocol.c

Paradigma de C Orientado a Objetos

No nível do código, mysqlnd usa um padrão C para implementar orientação a objetos.

Em C você usa uma struct para representar um objeto. Os membros da estrutura representam propriedades do objeto. Membros da estrutura que apontam para funções representam métodos.

Ao contrário de outras linguagens como C++ ou Java, não existem regras fixas sobre herança no paradigma do C orientado a objetos. No entanto, existem algumas convenções que precisam ser seguidas e que serão discutidas posteriormente.

O Ciclo de Vida do PHP

Ao considerar o ciclo de vida do PHP, existem dois ciclos básicos:

  • Ciclo de inicialização e desligamento do mecanismo PHP

  • Ciclo de requisição

Quando o mecanismo PHP for iniciado ele chamará a função de inicialização do módulo (MINIT) de cada extensão registrada. Isso permite que cada módulo configure variáveis ​​e aloque recursos que existirão durante a vida útil do processo do mecanismo PHP. Quando o mecanismo PHP for desligado, ele chamará a função de desligamento do módulo (MSHUTDOWN) de cada extensão.

Durante a vida útil do mecanismo PHP, ele receberá diversas solicitações. Cada solicitação constitui outro ciclo de vida. Em cada solicitação, o mecanismo PHP chamará a função de inicialização de solicitação de cada extensão. A extensão pode executar qualquer configuração de variável e alocação de recursos necessária para o processamento de solicitações. À medida que o ciclo de solicitação termina, o mecanismo chama a função de desligamento de solicitação (RSHUTDOWN) de cada extensão para que ela possa realizar qualquer limpeza necessária.

Como um plugin funciona

Um plugin mysqlnd funciona interceptando chamadas feitas para mysqlnd por extensões que usam mysqlnd. Isto é conseguido obtendo a tabela de funções mysqlnd, fazendo backup dela e substituindo-a por uma tabela de funções customizada, que chama as funções do plugin conforme necessário.

O código a seguir mostra como a tabela de funções mysqlnd é substituída:

/* um lugar para armazenar a tabela de funções original */
struct st_mysqlnd_conn_methods org_methods;

void minit_register_hooks(TSRMLS_D) {
  /* tabela de funções ativas */
  struct st_mysqlnd_conn_methods * current_methods
    = mysqlnd_conn_get_methods();

  /* tabela de funções original de backup */
  memcpy(&org_methods, current_methods,
    sizeof(struct st_mysqlnd_conn_methods);

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

As manipulações da tabela de funções de conexão devem ser feitas durante a inicialização do módulo (MINIT). A tabela de funções é um recurso global compartilhado. Em um ambiente multithread, com construção de TSRM, a manipulação de um recurso global compartilhado durante o processamento da solicitação quase certamente resultará em conflitos.

Note:

Não use nenhuma lógica de tamanho fixo ao manipular a tabela de funções mysqlnd: novos métodos podem ser adicionados ao final da tabela de funções. A tabela de funções pode mudar a qualquer momento no futuro.

Chamando métodos da classe pai

Se for feito backup das entradas originais da tabela de funções, ainda será possível chamar as entradas originais da tabela de funções - os métodos pai.

Em alguns casos, como em Connection::stmt_init(), é vital chamar o método pai antes de qualquer outra atividade no 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); /* retorna com chamada ao método pai */
}

Estendendo propriedades

Um objeto mysqlnd é representado por uma estrutura C. Não é possível adicionar um membro a uma estrutura C em tempo de execução. Usuários de objetos mysqlnd não podem simplesmente adicionar propriedades aos objetos.

Dados arbitrários (propriedades) podem ser adicionados a um objeto mysqlnd usando uma função apropriada da família mysqlnd_plugin_get_plugin_<object>_data(). Ao alocar um objeto, mysqlnd reserva espaço no final do objeto para conter um ponteiro void * para dados arbitrários. mysqlnd reserva espaço para um ponteiro void * por plugin.

A tabela a seguir mostra como calcular a posição do ponteiro para um plugin específico:

Cálculo de ponteiro para mysqlnd
Endereço de memória Conteúdo
0 Início da estrutura C do objeto mysqlnd
n Final da estrutura C do objeto mysqlnd
n + (m x sizeof(void*)) void* para dados do objeto do m-ésimo plugin

Se for planejada uma sub-classe de qualquer dos construtores de objeto mysqlnd, que é permitido, deve-se ter isto em mente!

O código a seguir mostra extensão de propriedades:

/* qualquer dado que se queira associar */
typedef struct my_conn_properties {
  unsigned long query_counter;
} MY_CONN_PROPERTIES;

/* id do plugin */
unsigned int my_plugin_id;

void minit_register_hooks(TSRMLS_D) {
  /* obtém ID único para o plugin */
  my_plugin_id = mysqlnd_plugin_register();
  /* recorte - consulte Estendendo Conexão: 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;
}

O desenvolvedor do plugin é responsável pelo gerenciamento de memória dos dados do plugin.

O uso do alocador de memória mysqlnd é recomendado para dados do plugin. Essas funções são nomeadas usando a convenção: mnd_*loc(). O alocador mysqlnd possui alguns recursos úteis, como a capacidade de usar um alocador de depuração em uma compilação sem depuração.

Quando e como usar uma sub-classe
  Quando usar uma sub-classe? Cada instância tem sua própria tabela de funções privadas? Como usar uma sub-classe
Conexão (MYSQLND) MINIT Não mysqlnd_conn_get_methods()
Conjunto de resultados (MYSQLND_RES) MINIT ou depois Sim mysqlnd_result_get_methods() ou manipulação de tabela de funções de métodos de objeto
Resultset Meta (MYSQLND_RES_METADATA) MINIT Não mysqlnd_result_metadata_get_methods()
Statement (MYSQLND_STMT) MINIT Não mysqlnd_stmt_get_methods()
Rede (MYSQLND_NET) MINIT ou depois Sim mysqlnd_net_get_methods() ou manipulação de tabela de funções de métodos de objeto
Protocolo de fio (MYSQLND_PROTOCOL) MINIT ou depois Sim mysqlnd_protocol_get_methods() ou manipulação de tabela de funções de métodos de objeto

Não se deve manipular tabelas de funções em nenhum momento posterior ao MINIT se isso não for permitido de acordo com a tabela acima.

Algumas classes contêm um ponteiro para a tabela de funções do método. Todas as instâncias dessa classe compartilharão a mesma tabela de funções. Para evitar o caos, especialmente em ambientes com threads, tais tabelas de funções só devem ser manipuladas durante o MINIT.

Outras classes usam cópias de uma tabela de funções compartilhada globalmente. A cópia da tabela de funções de classe é criada junto com o objeto. Cada objeto usa sua própria tabela de funções. Isso dá duas opções: pode-se manipular a tabela de funções padrão de um objeto no MINIT, e também pode-se refinar os métodos de um objeto sem afetar outras instâncias da mesma classe.

A vantagem da abordagem de tabela de funções compartilhadas é o desempenho. Não há necessidade de copiar uma tabela de funções para cada objeto.

Estado do construtor
Tipo Alocação, construção, redefinição Pode ser modificada? Chamadora
Conexão (MYSQLND) mysqlnd_init() Não mysqlnd_connect()
Resultset(MYSQLND_RES)

Alocação:

  • Connection::result_init()

Redefinida e reinicializada durante:

  • Result::use_result()

  • Result::store_result

Sim, mas deve-se chamar o método pai!
  • Connection::list_fields()

  • Statement::get_result()

  • Statement::prepare() (Somente metadados)

  • Statement::resultMetaData()

Metadados do conjunto de resultados (MYSQLND_RES_METADATA) Connection::result_meta_init() Sim, mas deve-se chamar o método pai! Result::read_result_metadata()
Instrução (MYSQLND_STMT) Connection::stmt_init() Sim, mas deve-se chamar o método pai! Connection::stmt_init()
Rede (MYSQLND_NET) mysqlnd_net_init() Não Connection::init()
Protocolo de fio (MYSQLND_PROTOCOL) mysqlnd_protocol_init() Não Connection::init()

É altamente recomendável que não se substitua totalmente um construtor. Os construtores realizam alocações de memória. As alocações de memória são vitais para a API do plugin mysqlnd e para a lógica do objeto mysqlnd. Se não houver preocupação com avisos e houver insistência em conectar os construtores, deve-se pelo menos chamar o construtor pai antes de fazer qualquer coisa no construtor.

Independentemente de todos os avisos, pode ser útil criar sub-classes de construtores. Os construtores são o lugar perfeito para modificar as tabelas de funções de objetos com tabelas de objetos não compartilhados, como Conjunto de Resultados, Rede, Protocolo de Fio.

Estado do destruidor
Tipo Método derivado deve chamar o pai? Destruidor
Conexão sim, após execução do método free_contents(), end_psession()
Conjunto de resultados sim, após execução do método free_result()
Metadados do conjunto de resultados sim, após execução do método free()
Instrução sim, após execução do método dtor(), free_stmt_content()
Rede sim, após execução do método free()
Protocolo de fio sim, após execução do método free()

Os destruidores são o local apropriado para liberar propriedades, mysqlnd_plugin_get_plugin_<object>_data().

Os destruidores listados podem não ser equivalentes ao método mysqlnd real que libera o próprio objeto. No entanto, eles são o melhor lugar possível para se conectar e liberar os dados do plugin. Tal como acontece com os construtores, pode-se substituir totalmente os métodos, mas isso não é recomendado. Se vários métodos estiverem listados na tabela acima, será necessário conectar todos os métodos listados e liberar os dados do plugin em qualquer método chamado primeiro pelo mysqlnd.

O método recomendado para plugins é simplesmente conectar os métodos, liberar memória e chamar a implementação pai imediatamente após isso.