В 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
L-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
Комментарии (11)
Amareis
18.06.2016 23:00cp: cannot stat 'modules/*': No such file or directory
Вам действительно неясно что означает эта ошибка?..bizzonaru
19.06.2016 07:17Да, не ясно. Так как это директория есть и она видна. Поэтому я максимально упростил config.m4. Configure генерируется с логической ошибкой, более того, там внутри него есть используемые флаги (-qversion, -V) которые мой локальный gcc не понимает.
gcc version 4.8.5 20150623 (Red Hat 4.8.5-4) (GCC) configure:2688: $? = 0 configure:2677: cc -V >&5 cc: error: unrecognized command line option '-V' cc: fatal error: no input files compilation terminated. configure:2688: $? = 4 configure:2677: cc -qversion >&5 cc: error: unrecognized command line option '-qversion'
Amareis
19.06.2016 12:23Кроме того что эта директория должна существовать (кстати, в приведённом вами выводе
ls
её нет, она создаётся в цепочке./configure -> make -> make test
?), в ней ещё и должно что-нибудь лежать. Ну а эта ошибка скорее выглядит не как ошибка, а как перебор флагов для получения версии компилятора — они явно перебираются в цикле, на что недвусмысленно намекает одинаковый номер строк. Или на этой ошибке останавливается весь процесс?
akamajoris
20.06.2016 10:20+2Что в статье особенного? Ничего нового по сравнению с другими версиями пыхи. А без C++ вы не напишите ничего, кроме факториала и пары других простых алгоритмов. Расширения пишутся для конкретных задач, у которых нет быстрых решений на PHP.
mpakep
20.06.2016 18:49Статья открывает для многих его возможности. Многие пишут на данном языке, но не знают что критически важные места можно сильно оптимизировать. Не видел кто бы этим реально воспользовался, но раз существует такая возможность значит она кому то потребовалась. Спасибо. Очень интересная статья. Добавил в закладки.
POPSuL
22.06.2016 16:06Писал года 3-4 назад в своем личном бложике нубостатьи про разработку модулей для php, и почему-то не рискнул опубликовать их на хабре…
Не думаю что это кому-то окажется полезным, но немного про разработку модулей под PHP <7: https://popsul.name/blog/php%20extension/
Писал статьи на отъе… Поэтому это не реклама и не самый качественный туториал, а просто, материал который кому-то может быть поможет.
bolk
Если у всс это заняло 3 часа, вам лучше бы посмотреть на SWIG.
Obramko
С PHP 7 не работает: https://github.com/swig/swig/issues/571 .
AxisPod
Жесть какая, а потом еще таскать за собой подключаемые .php файлы вместе с расширением?