📜 ⬆️ ⬇️

We write an extension for PHP (7.0.7) without knowledge of C / C ++ and how it works in general

Is it possible to write my own module (extension) to PHP without any special knowledge that requires a lot of time to study the theory? If you are able to program on PHP itself, then it will not be difficult to write the simplest C code, especially since PHP allows you to generate a framework for the extension under development, within which you then write the code. There is still gaining popularity marshmallows in the field for this question. This publication is for those who decided to delve into the source code of PHP, a little look at its insides, with the goal of only superficial research. At the moment I am exploring the same one without the necessary knowledge. PHP interviews are often asked to write factorial counting code. This is the function we will write now in C, which can then be called from PHP code. I will describe the actions that I myself did and at the same time do not know anything initially on this part. On the Internet you can find many articles on this issue, most of them describe information using the zval "old" format, but I do not think that it will be worse if I also add from myself.

In PHP, there is a ready-made tool ./ext_skel (located in the ext folder), which generates a future template (framework) for the extension. I will not describe everything that it generates and why (I myself really don’t understand anything and I don’t know anything about it), but just sign for the minimal edits that our task will solve. The whole process takes place in CentOS 7.

Create a framework for the future expansion of mathstat, which will contain the factorial () function.

[root@localhost ext]# ./ext_skel --extname=mathstat ,     mathstat. [root@localhost mathstat]# ls config.m4 config.w32 CREDITS EXPERIMENTAL mathstat.c mathstat.php php_mathstat.h tests 

')
After executing the command to create an extension, the following supporting information will be displayed.

 To use your new extension, you will have to execute the following steps: 1. $ cd .. 2. $ vi ext/mathstat/config.m4 3. $ ./buildconf 4. $ ./configure --[with|enable]-mathstat 5. $ make 6. $ ./sapi/cli/php -f ext/mathstat/mathstat.php 7. $ vi ext/mathstat/mathstat.c 8. $ make 


In PHP7, I don’t have a buildconf file after generation (probably these are remnants of earlier versions of PHP), but I know that now the compilation of extensions begins with the phpize command. It “creates” a bunch of files, among which is the necessary ./configure. Recall that the custom version of the compilation of the extension is to sequentially execute the following commands.

 Phpize -> ./configure -> make -> make test -> make install 


If you immediately make this sequence of commands, then make install for reasons that are not clear will break and give an error to copy. If anyone knows, write down, in the comments, why.

 [root@localhost eugene]# make install Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20151012/ cp: cannot stat 'modules/*': No such file or directory make: *** [install-modules] Error 1 


Phpize creates files based on the description of config.m4. This, as I understand it, is a kind of declarative way of describing what the extension will be, whether it will pull up external sources or not, etc ... Therefore, looking at other PHP extensions in the source code, I just decided to simplify it as much as possible to minimize compilation errors from pure sheet. I act according to the principle - I do not want anything, “I’ll remove all the ticks.”

Open this file (config.m4) and leave only this text. The option “--enable-mathstat” says that this is just an extension without external sources (libraries) and which can either be turned on or off. (dnl means commenting the line)

 dnl $Id$ PHP_ARG_ENABLE(mathstat, whether to enable mathstat support, [ --enable-mathstat Enable mathstat support]) if test "$PHP_MATHSTAT" != "no"; then PHP_NEW_EXTENSION(mathstat, mathstat.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) fi 


Restart the phpize command.

 [root@localhost mathstat]# phpize Configuring for: PHP Api Version: 20151012 Zend Module Api No: 20151012 Zend Extension Api No: 320151012 [root@localhost mathstat]# ls acinclude.m4 config.guess configure EXPERIMENTAL mathstat.c php_mathstat.h aclocal.m4 config.h.in configure.in install-sh mathstat.php run-tests.php autom4te.cache config.m4 config.w32 ltmain.sh missing tests build config.sub CREDITS Makefile.global mkinstalldirs 


Next, we make familiar commands:

 ./configure && make 


make test - runs one test that was originally created. I wrote about these PHP tests in brief.

 [root@localhost mathstat]# make install Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20151012/ 


This time “make install” passes, then we try to register the extension in php.ini.

Determine where php.ini is located.

 [root@localhost mathstat]# php --ini Configuration File (php.ini) Path: /usr/local/lib Loaded Configuration File: /usr/local/lib/php.ini Scan for additional .ini files in: (none) Additional .ini files parsed: (none) viim /usr/local/lib/php.ini extension=mathstat.so ;zend_extension = /usr/local/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so [root@localhost mathstat]# systemctl restart php-fpm [root@localhost mathstat]# php -m | grep -i math mathstat 


The php -m command (looks at all installed modules) says that everything seems to be normal, the mathstat extension has loaded.

Run the mathstat.php test file in the current directory

 [root@localhost mathstat]# php mathstat.php Functions available in the test extension: confirm_mathstat_compiled Congratulations! You have successfully modified ext/mathstat/config.m4. Module mathstat is now compiled into PHP. [root@localhost mathstat]# 


Great, something is already working.

2. We start to implement the factorial () function.

Edit the mathstat.c file to add the factorial () function.

To do this, add a function to the “list” of mathstat and make a stub on it, via a macro. I do everything by analogy as in other extensions.

 const zend_function_entry mathstat_functions[] = { PHP_FE(confirm_mathstat_compiled, NULL) /* For testing, remove later. */ PHP_FE(factorial, NULL) PHP_FE_END /* Must be the last line in mathstat_functions[] */ }; 


The implementation of the stub function. Done in a macro wrapper. How it works in the end is not yet clear, I leave myself studying for the future. Just doing in a similar format.

 PHP_FUNCTION(factorial) { RETURN_LONG(1000); } 

In this case, for each type of returned data, its own variant RETURN_. An online search will show all possible options. We just have an integer value. Everything seems simple here.

Next, repeat make clean && make && make install

 [root@localhost mathstat]# make clean find . -name \*.gcno -o -name \*.gcda | xargs rm -f find . -name \*.lo -o -name \*.o | xargs rm -f find . -name \*.la -o -name \*.a | xargs rm -f find . -name \*.so | xargs rm -f find . -name .libs -a -type d|xargs rm -rf rm -f libphp.la modules/* libs/* Build complete. Don't forget to run 'make test'. [root@localhost mathstat]# make install Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20151012/ [root@localhost mathstat]# systemctl restart php-fpm [root@localhost mathstat]# systemctl status php-fpm ● php-fpm.service - The PHP FastCGI Process Manager Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; enabled; vendor preset: disabled) Active: active (running) since Thu 2016-06-16 01:12:22 EDT; 5s ago Main PID: 32625 (php-fpm) CGroup: /system.slice/php-fpm.service ├─32625 php-fpm: master process (/usr/local/etc/php-fpm.conf) ├─32626 php-fpm: pool www └─32627 php-fpm: pool www Jun 16 01:12:22 localhost.localdomain systemd[1]: Started The PHP FastCGI Process Manager. Jun 16 01:12:22 localhost.localdomain systemd[1]: Starting The PHP FastCGI Process Manager... 


Restarting php-fpm did not show that something was broken and therefore we go further and test the presence of the function in the extension. I do just in case, even if the compilation has passed.

 [root@localhost mathstat]# php mathstat.php Functions available in the test extension: confirm_mathstat_compiled factorial Congratulations! You have successfully modified ext/mathstat/config.m4. Module mathstat is now compiled into PHP. 

The name of the function appeared and moreover, now we can already call it from the PHP code.

 [root@localhost mathstat]# php -a Interactive mode enabled php > echo factorial(1); 1000 php > 


It is seen that the function called and returned a predetermined value of 1000.

We will teach the function to take an argument and return it, for this it is necessary to make a description of the function argument. We look at analogies in other PHP extensions (I watched bcmath). A bunch of macros, but the format is clear, in principle.

 ZEND_BEGIN_ARG_INFO(arginfo_factorial, 0) ZEND_ARG_INFO(0, number) ZEND_END_ARG_INFO() 


And add its use in the function. If you leave NULL, then the default is considered to be the type of the argument of type int.

 /* {{{ mathstat_functions[] * * Every user visible function must have an entry in mathstat_functions[]. */ const zend_function_entry mathstat_functions[] = { PHP_FE(confirm_mathstat_compiled, NULL) /* For testing, remove later. */ PHP_FE(factorial, arginfo_factorial) PHP_FE_END /* Must be the last line in mathstat_functions[] */ }; 
Slightly correct the function body.

 PHP_FUNCTION(factorial) { int argc = ZEND_NUM_ARGS(); long number = 0; if (zend_parse_parameters(argc, "l", &number) == FAILURE) { RETURN_LONG(0); } RETURN_LONG(number); } 


Here, zend_parse_parameters is used, which checks the passed arguments for the type using the format in quotes (""), then specifies the received value by address. Details can be easily found on the Internet. For the task of implementing factorial large knowledge is not needed.

Check after recompilation (make clean && make && make install).

 [root@localhost mathstat]# php -r "echo factorial('80');"; 80[root@localhost mathstat]# php -r "echo factorial(80);"; 80[root@localhost mathstat]# 


If we pass a string in the argument, we get an error. It is not yet clear how all this actually works until the end, but the required task has been done.

 [root@localhost mathstat]# php -r "echo factorial('aaaa');"; PHP Warning: factorial() expects parameter 1 to be integer, string given in Command line code on line 1 PHP Stack trace: PHP 1. {main}() Command line code:0 PHP 2. factorial() Command line code:1 Warning: factorial() expects parameter 1 to be integer, string given in Command line code on line 1 Call Stack: 0.2040 349464 1. {main}() Command line code:0 0.2040 349464 2. factorial() Command line code:1 


Since the body of the function seems to be working, we now implement the factorial calculation algorithm itself. As you know, the algorithm is based on a recursive call, we will do the same. We set the body of the function calculate () in the same file mathstat.c with its subsequent call.

 static long calculate(long number) { if(number == 0) { return 1; } else { return number * calculate(number - 1); } } PHP_FUNCTION(factorial) { int argc = ZEND_NUM_ARGS(); long number = 0; if (zend_parse_parameters(argc, "l", &number) == FAILURE) { RETURN_LONG(0); } number = calculate(number); RETURN_LONG(number); } 

We compile, restart, check.

 [root@localhost mathstat]# php -a Interactive mode enabled php > echo factorial(1); 1 php > echo factorial(2); 2 php > echo factorial(3); 6 php > echo factorial(4); 24 php > echo factorial(5); 120 


Surprisingly, it works. It turns out that to implement this function without basic knowledge how everything is arranged in PHP, and the C / C ++ language itself did not look from the university, it took me no more than 3-4 hours. The whole process of writing code resembles working in some kind of framework for PHP. All you need is to study the architecture of the framework and its API, and then work within its framework, the same thing here.

There is no particularly large code according to the described variant, but I will leave a link to github

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


All Articles