Por qué
Estoy escribiendo este artículo para que el camino que tomé un total de al menos un año, el lector pudiera caminar en un par de horas. Como ha demostrado mi experiencia personal, simplemente programar en C es un poco más fácil que hacer que una extensión seria para PHP funcione. Aquí le contaré lo más posible sobre cómo hacer una extensión usando el ejemplo de la biblioteca
libtrie que implementa el árbol de prefijos, mejor conocido como trie. Escribiré y realizaré simultáneamente las acciones descritas en un sistema Lubuntu 18.04 recién instalado.
Empecemos
Instalación de software
Php
- Primero colocamos el paquete php7.2-dev, en él el script phpize necesario para construir la extensión. Además, necesitaremos una versión funcional de php, en la que verificaremos nuestra extensión. La instalación de este paquete extraerá varios paquetes dependientes, ponemos todo lo que se ofrece.
sudo apt install php7.2-dev
- Vamos al sitio web php.net, vamos a la sección de descargas y sacamos un enlace al archivo con la última versión estable de php, ahora es la versión 7.2.11.
Descargue el archivo fuente de php:
cd /tmp && wget http://it2.php.net/get/php-7.2.11.tar.gz/from/this/mirror -O php7.tar.gz
- Ahora descomprima el archivo para nosotros:
sudo tar -xvf php7.tar.gz -C /usr/local/src
Editores de código
Usualmente uso 2 editores de código. Simple y rápido geany y bastante nerd, pero clion muy avanzado de JetBrains. Geany instalar desde el nabo estándar Ubuntu.
sudo apt install geany
Descargue Clion del sitio web oficial de 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
Hagamos un enlace para que sea conveniente ejecutar clion desde la consola.
sudo ln -s /usr/share/clion-2018.2.5/bin/clion.sh /usr/bin/clion
Después del primer lanzamiento, clion creará atajos para sí mismo desde el menú de shell de LXpanel, pero la primera vez que necesita ejecutarlo a mano.
Crear extensión
Aquí tenemos al menos 3 opciones:
- Tome el disco estándar sin procesar de las fuentes de php que descargamos.
- Vi un poco el disco estándar con un script especial ext_skel
- Consigue un buen disco minimalista desde aquí .
La tercera opción me gusta más, pero usaré la segunda, en caso de falla, para minimizar la cantidad de lugares donde podría estar equivocado. Aunque elegir un espacio en blanco de desarrolladores sigue siendo un placer :-)
- Vayamos al directorio con extensiones php estándar.
cd /usr/local/src/php-7.2.11/ext
Además del nombre del script, puede especificar algunos parámetros de extensión a través del archivo proto. Todo esto no se puede hacer. Haré todo a mano, pero mostraré cómo funciona el protocolo. Hacemos trie, así que llamemos a nuestra extensión libtrie. Para trabajar en el directorio / usr / local / src, necesita privilegios de administrador, para no terminar escribiendo sudo, incluiré bash con privilegios elevados.
sudo bash
- Aquí estableceré los parámetros de solo 1 función que implementará la extensión creada. Esta es solo una función de demostración para mostrar cómo se hace esto.
Haremos un análogo completo de la función estándar
array array_fill ( int $start_index , int $num , mixed $value )
La sintaxis en el archivo proto es muy simple, solo necesita especificar el nombre de la función. Escribiré un poco más para que parezca más informativo.
echo my_array_fill \( int start_index , int num , mixed value \) >> libtrie.proto
- Ahora ejecute el script ext_skel, dándole el nombre de la extensión y el archivo proto que creamos.
./ext_skel --extname=libtrie --proto=./libtrie.proto
- Hemos creado un directorio con nuestra extensión. Vamos a entrar en eso.
cd libtrie
Estructura de archivo y principio de ensamblaje
Estructura de archivo config.m4 - phpize ./configure, makefile. CREDITS - , , libtrie.c - php_libtrie.h - config.w32 - windows EXPERIMENTAL - . , . libtrie.php - php . tests -
Para construir con éxito la extensión, solo necesita 3 archivos. En el disco minimalista de la extensión, que mencioné anteriormente, solo hay 3 archivos.
config.m4 php_libtrie.h libtrie.c
No me gusta el nombre estándar aceptado en php, me gusta que los archivos de encabezado y los archivos con el cuerpo del programa tengan el mismo nombre. Por lo tanto renombrar
libtrie.c
en
php_libtrie.c
mv libtrie.c php_libtrie.c
Edición de config.m4
El archivo config.m4 predeterminado está literalmente repleto de contenido, cuya abundancia es confusa y confusa. Como dije, este archivo es necesario para formar el script de configuración. Lea más sobre esto
aquí .
geany config.m4 &
Solo dejamos esto:
PHP_ARG_ENABLE(libtrie, whether to enable libtrie support, [ --enable-libtrie Enable libtrie support]) if test "$PHP_LIBTRIE" != "no"; then

La primera macro crea la capacidad de habilitar y deshabilitar nuestra extensión al ejecutar el script de configuración generado.
El segundo bloque es el más importante, determina qué archivos se compilarán como parte de nuestra extensión, si la extensión se conectará dinámicamente a través de un archivo .so o si la extensión será estática y se integrará durante la compilación de php. El nuestro será dinámico.
Guarda el archivo.
Copiamos el archivo al directorio del usuario para que no funcione en modo raíz.
Copia:
cp /usr/local/src/php-7.2.11/ext/libtrie ~/Documents/ -r
Función de demostración
Déjame recordarte que haremos el análogo completo de array_fill (). Trabajaré a través de clion, pero esta guía se puede hacer en cualquier editor. Clion es bueno porque le permite realizar automáticamente una verificación básica de sintaxis, y también admite la navegación rápida a través de archivos o funciones a través de Ctrl + clic. Para que tales transiciones funcionen en nuestra extensión, deberá configurar el archivo CMakeLists.txt
Para que clion funcione correctamente, necesitará instalar el sistema de compilación cmake, con el cual se instalarán varios paquetes dependientes. Instala todo esto con el comando:
sudo apt install cmake
Configurar cmake
Abrimos nuestro catálogo con expansión en clion. Cree un archivo CMakeLists.txt con los siguientes contenidos a través del menú contextual haciendo clic en el nombre del directorio raíz en la parte superior de la pantalla:
cmake_minimum_required(VERSION 3.12) project(php-ext-libtrie C) set(CMAKE_C_STANDARD 11)

Tal vez alguien sepa cómo acortar este archivo para que clion comience a indexar los archivos del proyecto. No encontré un camino más corto. Si alguien lo sabe, escriba los comentarios.
Código de función de demostración
Abra nuestro archivo con el cuerpo de nuestra extensión
php_libtrie.c
y
elimine todos los comentarios para que no nos confundan.


Clion verifica si todas las funciones y macros utilizadas en el código han sido declaradas y arroja un error si no es así. Obviamente, los desarrolladores de PHP no usan clion, de lo contrario probablemente harían algo al respecto. Para evitar que ocurran estos errores en nuestra extensión, incluiremos los archivos de encabezado que faltan.
Para simplificar todo, hago esto:
Transfiero todo incluido con encabezados del archivo
php_libtrie.h
a
php_libtrie.h
, solo queda 1 entrada en el primer archivo:
#include "php_libtrie.h"

El archivo
php_libtrie.h
contendrá todas las demás inclusiones necesarias.

Contenido de mi archivo de encabezado #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; ...
Si todo se hace correctamente, el probador de cliones mostrará un cuadrado amarillo o verde en la esquina superior derecha, lo que significa que no hay errores críticos.

Una pequeña digresión teórica
Para que la extensión funcione correctamente, se necesitan 2 cosas:
- Debe inicializar la estructura especial zend_module_entry, que contiene lo siguiente:
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 // };
- Inicialice la misma matriz que contiene todas las funciones de nuestra extensión.
Aquí, a través de un macro wrapper especial PHP_FE (), se establecen los nombres de todas las funciones en nuestra extensión. En general, las macros se usan de manera muy activa en PHP, hay muchas macros que simplemente se refieren a otras macros, y esas a su vez son más. Tienes que acostumbrarte. Puede averiguar si pasa por las macros a través de Ctrl + clic. Aquí clion es indispensable.
¿Recuerdas el archivo proto? Establecemos allí 1 función my_array_fill (). Entonces ahora tenemos 3 elementos aquí:
const zend_function_entry libtrie_functions[] = { PHP_FE(confirm_libtrie_compiled, NULL) PHP_FE(my_array_fill, NULL) PHP_FE_END };
La primera línea es una función de prueba que mostrará que la extensión funciona, la segunda es nuestra función, la tercera es una macro especial, que debería ser la última en esta matriz.
Encuentra nuestra función:
PHP_FUNCTION(my_array_fill)
Como puede ver, también se inicializa a través de una macro. La cuestión es que todas las funciones de php no devuelven nada (para ser precisos, devuelven vacío) dentro de C, y sus argumentos no se pueden cambiar. En algún lugar es incluso conveniente.
Si observa la estructura del archivo (parte de la ventana de la izquierda), todas las funciones del archivo se enumeran aquí, pero en la forma en que estarán después de precompilar macros. La captura de pantalla muestra que nuestra función my_array_fill en realidad será zif_my_array_fill.

Argumentos desde las entrañas de php a nuestra función C que obtenemos con una macro. Se pueden encontrar más detalles sobre esta macro en el archivo:
/usr/local/src/php-7.2.11/README.PARAMETER_PARSING_API
A continuación se muestra el código de nuestra función analógica con explicaciones detalladas.
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 y probar extensiones
Primero, ejecuta phpize, lo que nos hará configurar el archivo.
phpize
Ahora ejecute ./configure, que hará el archivo MAKE.
./configure
Finalmente, ejecuta make, que recogerá nuestra extensión.
make
Veamos qué hicimos.
Entramos en la consola php:
print_r(my_array_fill(50, 2, "hello, baby!"));
Disfruta el resultado.

Alguien pregunta, ¿dónde está aquí? Sobre las funciones que implementan el trabajo de trie, escribiré en la segunda parte.
Estén atentos.