Como tornar a extensão no PHP7 mais difícil do que "olá, mundo" e não ficar com os olhos vermelhos. Parte 1

Porque


Estou escrevendo este artigo para que, no caminho que segui por pelo menos um ano, o leitor possa percorrer algumas horas. Como minha experiência pessoal mostrou, simplesmente programar em C é um pouco mais fácil do que fazer uma extensão séria para o trabalho do PHP. Aqui, mostrarei o máximo possível sobre como fazer uma extensão usando o exemplo da biblioteca libtrie que implementa a árvore de prefixos, mais conhecida como trie. Escreverei e executarei simultaneamente as ações descritas em um sistema Lubuntu 18.04 recém-instalado.

Vamos começar.

Instalação de software


Php


  1. Primeiro, colocamos o pacote php7.2-dev, nele o script phpize necessário para construir a extensão. Além disso, precisaremos de uma versão funcional do php, na qual verificaremos nossa extensão. A instalação deste pacote exibirá vários pacotes dependentes. Colocamos tudo o que é oferecido.

    sudo apt install php7.2-dev 

  2. Vamos ao site php.net, à seção de downloads e acessamos o arquivo com a versão estável mais recente do php, agora é a versão 7.2.11.
    Faça o download do arquivo fonte do php:

     cd /tmp && wget http://it2.php.net/get/php-7.2.11.tar.gz/from/this/mirror -O php7.tar.gz 

  3. Agora descompacte o arquivo para nós mesmos:

     sudo tar -xvf php7.tar.gz -C /usr/local/src 


Editores de código


Eu costumo usar 2 editores de código. Clean simples e rápido e bastante nerd, mas muito avançado do JetBrains. Geany instalar a partir do nabo padrão Ubuntu.

 sudo apt install geany 

Faça o download do Clion no site oficial do JetBrains:

 cd ~/Downloads && wget https://download.jetbrains.com/cpp/CLion-2018.2.5.tar.gz -O clion.tar.gz 

 sudo tar -xvf clion.tar.gz -C /usr/share 

Vamos criar um link para facilitar a execução do clion a partir do console.

 sudo ln -s /usr/share/clion-2018.2.5/bin/clion.sh /usr/bin/clion 

Após o primeiro lançamento, o próprio clion criará atalhos para si no menu de shell do LXpanel, mas na primeira vez em que você precisar executá-lo manualmente.

 # clion 

Criar extensão


Aqui temos pelo menos 3 opções:

  1. Pegue o disco padrão bruto das fontes php que baixamos.
  2. Vi o disco padrão um pouco com um script ext_skel especial
  3. Obtenha um bom disco minimalista a partir daqui .

Eu gosto mais da terceira opção, mas usarei a segunda, em caso de falha, para minimizar o número de lugares em que posso estar errado. Embora escolher um espaço em branco dos desenvolvedores ainda seja um prazer :-)

  1. Vamos para o diretório com extensões php padrão.

     cd /usr/local/src/php-7.2.11/ext 

    Além do nome do script, você pode especificar alguns parâmetros de extensão através do arquivo proto. Tudo isso não pode ser feito. Farei tudo manualmente, mas mostrarei como o proto funciona. Nós tentamos, então vamos nomear nossa extensão libtrie. Para trabalhar no diretório / usr / local / src, você precisa de privilégios de administrador, para não acabar escrevendo o sudo, incluirei o bash com privilégios elevados.

     sudo bash 

  2. Aqui vou definir os parâmetros de apenas 1 função que a extensão criada implementará. Esta é apenas uma função de demonstração para mostrar como isso é feito.

    Faremos um análogo completo da função padrão

     array array_fill ( int $start_index , int $num , mixed $value ) 

    A sintaxe no arquivo proto é muito simples, basta especificar o nome da função. Vou escrever um pouco mais para parecer mais informativo.

     echo my_array_fill \( int start_index , int num , mixed value \) >> libtrie.proto 

  3. Agora execute o script ext_skel, fornecendo o nome da extensão e o arquivo proto que criamos.

     ./ext_skel --extname=libtrie --proto=./libtrie.proto 

  4. Criamos um diretório com nossa extensão. Vamos entrar nisso.

     cd libtrie 


Estrutura do arquivo e princípio de montagem


Estrutura de arquivo
 config.m4 -           phpize   ./configure,       makefile. CREDITS -  ,     ,    libtrie.c -      php_libtrie.h -     config.w32 -        windows EXPERIMENTAL -  .    ,    . libtrie.php -  php      . tests -   


Para criar a extensão com sucesso, você precisa de apenas 3 arquivos. No disco minimalista da extensão, que mencionei acima, existem apenas 3 arquivos.

 config.m4 php_libtrie.h libtrie.c 

Não gosto da nomeação padrão aceita no php, gosto que os arquivos de cabeçalho e os arquivos com o corpo do programa tenham o mesmo nome. Portanto, renomeie
libtrie.c
em
php_libtrie.c

 mv libtrie.c php_libtrie.c 

Editando config.m4


O arquivo config.m4 padrão está literalmente repleto de conteúdo, cuja abundância é confusa e confusa. Como eu disse, esse arquivo é necessário para formar o script de configuração. Leia mais sobre isso aqui .

 geany config.m4 & 

Deixamos apenas isso:

 PHP_ARG_ENABLE(libtrie, whether to enable libtrie support, [ --enable-libtrie Enable libtrie support]) if test "$PHP_LIBTRIE" != "no"; then #    -    # PHP_ADD_INCLUDE() #   PHP_NEW_EXTENSION(libtrie, php_libtrie.c, $ext_shared) # PHP_NEW_EXTENSION(libtrie, php_libtrie.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) fi 



A primeira macro cria a capacidade de ativar e desativar nossa extensão ao executar o script de configuração gerado.

O segundo bloco é o mais importante: determina quais arquivos serão compilados como parte de nossa extensão, se a extensão será conectada dinamicamente por meio de um arquivo .so ou se a extensão será estática e será integrada durante a compilação do php. A nossa será dinâmica.
Salve o arquivo.

Copiamos o arquivo no diretório do usuário para que ele não funcione no modo raiz.

 #    exit 

Cópia:

 cp /usr/local/src/php-7.2.11/ext/libtrie ~/Documents/ -r 

Função de demonstração


Deixe-me lembrá-lo de que faremos o análogo completo de array_fill (). Vou trabalhar no clion, mas este guia pode ser feito em qualquer editor. O Clion é bom porque permite que você faça automaticamente a verificação básica de sintaxe e também suporta a navegação rápida por arquivos ou funções por meio de ctrl + clique. Para que essas transições funcionem em nossa extensão, você precisará configurar o arquivo CMakeLists.txt

Para que o clion funcione corretamente, você precisará instalar o sistema cmake build, com o qual vários pacotes dependentes serão instalados. Instale tudo isso com o comando:

 sudo apt install cmake 

Configurar cmake


Abrimos nosso catálogo com expansão em clion. Crie um arquivo CMakeLists.txt com o seguinte conteúdo no menu de contexto, clicando no nome do diretório raiz na parte superior da tela:

 cmake_minimum_required(VERSION 3.12) project(php-ext-libtrie C) set(CMAKE_C_STANDARD 11) #   phproot,       php set(phproot /usr/local/src/php-7.2.11/) #   ,      #      clion       php include_directories(${phproot}) include_directories(${phproot}TSRM/) include_directories(${phproot}main/) include_directories(${phproot}Zend/) #    clion          add_executable(php-ext-libtrie php_libtrie.c) 



Talvez alguém saiba como diminuir esse arquivo para que o clion comece a indexar os arquivos do projeto. Não encontrei um caminho mais curto. Se alguém souber, escreva nos comentários.

Código da Função Demo


Abra nosso arquivo com o corpo da nossa extensão php_libtrie.c e
exclua todos os comentários para que não nos confundam.





Clion verifica se todas as funções e macros usadas no código foram declaradas e gera um erro, caso contrário. Obviamente, os desenvolvedores de PHP não usam clion, caso contrário, provavelmente fariam algo a respeito. Para impedir que esses erros ocorram em nossa extensão, incluiremos os arquivos de cabeçalho ausentes.

Para otimizar tudo, eu faço o seguinte:
Eu transfiro tudo incluído com cabeçalhos do arquivo php_libtrie.h para php_libtrie.h , apenas uma entrada permanece no primeiro arquivo:

 #include "php_libtrie.h" 



O arquivo php_libtrie.h conterá todas as outras inclusões necessárias.



Conteúdo do meu arquivo de cabeçalho
 #ifndef PHP_LIBTRIE_H #define PHP_LIBTRIE_H #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdarg.h> //   va_start() #include <inttypes.h> //    //  #if defined(__GNUC__) && __GNUC__ >= 4 # define ZEND_API __attribute__ ((visibility("default"))) # define ZEND_DLEXPORT __attribute__ ((visibility("default"))) #else # define ZEND_API # define ZEND_DLEXPORT #endif # define SIZEOF_SIZE_T 8 //   ZVAL_COPY_VALUE() #ifndef ZEND_DEBUG #define ZEND_DEBUG 0 #endif //  ,      #include "php.h" #include "php_ini.h" #include "zend.h" #include "zend_types.h" //ZVAL_COPY_VALUE #include "ext/standard/info.h" #include "zend_API.h" #include "zend_modules.h" #include "zend_string.h" #include "spprintf.h" extern zend_module_entry libtrie_module_entry; ... 


Se tudo for feito corretamente, o testador de clion mostrará um quadrado amarelo ou verde no canto superior direito, o que significa que não há erros críticos.



Uma pequena digressão teórica


Para que a extensão funcione corretamente, são necessárias duas coisas:

  1. Você precisa inicializar a estrutura especial zend_module_entry, que contém o seguinte:

     zend_module_entry libtrie_module_entry = { STANDARD_MODULE_HEADER, //  "libtrie", //  libtrie_functions, //     PHP_MINIT(libtrie), //,     PHP_MSHUTDOWN(libtrie), //   PHP_RINIT(libtrie), /* Replace with NULL if there's nothing to do at request start */ PHP_RSHUTDOWN(libtrie), /* Replace with NULL if there's nothing to do at request end */ PHP_MINFO(libtrie), // ,    php  phpinfo() PHP_LIBTRIE_VERSION, // ,     STANDARD_MODULE_PROPERTIES //    }; 

  2. Inicialize a mesma matriz que contém todas as funções da nossa extensão.

    Aqui, por meio de um invólucro de macro especial PHP_FE (), os nomes de todas as funções em nossa extensão são definidos. Em geral, as macros são muito ativamente usadas no PHP, existem muitas que simplesmente se referem a outras macros e, por sua vez, outras. Você tem que se acostumar com isso. Você pode descobrir se percorre as macros com a tecla Ctrl + clique. Aqui clion é indispensável.

    Lembra do arquivo proto? Definimos 1 função my_array_fill (). Então agora temos 3 elementos aqui:

     const zend_function_entry libtrie_functions[] = { PHP_FE(confirm_libtrie_compiled, NULL) /* For testing, remove later. */ PHP_FE(my_array_fill, NULL) PHP_FE_END /* Must be the last line in libtrie_functions[] */ }; 

    A primeira linha é uma função de teste que mostra que a extensão funciona, a segunda é a nossa função, a terceira é uma macro especial, que deve ser a última nessa matriz.

Encontre nossa função:

 PHP_FUNCTION(my_array_fill) 

Como você pode ver, também é inicializado através de uma macro. O fato é que todas as funções php não retornam nada (para ser preciso, retornam nulas) dentro de C, e seus argumentos não podem ser alterados. Em algum lugar é até conveniente.

Se você olhar para a estrutura do arquivo (parte da janela à esquerda), todas as funções do arquivo estão listadas aqui, mas na forma em que estarão após pré-compilar as macros. A captura de tela mostra que nossa função my_array_fill será realmente zif_my_array_fill.



Argumentos das entranhas do php para a nossa função C obtemos com uma macro. Mais detalhes sobre essa macro podem ser encontrados no arquivo:

 /usr/local/src/php-7.2.11/README.PARAMETER_PARSING_API 

Abaixo está o código da nossa função analógica com explicações detalhadas.

Código
 PHP_FUNCTION(my_array_fill) { //   ,     //    2 : // zend_execute_data *execute_data, zval *return_value //     ,      //zend_long  int64_t  x64   int32_t  x86  //      zend_long zend_long start_index; //1  , zend_long num; //2   zval *value; //   mixed ,   zval,      //    ,          if (zend_parse_parameters(ZEND_NUM_ARGS(), "llz", &start_index, &num, &value) == FAILURE) { /*     *    RETURN_    * return_value     */ RETURN_FALSE; } //   ,   -    if (num <= 0) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "argument 2 must be > 0"); //     RETURN_FALSE; } //zval *return_value  ,        //       zval,     ,  -  //   zend_long  unsigned int32. //      + -. ..   1,    3,     4  array_init_size(return_value, (uint32_t)(start_index + num)); //  ,   ,   for(zend_long i = start_index, last = start_index + num; i < last; ++i) { //   zval       add_index_zval(return_value, i, value); } //   ,      return_value return; } 




Construir e testar extensões


Primeiro, execute phpize, o que nos fará configurar o arquivo.

 phpize 

Agora execute ./configure, que fará o makefile.

 ./configure 

Por fim, execute make, que coletará nossa extensão.

 make 

Vamos verificar o que fizemos.

 #    php,       # modules.  -a  php      php -d extension=modules/libtrie.so -a 

Entramos no console php:

 print_r(my_array_fill(50, 2, "hello, baby!")); 

Aprecie o resultado.



Alguém pergunta, onde está Trie aqui? Sobre as funções que implementam o trabalho de Trie, escreverei na segunda parte.

Fique atento.

Source: https://habr.com/ru/post/pt428626/


All Articles