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
- 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
- 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
- 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.
Criar extensão
Aqui temos pelo menos 3 opções:
- Pegue o disco padrão bruto das fontes php que baixamos.
- Vi o disco padrão um pouco com um script ext_skel especial
- 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 :-)
- 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
- 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
- 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
- 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

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.
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)

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 #endif #include <stdarg.h> // va_start() #include <inttypes.h> // // #if defined(__GNUC__) && __GNUC__ >= 4 # define ZEND_API __attribute__ ((visibility())) # define ZEND_DLEXPORT __attribute__ ((visibility())) #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 #include #include #include //ZVAL_COPY_VALUE #include #include #include #include #include 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:
- 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 // };
- 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) PHP_FE(my_array_fill, NULL) PHP_FE_END };
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.
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.