We are writing an extension for PHP (7.0.7) without knowledge of C / C ++ and how it works
- Tutorial
Is it possible to write my module (extension) to PHP without special knowledge that requires a lot of time to study the theory? If you know how to program in PHP itself, then writing the simplest C code will not be difficult, 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 on a habr for this question. This publication is for those who decided to delve into the source code of PHP, to look a little at its insides, with the aim of only a superficial study. At the moment I am exploring the same without the necessary knowledge. PHP interviews often ask you to write factorial code. We will write such a function 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 from the very beginning. On the Internet you can find many articles on this issue, most of them describe information using the zval “old” format, but I don’t think it would be worse if I add it myself.
PHP has a ready-made tool ./ext_skel (located in the ext folder) that generates a future template (wireframe) for the extension. I will not describe everything that they generate and why (I myself don’t understand anything about it and I don’t know), but I will simply write down the minimum edits that our task will solve. The whole process takes place in CentOS 7.
Create a framework for the future mathstat extension, which will contain the factorial () function.
After executing the create extension command, the following supporting information will be displayed.
In PHP7 I don’t have a buildconf file after generation (probably these are the remnants of earlier versions of PHP), but I know that now compilation of extensions starts with the phpize command. She “creates” a bunch of files, among which there is the necessary ./configure. Let me remind you that the custom version of compiling the extension consists in sequentially executing the following commands.
If you immediately make this sequence of commands, then make install will break for obvious reasons and give an error for copying. If anyone is in the know, write down in the comments why this is so.
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 sources, I just decided to simplify it as much as possible to minimize compilation errors from scratch sheet. I act on the principle - I do not want anything, "I remove all the checkmarks."
Open this file (config.m4) and leave only this text. The option “--enable-mathstat” says that this is simply an extension without external sources (libraries) and which can be turned on or off. (dnl means commenting a line)
Restart the phpize command.
Next, we make familiar commands:
make test - run one initially created test. About these PHP tests, I somehow wrote already briefly.
This time “make install” passes, then we try to register the extension in php.ini.
We determine where php.ini is located.
The php -m command (looks through all installed modules) says that everything seems to be fine, the mathstat extension is loaded.
Run the mathstat.php test file in the current directory
Great, something is already working.
2. We begin to implement the factorial () function.
Edit the mathstat.c file to add the factorial () function.
To do this, add the function to the “list” of mathstat and make a stub on it, through the macro. I do everything by analogy as in other extensions.
The implementation of the stub function. Made in a macro wrapper. How it works in the end is not yet clear, I leave the study to myself for the future. Just doing in a similar format.
In this case, for each type of returned data, its own version is RETURN_. A search on the Internet will show all possible options. We just have a whole meaning. Everything seems to be simple here.
Next, repeat make clean && make && make install
Restarting php-fpm did not show that something was broken and therefore go ahead and test the presence of the function in the extension. Just in case, even if the compilation has passed.
The name of the function has appeared and moreover, now we can already call it from the PHP code.
It can be seen that the function was called and returned a predetermined value of 1000.
We will teach the function to take an argument and give it away, for this it is necessary to make a description of the function argument. We look at the analogies in other extensions of PHP (I looked at bcmath). A bunch of macros, but the format is understandable, in principle.
And we add its use in function. If you leave NULL, then the default is considered to be an argument type of type int.
It uses zend_parse_parameters, which checks the passed arguments for type using the format in quotation marks (""), then sets the accepted value to the address. Details can be easily found on the Internet. For the task of implementing the factorial, great knowledge is not yet needed.
Check after recompilation (make clean && make && make install).
If we pass a string in the argument, we get an error. It is not yet clear how in fact all this works to the end, but the required task has been done.
Since the body of the function seems to be fulfilling, 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 write the body of the function calculate () in the same file mathstat.c with its subsequent call.
Compile, restart, check.
Surprisingly, this works. It turns out that to implement this function without basic knowledge of 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 that is needed 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 for the described option, but I will leave a link to github
PHP has a ready-made tool ./ext_skel (located in the ext folder) that generates a future template (wireframe) for the extension. I will not describe everything that they generate and why (I myself don’t understand anything about it and I don’t know), but I will simply write down the minimum edits that our task will solve. The whole process takes place in CentOS 7.
Create a framework for the future mathstat extension, 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 create extension command, 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 the remnants of earlier versions of PHP), but I know that now compilation of extensions starts with the phpize command. She “creates” a bunch of files, among which there is the necessary ./configure. Let me remind you that the custom version of compiling the extension consists in sequentially executing the following commands.
Phpize -> ./configure -> make -> make test -> make install
If you immediately make this sequence of commands, then make install will break for obvious reasons and give an error for copying. If anyone is in the know, write down in the comments why this is so.
[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 sources, I just decided to simplify it as much as possible to minimize compilation errors from scratch sheet. I act on the principle - I do not want anything, "I remove all the checkmarks."
Open this file (config.m4) and leave only this text. The option “--enable-mathstat” says that this is simply an extension without external sources (libraries) and which can be turned on or off. (dnl means commenting a 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 - run one initially created test. About these PHP tests, I somehow wrote already briefly.
[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.
We 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 through all installed modules) says that everything seems to be fine, the mathstat extension is 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 begin to implement the factorial () function.
Edit the mathstat.c file to add the factorial () function.
To do this, add the function to the “list” of mathstat and make a stub on it, through the 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. Made in a macro wrapper. How it works in the end is not yet clear, I leave the study to myself 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 version is RETURN_. A search on the Internet will show all possible options. We just have a whole meaning. Everything seems to be 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 go ahead and test the presence of the function in the extension. 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 has 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 can be seen that the function was called and returned a predetermined value of 1000.
We will teach the function to take an argument and give it away, for this it is necessary to make a description of the function argument. We look at the analogies in other extensions of PHP (I looked at bcmath). A bunch of macros, but the format is understandable, in principle.
ZEND_BEGIN_ARG_INFO(arginfo_factorial, 0)
ZEND_ARG_INFO(0, number)
ZEND_END_ARG_INFO()
And we add its use in function. If you leave NULL, then the default is considered to be an argument type 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[] */
};
We slightly correct the body of the function.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);
}
It uses zend_parse_parameters, which checks the passed arguments for type using the format in quotation marks (""), then sets the accepted value to the address. Details can be easily found on the Internet. For the task of implementing the factorial, great knowledge is not yet 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 in fact all this works to 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 fulfilling, 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 write 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);
}
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, this works. It turns out that to implement this function without basic knowledge of 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 that is needed 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 for the described option, but I will leave a link to github