Comment rendre l'extension en PHP7 plus difficile que "bonjour, monde", et ne pas devenir aux yeux rouges. Partie 1

Pourquoi?


J'écris cet article pour que le chemin que j'ai pris au total au moins un an, le lecteur puisse marcher en quelques heures. Comme mon expérience personnelle l'a montré, il est légèrement plus facile de programmer en C que de faire une extension sérieuse pour PHP. Ici, je vais vous en dire autant que possible sur la façon de faire une extension en utilisant l'exemple de la bibliothèque libtrie qui implémente l'arbre de préfixe, mieux connu sous le nom de trie. J'écrirai et effectuerai simultanément les actions décrites sur un système Lubuntu 18.04 fraîchement installé.

Commençons.

Installation du logiciel


Php


  1. D'abord, nous avons mis le paquet php7.2-dev, dedans le script phpize nécessaire pour construire l'extension. De plus, nous aurons besoin d'une version fonctionnelle de php, sur laquelle nous vérifierons notre extension. L'installation de ce package entraînera un certain nombre de packages dépendants, nous mettons tout ce qui est proposé.

    sudo apt install php7.2-dev 

  2. Nous allons sur le site Web de php.net, allons dans la section des téléchargements et retirons un lien vers l'archive avec la dernière version stable de php, maintenant c'est la version 7.2.11.
    Téléchargez l'archive source php:

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

  3. Maintenant, décompressez l'archive pour nous:

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


Éditeurs de code


J'utilise habituellement 2 éditeurs de code. Geany simple et rapide et assez ringard, mais clion très avancé de JetBrains. Installez Geany à partir du navet standard Ubuntu.

 sudo apt install geany 

Téléchargez Clion sur le site officiel 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 

Faisons un lien pour faciliter l'exécution de clion à partir de la console.

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

Après le premier lancement, clion lui-même créera des raccourcis pour lui-même à partir du menu du shell LXpanel, mais la première fois que vous devez l'exécuter à la main.

 # clion 

Créer une extension


Ici, nous avons au moins 3 options:

  1. Prenez le disque standard brut des sources php que nous avons téléchargées.
  2. Vu un peu le disque standard avec un script ext_skel spécial
  3. Obtenez un bon disque minimaliste d'ici .

J'aime le plus la troisième option, mais j'utiliserai la deuxième, en cas d'échec, pour minimiser le nombre d'endroits où je pourrais me tromper. Bien que choisir un blanc de développeurs soit toujours un plaisir :-)

  1. Allons dans le répertoire avec les extensions php standard.

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

    En plus du nom du script, vous pouvez spécifier certains paramètres d'extension via le fichier proto. Tout cela ne peut pas être fait. Je ferai tout à la main, mais je montrerai comment fonctionne le proto. Nous faisons du tri, alors nommons notre extension libtrie. Pour travailler dans le répertoire / usr / local / src, vous avez besoin de privilèges d'administrateur, afin de ne pas finir par écrire sudo, j'inclurai bash avec des privilèges élevés.

     sudo bash 

  2. Ici, je définirai les paramètres d'une seule fonction que l'extension créée implémentera. Ceci est juste une fonction de démonstration pour montrer comment cela se fait.

    Nous ferons un analogue complet de la fonction standard

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

    La syntaxe dans le fichier proto est très simple, il vous suffit de spécifier le nom de la fonction. J'écrirai un peu plus pour le rendre plus informatif.

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

  3. Exécutez maintenant le script ext_skel, en lui donnant le nom de l'extension et le fichier proto que nous avons créé.

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

  4. Nous avons créé un répertoire avec notre extension. Allons-y.

     cd libtrie 


Structure des fichiers et principe d'assemblage


Structure des fichiers
 config.m4 -           phpize   ./configure,       makefile. CREDITS -  ,     ,    libtrie.c -      php_libtrie.h -     config.w32 -        windows EXPERIMENTAL -  .    ,    . libtrie.php -  php      . tests -   


Pour créer correctement l'extension, vous n'avez besoin que de 3 fichiers. Dans le disque minimaliste de l'extension, que j'ai mentionné ci-dessus, il n'y a que 3 fichiers.

 config.m4 php_libtrie.h libtrie.c 

Je n'aime pas la dénomination standard acceptée en php, j'aime que les fichiers d'en-tête et les fichiers avec le corps du programme soient nommés de la même manière. Renommez donc
libtrie.c
dans
php_libtrie.c

 mv libtrie.c php_libtrie.c 

Modification de config.m4


Le fichier config.m4 par défaut est littéralement rempli de contenu, dont l'abondance est source de confusion et de confusion. Comme je l'ai dit, ce fichier est nécessaire pour former un script de configuration. En savoir plus à ce sujet ici .

 geany config.m4 & 

Nous laissons seulement ceci:

 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 



La première macro crée la possibilité d'activer et de désactiver notre extension lors de l'exécution du script de configuration généré.

Le deuxième bloc est le plus important, il détermine quels fichiers seront compilés dans le cadre de notre extension, si l'extension sera connectée dynamiquement via un fichier .so, ou si l'extension sera statique et sera intégrée lors de la construction de php. La nôtre sera dynamique.
Enregistrez le fichier.

Nous copions le fichier dans le répertoire utilisateur afin qu'il ne fonctionne pas en mode racine.

 #    exit 

Copie:

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

Fonction démo


Permettez-moi de vous rappeler que nous ferons l'analogue complet de array_fill (). Je vais travailler avec clion, mais ce guide peut être fait dans n'importe quel éditeur. Clion est bon car il vous permet de faire automatiquement une vérification de base de la syntaxe et prend également en charge la navigation rapide dans les fichiers ou les fonctions via ctrl + clic. Pour que de telles transitions fonctionnent dans notre extension, vous devrez configurer le fichier CMakeLists.txt

Pour que clion fonctionne correctement, vous devrez installer le système de construction cmake, avec lequel un paquet de packages dépendants sera installé. Installez tout cela avec la commande:

 sudo apt install cmake 

Configurer cmake


Nous ouvrons notre catalogue avec expansion en clion. Créez un fichier CMakeLists.txt avec le contenu suivant via le menu contextuel en cliquant sur le nom du répertoire racine en haut de l'écran:

 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) 



Peut-être que quelqu'un sait comment raccourcir ce fichier pour que clion commence à indexer les fichiers de projet. Je n'ai pas trouvé de moyen plus court. Si quelqu'un le sait, écrivez dans les commentaires.

Code de fonction de démonstration


Ouvrez notre fichier avec le corps de notre extension php_libtrie.c et
supprimez tous les commentaires afin qu'ils ne nous déroutent pas.





Clion vérifie si toutes les fonctions et macros utilisées dans le code ont été déclarées et renvoie une erreur dans le cas contraire. De toute évidence, les développeurs PHP n'utilisent pas clion, sinon ils feraient probablement quelque chose à ce sujet. Pour éviter que ces erreurs ne se produisent dans notre extension, nous allons nous inclure les fichiers d'en-tête manquants.

Pour tout rationaliser, je fais ceci:
Je transfère tous les include avec en-têtes du fichier php_libtrie.c vers php_libtrie.h , il ne reste qu'une entrée dans le premier fichier:

 #include "php_libtrie.h" 



Le fichier php_libtrie.h contiendra toutes les autres inclusions nécessaires.



Contenu de mon fichier d'en-tête
 #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; ... 


Si tout est fait correctement, le testeur de clion affichera un carré jaune ou vert dans le coin supérieur droit, ce qui signifie qu'il n'y a aucune erreur critique.



Une petite digression théorique


Pour que l'extension fonctionne correctement, 2 choses sont nécessaires:

  1. Vous devez initialiser la structure spéciale zend_module_entry, qui contient les éléments suivants:

     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. Initialisez le même tableau qui contient toutes les fonctions de notre extension.

    Ici, grâce à un wrapper de macro spécial PHP_FE (), les noms de toutes les fonctions de notre extension sont définis. En général, les macros sont très activement utilisées en PHP, il y a beaucoup de macros qui se réfèrent simplement à d'autres macros, et celles-ci sont plus loin. Vous devez vous y habituer. Vous pouvez déterminer si vous passez par les macros via ctrl + clic. Ici, le clion est indispensable.

    Rappelez-vous le fichier proto? Nous y avons mis 1 fonction my_array_fill (). Alors maintenant, nous avons 3 éléments ici:

     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[] */ }; 

    La première ligne est une fonction de test qui montrera que l'extension fonctionne, la seconde est notre fonction, la troisième est une macro spéciale, qui devrait être la dernière de ce tableau.

Retrouvez notre fonction:

 PHP_FUNCTION(my_array_fill) 

Comme vous pouvez le voir, il est également initialisé via une macro. Le fait est que toutes les fonctions php ne renvoient rien (pour être précis, renvoient void) à l'intérieur de C, et leurs arguments ne peuvent pas être modifiés. Quelque part, c'est même pratique.

Si vous regardez la structure du fichier (une partie de la fenêtre à gauche), toutes les fonctions du fichier sont répertoriées ici, mais sous la forme dans laquelle elles seront après la précompilation des macros. La capture d'écran montre que notre fonction my_array_fill sera en fait zif_my_array_fill.



Les arguments des entrailles de php à notre fonction C nous obtenons avec une macro. Plus de détails sur cette macro peuvent être trouvés dans le fichier:

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

Ci-dessous le code de notre fonction analogique avec des explications détaillées.

Code
 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; } 




Créer et tester des extensions


Tout d'abord, exécutez phpize, ce qui nous fera configurer le fichier.

 phpize 

Maintenant, lancez ./configure, qui fera le makefile.

 ./configure 

Enfin, lancez make, qui collectera notre extension.

 make 

Vérifions ce que nous avons fait.

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

On entre dans la console php:

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

Profitez du résultat.



Quelqu'un demande, où est trie ici? A propos des fonctions qui implémentent le travail de trie, j'écrirai dans la deuxième partie.

Restez à l'écoute.

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


All Articles