В PHP есть уже готовый инструмент ./ext_skel (находится в папке ext), который генерирует будущий шаблон (каркас) для расширения. Я не буду описывать все, что им генерируется и зачем (сам особо в этом ничего еще не понимаю и не знаю), а просто распишу минимальные правки, которую решат нашу задачу. Весь процесс происходит в CentOS 7.
Создаем каркас для будущего расширения mathstat, которое будет содержать функцию factorial().
[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
После выполнения команды создания расширения, будет выдана следующая вспомогательная информация.
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
В PHP7 файла buildconf после генерации у меня нет (наверное это остатки ранних версий PHP), но я знаю, что сейчас компиляция расширений начинается с команды phpize. Она “создает” кучу файлов, среди которых есть необходимый ./configure. Напомню, что пользовательский вариант компиляции расширения состоит в последовательном выполнении следующих команд.
Phpize -> ./configure -> make -> make test -> make install
Если сразу сделать эту последовательность команд, то make install по не ясным причинам будет ломаться и выдавать ошибку на копирование. Если кто в курсе, отпишите, в комментариях, почему так.
[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 создает файлы на основе описания config.m4. Это, как я понял, своеобразный декларативный способ описания того, каким будет расширение, будет ли оно подтягивать внешние исходники или нет и т.д… Поэтому просмотрев другие расширения PHP в исходниках, я просто решил его максимально упростить, чтобы минимизировать ошибки компиляций с чистого листа. Действую по принципу — ничего не хочу, «все галочки снимаю».
Открываем этот файл (config.m4) и оставляем только этот текст. Опция “--enable-mathstat” говорит о том, что это просто расширение без внешних исходников (библиотек) и который можно либо включить, либо выключить. (dnl означает комментирование строки)
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
Перезапускаем команду phpize.
[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
Далее, делаем знакомые команды:
./configure && make
make test — запустит один изначально созданный тест. Про эти тесты PHP я как то писал уже вкратце.
[root@localhost mathstat]# make install Installing shared extensions: /usr/local/lib/php/extensions/no-debug-non-zts-20151012/
В этот раз “make install” проходит, далее пробуем прописывать расширение в php.ini.
Определяем, где находится php.ini.
[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
Команда php -m (просматривает все установленные модули) говорит, что вроде бы все нормально, расширение mathstat подгрузилось.
Запускаем в текущей директории тестовый файл mathstat.php
[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]#
Отлично, что — то уже работает.
2. Начинаем реализовывать функцию factorial().
Редактируем файл mathstat.c для добавления функции factorial().
Для этого нужно добавить функцию в “список” mathstat и сделать на неё заглушку, через макрос. Делаю все по аналогии как в других расширениях.
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[] */ };
Реализация функции заглушки. Делается в обертке макроса. Как он работает в итоге, пока не ясно, оставляю изучение себе на будущее. Просто делаю в аналогичном формате.
PHP_FUNCTION(factorial) { RETURN_LONG(1000); }
В данной случае под каждый тип возвращаемых данных, свой вариант RETURN_. Поиск в интернете покажет все возможные варианты. У нас просто целое значение. Тут вроде все просто.
Далее повторяем 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...
Перезапуск php-fpm не показал, что что-то сломали и поэтому идем дальше и тестим наличие функции в расширении. Делаю на всякий случай, даже если компиляция прошла.
[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.
Наименование функции появилось и более того, теперь мы можем её уже вызывать из кода PHP.
[root@localhost mathstat]# php -a Interactive mode enabled php > echo factorial(1); 1000 php >
Видно, что функция вызвалась и вернула заранее указанное значение 1000.
Научим функцию принимать аргумент и его же отдавать, для этого необходимо сделать описание аргумента функции. Смотрим аналогии в других расширениях PHP (я смотрел bcmath). Куча макросов, но формат понятен, в принципе.
ZEND_BEGIN_ARG_INFO(arginfo_factorial, 0) ZEND_ARG_INFO(0, number) ZEND_END_ARG_INFO()
И добавляем его использование в функции. Если оставлять NULL, то умолчанию считается, что тип аргумента типа 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[] */ };
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); }
Здесь используется zend_parse_parameters, который проверяет переданные аргументы на тип используя формат в кавычках (""), затем по адресу задает принятое значение. Детали можно легко найти в интернете. Для задачи реализации факториала больших знаний пока не нужно.
Проверяем после перекомпиляции (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]#
Если передадим строку в аргументе, получим ошибку. Пока не ясно, как на самом деле все это работает до конца, но требуемая задача сделана.
[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
Так как тело функции вроде бы отрабатывает, реализуем теперь сам алгоритм расчета факториала. Как Вы знаете, алгоритм основан на рекурсивном вызове, сделаем тоже самое. Прописываем тело функции calculate() в этом же файле mathstat.c с последующим его вызовом.
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); }
Компилируем, перезапускаем, проверяем.
[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
Удивительно, но это работает. Получается, чтобы реализовать данную функцию без базовых знаний как там все устроенно в PHP, да и сам язык С/C++ не смотрелся с университета, мне понадобилось не более 3-4 часов. Весь процесс написания кода напоминает работу в каком то фреймворке для PHP. Все что нужно, это изучить архитектуру фреймворка и его API, а дальше работать в рамках его каркаса, тоже самое и здесь.
Особо большого кода по описанному варианту нет, но оставлю ссылку на github

