📜 ⬆️ ⬇️

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

What for?


I am writing this article so that the reader, who took a total of not less than a year to complete the journey, could walk a couple of hours. As my personal experience has shown, simply programming in C is somewhat easier than making a serious PHP extension work. Here I will tell you in as much detail as possible how to make an extension using the example of the libtrie library, which implements a prefix tree, better known as trie. I will write and perform the described actions in parallel on a freshly installed Lubuntu 18.04 system.

Let's start.

Software installation


Php


  1. First we install the php7.2-dev package, in it the phpize script needed for building the extension. In addition, we need a working version of php, on which we will check our extension. Installing this package will pull up a certain number of dependent packages; we put everything that is offered.

    sudo apt install php7.2-dev 

  2. Go to the site php.net, go to the downloads section and pull out a link to the archive with the latest stable version of php, now it is version 7.2.11.
    Downloading the php source archive:
    ')
     cd /tmp && wget http://it2.php.net/get/php-7.2.11.tar.gz/from/this/mirror -O php7.tar.gz 

  3. Now unpack the archive yourself:

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


Code editors


I usually use 2 code editors. Simple and fast geany and pretty brake, but very advanced clion of the company JetBrains. Geany install from standard turnips Ubuntu.

 sudo apt install geany 

Clion download from the official website of 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 

Make a link to make it convenient to run clion from the console.

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

After the first launch, clion will create shortcuts for itself from the LXpanel shell menu, but the first time you need to launch it with your hands.

 # clion 

Creating an extension


Here we have at least 3 options:

  1. Take the raw standard disc from the php sources we downloaded.
  2. Saw a standard blank with a special ext_skel script
  3. Get a good minimalist blank from here .

The third option I like the most, but I will use the second, in case of failure to minimize the number of places where I could be mistaken. Although picking a pig of developers is still a pleasure :-)

  1. Go to the directory with standard php extensions.

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

    In addition to the name of the script, you can set some extension parameters through the proto file. All this can not be done. I'll do everything with my hands, but how proto works will show. We make a trie, so we will call our extension libtrie. To work in the / usr / local / src directory, you need administrator privileges, so as not to endlessly write sudo, I will enable bash with elevated privileges.

     sudo bash 

  2. Here I will set the parameters of only 1 function that the extension will implement. This is just a demonstration function to show how this is done.

    We will make a complete analog of the standard function.

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

    The syntax in the proto file is very simple, you just need to specify the name of the function. I will write a little more to look more informative.

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

  3. Now run the ext_skel script, giving it the extension name and the proto file we created.

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

  4. We have created a directory with our extension. Let's go into it.

     cd libtrie 


File structure and build principle


File structure
 config.m4 -           phpize   ./configure,       makefile. CREDITS -  ,     ,    libtrie.c -      php_libtrie.h -     config.w32 -        windows EXPERIMENTAL -  .    ,    . libtrie.php -  php      . tests -   


To successfully build the extension, you need only 3 files. In the minimalist extension bar, which I mentioned above, there are only 3 files.

 config.m4 php_libtrie.h libtrie.c 

I do not like the standard naming convention in php; I like the header files and files with the body of the program to be called the same. Therefore rename
libtrie.c
at
php_libtrie.c

 mv libtrie.c php_libtrie.c 

Editing config.m4


The default config.m4 file is literally stuffed with content, the abundance of which is confusing and confusing. As I said, this file is needed to form the configure script. Read more about it here .

 geany config.m4 & 

We leave only this:

 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 



The first macro creates the ability to enable and disable our extension when running the configure script being created.

The second block is the most important, it determines which files will be compiled as part of our extension, whether the extension will be dynamically connected via the .so file, or the extension will be static and will be integrated when building php. Our will be dynamic.
Save the file.

Copy the file in the user directory, so as not to work in root mode.

 #    exit 

We copy:

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

Demonstration function


Let me remind you that we will do the full analogue of array_fill (). I will work through clion, but this guide can be done in any editor. Clion is good in that it allows you to automatically do a basic syntax check, and also supports quick transition to files or functions via ctrl + click. In order for such transitions to work in our extension, you will have to configure the CMakeLists.txt file.

For clion to work properly, you will need to install the cmake build system, along with which a bunch of dependent packages will be installed. Install all this with the command:

 sudo apt install cmake 

Cmake setup


Open our catalog with the extension in clion. We create a CMakeLists.txt file with the following contents through the context menu by clicking on the name of the root directory at the top of the screen:

 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) 



Maybe someone knows how to make this file shorter so that clion starts indexing the project files. I did not find a shorter way. If someone knows, write in the comments.

Demo function code


Open our body file with our extension php_libtrie.c and
delete all comments so that they do not confuse us.





Clion checks whether all the functions and macros used in the code have been declared and throws an error if this is not the case. Obviously, PHP developers do not use clion, but they probably would have done something with it. To prevent these errors from appearing in our extension, we include the missing header files to us.

To streamline everything, I do this:
I include all include with headers from the php_libtrie.c file in php_libtrie.h , only 1 entry remains in the first file:

 #include "php_libtrie.h" 



In the file php_libtrie.h will be all the other necessary inclusions.



Content of my header file
 #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; ... 


If everything is done correctly, the clion checker will show a yellow or green square in the upper right corner, which means that there are no critical errors.



A small theoretical retreat


For the normal operation of the expansion you need 2 things:

  1. It is necessary to initialize the special structure zend_module_entry, which contains the following:

     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. Initialize the same array that contains all the functions of our extension.

    Here, through a special macro wrapper PHP_FE (), the names of all functions in our extension are specified. In general, macros are very actively used in PHP, there are a lot of such macros that simply refer to other macros, and those in turn are further. It is necessary to get used to this. You can figure out if you go through the macros via ctrl + click. Here just clion is irreplaceable.

    Remember the proto file? We set there 1 function my_array_fill (). So now we have 3 elements here:

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

    The first line is a test function that will indicate that the extension is working, the second is our function, the third is a special macro that should be the last in this array.

Find our function:

 PHP_FUNCTION(my_array_fill) 

As you can see, it is also initialized through a macro. The thing is that all php functions do not return anything (to be exact, they return void) inside C, and their arguments cannot be changed. Somewhere it is even convenient.

If you look at the file structure (part of the window on the left), all the functions of the file are listed here, but in the form in which they will be after precompiling macros. The screenshot shows that our function my_array_fill will actually be zif_my_array_fill.



Arguments from the depths of php to our C function we get a macro. More information about this macro can be found in the file:

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

Below is the code of our analog function with detailed explanations.

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




Build and test extensions


First, run phpize, which will make us the configure file.

 phpize 

Now run ./configure, which will make the makefile.

 ./configure 

Finally, run make, which will build us our extension.

 make 

Check out what we did.

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

Enter in php console:

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

Enjoying the result.



Someone will ask, but where is the trie? I will write about functions implementing trie in the second part.

Stay tuned.

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


All Articles