Пишемо під розширення PHP (7.0.7) без знань про С/C++ і як це взагалі працює

Можна написати свій власний модуль (розширення) до PHP без особливих знань, що вимагають великого часу вивчення теорії? Якщо вмієш програмувати на самому PHP, то написати найпростіший код на С не складе особливих труднощів, тим більше, що PHP дозволяє генерувати каркас під розробляється розширення, в рамках якого потім пишеш код. Є ще набирає популярність зефір на хабре для цього питання. Дана публікація для тих, хто вирішив покопатися в исходниках PHP, трохи подивитися його нутрощі, переслідуючи мету лише поверхневого дослідження. В даний момент я той же самий досліджувати без необхідних знань. На співбесідах по PHP часто просять написати код підрахунку факториала. Ось таку функцію і ми напишемо зараз на З, яку потім можна викликати з коду PHP. Я буду описувати дії, які я робив і при цьому нічого не знаю спочатку по цій частині. В інтернеті можна знайти багато статей з цього питання, більшість з них описує інформацію з використанням zval «старого» формату, але я не думаю, що буде гірше, якщо і я ще додам від себе.

В 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" != "ні"; 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 now is 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 now is 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, а далі працювати в рамках його каркаса, теж саме і тут.
Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.