📜 ⬆️ ⬇️

How to make an extension for PHP7 harder than "hello, world", and not become a red-eyed. Part 2

Summary of the first part


In the first part, I made a blank of the extension, made it work correctly in IDE Clion, wrote the analog function my_array_fill () and checked its efficiency in php.

Now what?


Now I will fill in the libtrie library code in our extension.

I’ll tell you a little about how you can make old php5 extensions work in php7.
Then I will do a few basic functions from this library in php and check what happened.

Go


Get the libtrie code in our extension


Go to the directory extension
')
cd ~/Documents/libtrie/ 

Cloning libtrie repository

 git clone https://github.com/legale/libtrie 



I open the file with the extension code php_libtrie.c and the file with the library code libtrie/src/libtrie.c .



I will use the last one to check the names and syntax of functions.

I will use the same functions in php as in the library itself.
First of all, we need to include the header file of the library in the code of our extension.
We write in php_libtrie.c:

 #include "libtrie/src/libtrie.h" 

Yatrie_new function


I do the first function that will create a prefix tree. In the library, it is called

 trie_s *yatrie_new(uint32_t max_nodes, uint32_t max_refs, uint32_t max_deallocated_size) {...} 

As can be seen from the code, the function takes 3 numeric arguments as input and returns a pointer to the trie_s structure. Simply put, returns a link to the created prefix tree.

In order to pull out our prefix tree in PHP, PHP has a special data type called resource. When a function is executed in PHP

 fopen("filename.ext"); 

Speaking in C, the program asks the operating system to open the specified file, creates a pointer to this file, which is in the form of a resource and returns to the outside in PHP.

We will do the same with our tree.

Make a function in php_libtrie.c:

Function code
 PHP_FUNCTION (yatrie_new) { /*     */ trie_s *trie; //     zend_long max_nodes; // -      zend_long max_refs; /*  -   . *        . -  +25%     . * ,    OpenCorpora ~3.    5.   5.  */ zend_long max_deallocated_size; /*        *    .    96    , 1  ,  95. *              95,  , *  .         94. */ //   PHP if (zend_parse_parameters(ZEND_NUM_ARGS(), "lll", &max_nodes, &max_refs, &max_deallocated_size) == FAILURE) { RETURN_FALSE; } //             trie = yatrie_new((uint32_t)max_nodes, (uint32_t)max_refs, (uint32_t)max_deallocated_size); //   -   if (!trie) { RETURN_NULL(); } //  2  /*  zend_register_resource()     Zend, *        le_libtrie,   ZVAL_RES() *     zval return_value */ ZVAL_RES(return_value, zend_register_resource(trie, le_libtrie)); } 


Now you need to add the created function to the array of extension functions, otherwise the function will not be visible from PHP.

 PHP_FE(yatrie_new, NULL) 



To make it quite beautiful, I will add a function declaration to the header file. This is not necessary because our PHP functions do not interact with each other, but I still prefer to declare all the functions in the header file.

Just add the lines:

 PHP_FUNCTION(confirm_libtrie_compiled); PHP_FUNCTION(my_array_fill); PHP_FUNCTION(yatrie_new); 
to the php_libtrie.h file. Somewhere between:
 #ifndef PHP_LIBTRIE_H #define PHP_LIBTRIE_H 
and
 #endif /* PHP_LIBTRIE_H */ 



PHP resource destructor


The created yatrie_new () function creates a tree, and also registers a PHP resource. Now we need a function that will close the created resource and free the memory occupied by the prefix tree.

 /** * @brief  ,           * @param rsrc : zend_resource *  * @return void */ static void php_libtrie_dtor(zend_resource *rsrc TSRMLS_DC) { //     trie   trie_s *trie = (trie_s *) rsrc->ptr; //   ,   , //   trie yatrie_free(trie); } 

Since the function is internal to the array of expansion functions, it is not included. I will add her declaration in php_libtrie.h:



Now you need to register the created destructor function in PHP. This is done through a special extension initialization function. Before that, this function simply returned SUCCESS immediately. It is necessary to add the destructor registration there.

 //  PHP     trie PHP_MINIT_FUNCTION (libtrie) { le_libtrie = zend_register_list_destructors_ex( php_libtrie_dtor, NULL, PHP_LIBTRIE_RES_NAME, module_number); return SUCCESS; } 



Delete tree function


Like the fopen() function has a pair of fclose() , so my tree creation function should have a friend who will balance it.

Code
 /** * @brief     * @param trie : resource * @return true/false : bool */ PHP_FUNCTION (yatrie_free) { zval *resource; //  zval    //    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &resource) == FAILURE) { RETURN_FALSE; } /*    ,    zend_resource, *      zval.     Z_RES_P() */ if (zend_list_close(Z_RES_P(resource)) == SUCCESS) { //  true  return_vale   return RETURN_TRUE; } //  false  return_vale   return RETURN_FALSE; } 


I add a function to the array of extension functions:

 PHP_FE(yatrie_free, NULL) 

I add the function declaration to the header file:

 PHP_FUNCTION(yatrie_free); 



As you can see in the screenshot, I added the internal resource name in PHP to the header file, as well as the PHP5 macros, which for some reason were deleted from PHP7. I do not use them, but if someone wants with them, you can easily build a PHP5 extension on PHP7.

 #define PHP_LIBTRIE_VERSION "0.1.0" /* Replace with version number for your extension */ #define PHP_LIBTRIE_RES_NAME "libtrie data structure" /* PHP resource name */ //previously (php5) used MACROS #define ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type) \ (rsrc = (rsrc_type) zend_fetch_resource(Z_RES_P(*passed_id), resource_type_name, resource_type)) #define ZEND_REGISTER_RESOURCE(return_value, result, le_result) ZVAL_RES(return_value,zend_register_resource(result, le_result)) 

Function add words to trie


Now we will make the function of adding a word to the prefix tree.


  1. Code
     /** * @brief   trie    node_id     * @param trie : resource * @param word : string * @return node_id : int */ PHP_FUNCTION (yatrie_add) { trie_s *trie; //   zval *resource; //  zval    unsigned char *word = NULL; //     size_t word_len; //  word uint32_t node_id; //id  ,   //  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &resource, &word, &word_len) == FAILURE) { RETURN_FALSE } /*    PHP,  : * 1    PHP ( zval,   ), * 2    *         * 3   id ,       *     void *,        trie_s * *  PHP5      ZEND_FETCH_RESOURCE(),  -    PHP7. */ trie = (trie_s *) zend_fetch_resource(Z_RES_P(resource), PHP_LIBTRIE_RES_NAME, le_libtrie); /*    trie *   -      *   - id     ,      *   -    . */ node_id = yatrie_add(word, 0, trie); //   RETURN_LONG(node_id); } 


  2. I add an entry to the array of functions:

     PHP_FE(yatrie_add, NULL) 
  3. I add the declaration to the header file:

     PHP_FUNCTION(yatrie_add) 

The function of the withdrawal of all words from the dictionary


Now we will make a function that will select all words from the prefix tree and output them in PHP as an array.


  1. Code
     /** * @brief    ,    ,      * ,     * @param trie : resource * @param node_id : int * @param head () : string ,   *       * @return array */ PHP_FUNCTION (node_traverse) { trie_s *trie; //  trie words_s *words; //     trie zval * resource; //  zval  zend_long node_id; //  unsigned char *head = NULL; //    size_t head_len; //  //   PHP if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl|s", &resource, &node_id, &head, &head_len) == FAILURE) { RETURN_NULL(); // null    } //     trie = (trie_s *) zend_fetch_resource(Z_RES_P(resource), PHP_LIBTRIE_RES_NAME, le_libtrie); //    trie  node_traverse()    words_s //    words = (words_s *) calloc(1, sizeof(words_s)); words->counter = 0; //    0 //  1     string_s *head_libtrie = calloc(1, sizeof(string_s)); // head  if(head != NULL) { head_libtrie->length = (uint32_t)head_len; //  memcpy(&head_libtrie->letters, head, head_len); //   head_libtrie } //    trie node_traverse(words, (uint32_t) node_id, head_libtrie, trie); //  PHP ,       words array_init_size(return_value, words->counter); //    php while (words->counter--) { //  trie     ,    //     uint8_t dst[256]; //   libtrie decode_string(dst, words->words[words->counter]); //  Zend API,        php string    char * add_next_index_string(return_value, (const char *) dst); } //      words  head_libtrie free(words); free(head_libtrie); } 

  2. I add an entry to the array of functions:

     PHP_FE(node_traverse, NULL) 

  3. I add the declaration to the header file:

     PHP_FUNCTION(node_traverse) 


Extension build


Since the extension now uses files from a third-party library, these files must also be compiled. I open the config.m4 file and add 2 source libtrie files there:

libtrie/src/libtrie.c
libtrie/src/single_list.c


Here is the full contents of the file after the changes.

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




Now you need to re-do the ./configure script. I run from the root directory of the extension:

 phpize && ./configure 

Now I collect the extension:

 make 

We are testing


For the test, it's best to make a php script so that you don’t have to write a lot to the console I will do this:

 nano yatrie_test.php 

And this is the contents of the file:

 <?php echo "C   500   500 \n\n"; $trie = yatrie_new(500, 500, 100); echo "!\n  ,  id    \$nodes\n"; $nodes[] = yatrie_add($trie, ""); $nodes[] = yatrie_add($trie, ""); $nodes[] = yatrie_add($trie, ""); echo "     .\n    2 ,    2.\n    3 ,  2     ,\n   1  \n"; print_r($nodes); print_r(node_traverse($trie, 0)); yatrie_free($trie); 

Perform in the console:

 php -d extension=modules/libtrie.so yatrie_test.php 

This is what should happen:



The resulting extension source code is taken from here . Do not hesitate and put asterisks :-)

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


All Articles