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.

 [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

Also popular now: