Detalhando o ciclo de vida de uma extensão PHP - Parte 03

No post anterior, eu expliquei a estrutura de carregamento de uma extensão, e o principal meio de configura-la, usando zend_module_entry.

Nesse post eu iria começar falando sobre funções e como registra-las, mas achei muito importante falar sobre o ciclo de vida de uma extensão. Eu não ia detalhar tanto assim, por que sempre desenvolvo minhas extensões para CLI, mas já que estou escrevendo, é certo que nem todos farão com o mesmo propósito.

Ciclo de vida, é entender melhor quando uma extensão é registrada, quando ela é finalizada, quando é instanciada quando é destruída, por exemplo. E à primeira vista, isso não faz diferença alguma, mas uma extensão pode ser carregada em 3 ambientes muito diferentes:
  • CLI: Nesse modo, que executa scripts via linha de comando. Nesse caso, o PHP é instanciado, chamando a extensão, executando, finalizando a extensão, e destruindo a instância. Existe só um request, então é tudo carregado sequencialmente. Esse é o meio mais simples de entender;
  • FastCGI (PHP-CGI, PHP-FPM, etc): Esse modo é exatamente igual ao CLI, porem como o apache (nigx, etc) ficam esperando um processo, não tem o por que a cada momento instanciar um PHP novo (de grosso modo, pois de tempo em tempo, existe um processo que trata tudo isso, então não leve essa interpretação ao pé da letra se você sabe como funciona corretamente). O PHP já está carregado junto ao servidor, e por isso o torna mais rápido. Nesse caso, tempos que saber onde instanciar as nossas configurações globais e onde as locais, já que ganchos de request não compartilham informações. Isso vai ficar mais claro daqui a pouco;
  • E finalmente temos o modelo thead-bases. onde o processo principal vai criando threads à cada request;
Tá, mas por que vc precisa saber disso? por que não é você quem escolhe onde sua extensão vai rodar. Se for Window é de uma forma, se for Linux outra, se for CLI outra, e sua extensão precisa suportar estes ambientes.

Então os ganchos são separados da seguinte forma
zend_module_entry myext_module_entry = {
 STANDARD_MODULE_HEADER, // Campos size, zend_api, zend_debug, zts, ini_entry e deps
 "myext", // Nome da extensão
 ext_functions, // Funções da nossa extensão

 MINIT(), // Função executada ao iniciar a extensão
 MSHUTDOWN(), // Função executada ao finalizar a extensão
 RINIT(), // Função executada ao iniciar o processamento
 RSHUTDOWN(), // Função executada ao finalizar o processamento

 PHP_MINFO(myext), // Função executada quando alguem pede informações da extensão
 "0.0.1", // Versão da extensão
 STANDARD_MODULE_PROPERTIES // Campos globais e de controle interno
};
inicialização do modulo, MINIT()

Na sua extensão, o modulo MINIT() será chamado antes de qualquer thread ou processo ser chamado, então você consegue ter acesso à todas as informações globais sem nenhuma proteção ainda. Nesse ponto, não houve request nenhuma ainda. Ou seja, não tem nenhum script pra ser executado. Geralmente se configura objetos, resources e qualquer outro recurso read-only aqui, e todos os requests terão acesso á eles.

Nesse momento, é muito comum registrar entradas de configuração INI da sua extensão.

finalização do modulo, MSHUTDOWN()

Esse gancho, é quando seu modulo é descarregado. Você precisa destruir e liberar memória resources, objetos, remover registros de configuração do INI, tudo que fez no MINIT().

Nesse ponto, o core já está desligado, então você já não tem mais acesso à variáveis de execução.

inicialização do request: RINIT()

Aqui sua thread ou processo foi lançado, ainda não está em execução, mas ja está alocado. Tudo que foi executado aqui é exclusivamente acessível por essa thread/processo. Caso queira registrar ou modificar algo global, lembre-se de que outras threads/processos estão usando a mesma informação. Por isso é muito recomendável o uso de recursos read-only no global.

Outra coisa importante, é que aqui, tudo é acumulativo. Se você começar a alocar memória e não liberar ela após o request, a cara request isso só vai aumentando até que seu servidor trave

finalização do request: RSHUTDOWN()

O mesmo se aplica ao
MSHITDOWN()
. No
RSHUTDOWN()
, você deve liberar tudo que criou no
RINIT()
, mesmo que outros processos estejam em processamento ou possa iniciar um novo processamento. Nesse ponto, a thread/processo ainda não finalizou, mas todo script PHP ja foi executa, ja foi enviado o buffer para o cliente, e nada mais do phpland pode ser executado.

Outros ganchos interessantes
zend_module_entry myext_module_entry = {
 STANDARD_MODULE_HEADER, // Campos size, zend_api, zend_debug, zts, ini_entry e deps
 "myext", // Nome da extensão
 ext_functions, // Funções da nossa extensão

 MINIT(), // Função executada ao iniciar a extensão
 MSHUTDOWN(), // Função executada ao finalizar a extensão
 RINIT(), // Função executada ao iniciar o processamento
 RSHUTDOWN(), // Função executada ao finalizar o processamento

 PHP_MINFO(myext), // Função executada quando alguem pede informações da extensão

 "0.0.1", // Versão da extensão
 PHP_MODULE_GLOBALS, // Macro para o tamanho da estrutura globais

 GINIT(), // Inicializa os globais
 GSHUTDOWN(), // Finaliza os globais,
 PRSHUTDOWN(), // Função executada após a finalização do request

 STANDARD_MODULE_PROPERTIES_EX // Campos globais e de controle interno
};
O STANDARD_MODULE_PROPERTIES engloba mais 3 ganchos para organização global. O menos comum de se usar é o
PRSHUTDOWN()
, raramente usado, chamado post-RSHUTDOWN(). Também tempos o
GINIT()
e o
GSHUTDOWN()
, que são os melhores locais para inicial recursos globais.

Além destes ganchos, que podem complicar bastante, temos um muito importante, mas de extrema simplicidade, o
MINFO()
. Esse gancho é chamado sempre que algo necessita de informações sobre sua extensão. Ele nunca é chamado automaticamente pelo core, mas sim, sempre que alguém chama-lo pelo phpland, como por exemplo via
phpinfo()
ou
ini_get_all()
.

Trabalhar com alocação de memória não é uma coisa trivial, e alguns dados precisam de tratamento especial, como por exemplo usar emalloc ao invés de pemalloc, pode travar tudo seu PHP. Mas vou deixar um post sobre Zend Memory Manager em separado, para não complicar mais ainda.

O código ficaria algo assim:
/**
 * Inclui as libs necessárias
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "ext/standard/info.h"


/**
 * Declarações globais
 */
ZEND_BEGIN_MODULE_GLOBALS(myext)

// Minhas variaveis globais
ZEND_END_MODULE_GLOBALS(myext)

ZEND_DECLARE_MODULE_GLOBALS(myext)

/**
 * Inicialização dos globais
 */
PHP_GINIT_FUNCTION(myext)
{
 ZEND_SECURE_ZERO(myext_globals, sizeof(*myext_globals));
}

/**
 * Função chamada quando o processamento é iniciado
 */
PHP_RINIT_FUNCTION(myext)
{
 return SUCCESS;
}

/**
 * Função executada ao iniciar a extensão
 */
PHP_MINIT_FUNCTION(myext)
{
 return SUCCESS;
}

/**
 * Função executada ao finalizar a extensão
 */
PHP_MSHUTDOWN_FUNCTION(myext)
{
 return SUCCESS;
}

/**
 * Função executada ao finalizar o processamento
 */
PHP_RSHUTDOWN_FUNCTION(myext)
{
 return SUCCESS;
}

/**
 * Finalização dos globais
 */
PHP_GSHUTDOWN_FUNCTION(myext)
{
}

/**
 * Função chamada quando alguem pede alguma informação da extensão
 */
PHP_MINFO_FUNCTION(myext)
{
 php_info_print_table_start();
 php_info_print_table_header(2, "myext support", "enabled");
 php_info_print_table_end();
}

// Registra as funções para o phpland (mundo php)
static const zend_function_entry ext_functions[] = {
 ZEND_FE_END
};

/**
 * Configuração da nossa extensão
 */
extern zend_module_entry myext_module_entry;
zend_module_entry myext_module_entry = {
 STANDARD_MODULE_HEADER, // Campos size, zend_api, zend_debug, zts, ini_entry e deps
 "myext", // Nome da extensão
 ext_functions, // Funções da nossa extensão
 PHP_MINIT(myext), // Função executada ao iniciar a extensão
 PHP_MSHUTDOWN(myext), // Função executada ao finalizar a extensão
 PHP_RINIT(myext), // Função executada ao iniciar o processamento
 PHP_RSHUTDOWN(myext), // Função executada ao finalizar o processamento
 PHP_MINFO(myext), // Função executada quando alguem pede informações da extensão

 "0.0.1", // Versão da extensão

 PHP_MODULE_GLOBALS(myext), // Macro para o tamanho da estrutura globais
 PHP_GINIT(myext), // Inicializa os globais
 PHP_GSHUTDOWN(myext), // Finaliza os globais,

 NULL, // Função executada após-finalização da finalização do request
 STANDARD_MODULE_PROPERTIES_EX // Campos globais e de controle interno
};

/**
 * Simbolo chamado pelo core
 */
ZEND_GET_MODULE(myext)
Apesar de não precisar colocar todos os ganchos, achei interessante colocar nesse exemplo. Nos próximos exemplos, só usaremos os ganchos que for necessário. O restante, só preencher com
NULL
, que serão ignorados

Até acho que ficou bem claro como funciona os ganchos e o que é importante e o que não é. No geral, registra no
RINIT
, destrói no
RSHUTDOWN
. No próximo post, espero começar a falar sobre as funções, que é quando a coisa começa a ficar divertida. Obrigado até aqui!

Pessoal, gostaria de deixar um apelo ai para darem uma força. compartilhem, comentem, deixe seu comentário no post. quem escreve ou faz vídeo, muitas vezes nem faz isso por dinheiro, mas sim para engajar a comunidade de alguma forma. não gostou da forma que eu escrevo? comenta na boa (nem eu gosto também), mas comenta algo, manda pro brodi, bora crescer e centralizar um conteúdo de qualidade

Referencias: https://www.php.net/manual/pt_BR/internals2.structure.modstruct.php, http://www.phpinternalsbook.com/index.html