- PVSM.RU - https://www.pvsm.ru -
Я пишу эту статью для того, чтобы путь, который у меня занял в общей сложности не меньше года, читатель смог пройти за пару часов. Как показал мой личный опыт, просто программировать на Си несколько легче, чем заставить работать серьезное расширение для PHP. Здесь я максимально подробно расскажу вам о том, как сделать расширение на примере библиотеки libtrie [1], реализующей префиксное дерево, более известное как trie. Я буду писать и параллельно выполнять описываемые действия на свежеустановленной системе Lubuntu 18.04.Начнем.
sudo apt install php7.2-dev
cd /tmp && wget http://it2.php.net/get/php-7.2.11.tar.gz/from/this/mirror -O php7.tar.gz
sudo tar -xvf php7.tar.gz -C /usr/local/src
Обычно я использую 2 редактора кода. Простой и быстрый geany и довольно тормозной, но очень продвинутый clion фирмы JetBrains. Geany установим из стандартной репы Убунту.
sudo apt install geany
Clion скачаем с официального сайта JetBrains:
cd ~/Downloads && wget https://download.jetbrains.com/cpp/CLion-2018.2.5.tar.gz -O clion.tar.gz
sudo tar -xvf clion.tar.gz -C /usr/share
Сделаем линк, чтобы было удобно запускать clion из консоли.
sudo ln -s /usr/share/clion-2018.2.5/bin/clion.sh /usr/bin/clion
После первого запуска clion сам создаст ярлыки для себя из меню оболочки LXpanel, но первый раз нужно запустить руками.
#запустим
clion
Тут у нас есть как минимум 3 варианта:
Третий вариант мне нравится больше всего, но я буду использовать второй, чтобы в случае неудачи минимизировать число мест, где я мог ошибиться. Хотя ковырять болванку разработчиков — то еще удовольствие :-)
cd /usr/local/src/php-7.2.11/ext
Скрипту кроме названия можно через файл proto задать некоторые параметры расширения. Все это можно не делать. Я буду все делать руками, но как работает proto покажу. Мы делаем trie, поэтому назовем наше расширение libtrie. Чтобы работать в каталоге /usr/local/src нужны привилегии администратора, чтобы без конца не писать sudo, я включу bash с повышенными правами.
sudo bash
array array_fill ( int $start_index , int $num , mixed $value )
Синтаксис в proto файле очень простой, нужно просто указать название функции. Я напишу немного побольше, чтобы выглядело информативнее.
echo my_array_fill ( int start_index , int num , mixed value ) >> libtrie.proto
./ext_skel --extname=libtrie --proto=./libtrie.proto
cd libtrie
config.m4 - тут хранится начальная конфигурация расшения на основании которой специальная программа phpize готовит скрипт ./configure, который создает конфигурацию для сборки расширения makefile.
CREDITS - пустой файл, в котором пишут кто автор, кого он благодарит
libtrie.c - тут основной код нашего расширения
php_libtrie.h - тут заголовочный файл расширения
config.w32 - тут начальная конфигурация для сборки расширения под windows
EXPERIMENTAL - пустой файл. Так и не разобрал, что в нем пишут.
libtrie.php - сгенерированный php файл для базовой проверки работоспособности расширения.
tests - тесты расширения
Для успешной сборки расширения необходимо всего 3 файла. В минималистской болванке расширения, которую я упомянул выше, есть только 3 файла.
config.m4
php_libtrie.h
libtrie.c
Стандартное именование принятое в php мне не нравится, я люблю, чтобы заголовочные файлы и файлы с телом программы назывались одинаково. Поэтому переименуем libtrie.c в php_libtrie.c
mv libtrie.c php_libtrie.c
Создаваемый по умолчанию файл config.m4 буквально напичкан контентом, обилие которого сбивает с толку и запутывает. Как я сказал, этот файл нужен для формирования configure скрипта. Подробнее об этом написано здесь [3].
geany config.m4 &
Оставляем только это:
PHP_ARG_ENABLE(libtrie, whether to enable libtrie support,
[ --enable-libtrie Enable libtrie support])
if test "$PHP_LIBTRIE" != "no"; then
# если понадобится включить какие-то дополнительные заголовочные файлы
# PHP_ADD_INCLUDE()
# ключевая строка
PHP_NEW_EXTENSION(libtrie, php_libtrie.c, $ext_shared)
# PHP_NEW_EXTENSION(libtrie, php_libtrie.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
fi
Первый макрос создает возможность включать и отключать наше расширение при запуске создаваемого скрипта configure.Второй блок — самый важный, он определяет какие файлы будут компилироваться в составе нашего расширения, будет ли расширение динамически подключаемым через .so файл, или расширение будет статичным и будет интегрировано при сборке php. Наше будет динамическим.Сохраняем файл.Скопируем файл в пользовательский каталог, чтобы не работать в режиме root.
#выход из рутового баша
exit
Копируем:
cp /usr/local/src/php-7.2.11/ext/libtrie ~/Documents/ -r
Напомню, что будем делать полный аналог array_fill(). Я буду работать через clion, но по этому руководству можно сделать в любом редакторе. Clion хорош тем, что позволяет автоматически делать базовую проверку синтаксиса, а также поддерживает быстрый переход по файлам или функциям через ctrl + click. Чтобы такие переходы работали в нашем расширении, придется настроить файл CMakeLists.txtДля правильной работы clion потребуется установка системы сборки cmake, вместе с которой установится еще куча зависимых пакетов. Установим все это командой:
sudo apt install cmake
Открываем наш каталог с раширением в clion. Создаем через контекстное меню по клику на названии корневого каталога в верхней части экрана файл CMakeLists.txt со следующим содержимым:
cmake_minimum_required(VERSION 3.12)
project(php-ext-libtrie C)
set(CMAKE_C_STANDARD 11)
# задаем переменную phproot, чтобы удобнее прописывать пути к файлам php
set(phproot /usr/local/src/php-7.2.11/)
# тут указываются каталоги, которые нужно включить в проект
# мы делаем это чтобы заставить clion понимать внутренние функции и макросы самого php
include_directories(${phproot})
include_directories(${phproot}TSRM/)
include_directories(${phproot}main/)
include_directories(${phproot}Zend/)
# без этой строки clion не сможет прочитать файл и не будет ничего индексировать
add_executable(php-ext-libtrie php_libtrie.c)
Может кто-то знает, как можно сделать этот файл короче, чтобы clion начал индексировать файлы проекта. Я короче способа не нашел. Если кто-то знает, напишите в комментариях.
Открываем наш файл с телом нашего расширения php_libtrie.c иудаляем все комментарии, чтобы они нас не путали.
Clion проверяет были ли объявлены все использованные в коде функции и макросы и вышибает ошибку, если это не так. Очевидно, что разработчики PHP не пользуются clion, а то наверняка бы что-то сделали с этим. Чтобы эти ошибки не выпадали в нашем расширении, включим недостающие заголовочные файлы к нам.Чтобы все упорядочить, я делаю так:
все include с заголовками из php_libtrie.c файла переношу в php_libtrie.h, в первом файле остается только 1 запись:
#include "php_libtrie.h"
В файле php_libtrie.h будут все остальные необходимые включения.
#ifndef PHP_LIBTRIE_H
#define PHP_LIBTRIE_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdarg.h> //тут для макроса va_start()
#include <inttypes.h> //тут стандартные числовые типы
//нужные константы
#if defined(__GNUC__) && __GNUC__ >= 4
# define ZEND_API __attribute__ ((visibility("default")))
# define ZEND_DLEXPORT __attribute__ ((visibility("default")))
#else
# define ZEND_API
# define ZEND_DLEXPORT
#endif
# define SIZEOF_SIZE_T 8 //нужна для макроса ZVAL_COPY_VALUE()
#ifndef ZEND_DEBUG
#define ZEND_DEBUG 0
#endif
//тут декларации того, что используется в нашем расширении
#include "php.h"
#include "php_ini.h"
#include "zend.h"
#include "zend_types.h" //ZVAL_COPY_VALUE
#include "ext/standard/info.h"
#include "zend_API.h"
#include "zend_modules.h"
#include "zend_string.h"
#include "spprintf.h"
extern zend_module_entry libtrie_module_entry;
...
Если все сделано правильно, то проверяльщик clion покажет в правом верхнем углу желтый или зеленый квадрат, который означает, что критических ошибок нет. 
Для нормальной работы расширения необходимо 2 вещи:
zend_module_entry libtrie_module_entry = {
STANDARD_MODULE_HEADER, //стандартный заголовок
"libtrie", //название расширения
libtrie_functions, //название массива с функциями расширения
PHP_MINIT(libtrie), //функция, запускаемая при включении расширения
PHP_MSHUTDOWN(libtrie), //функция при выключении
PHP_RINIT(libtrie), /* Replace with NULL if there's nothing to do at request start */
PHP_RSHUTDOWN(libtrie), /* Replace with NULL if there's nothing to do at request end */
PHP_MINFO(libtrie), //видимо то, что будет показывать php в phpinfo()
PHP_LIBTRIE_VERSION, //версия расширения, установлена в заголовочном файле
STANDARD_MODULE_PROPERTIES //не знаю что это
};
const zend_function_entry libtrie_functions[] = {
PHP_FE(confirm_libtrie_compiled, NULL) /* For testing, remove later. */
PHP_FE(my_array_fill, NULL)
PHP_FE_END /* Must be the last line in libtrie_functions[] */
};
Первая строка — тестовая функция, которая будет выдавать, что расширение работает, вторая — наша функция, третья — специальный макрос, который должен быть последним в этом массиве.
Находим нашу функцию:
PHP_FUNCTION(my_array_fill)
Как видно она тоже инициализируется через макрос. Все дело в том, что все функции php ничего не возвращают (если быть точным возвращают void) внутри Си, а их аргументы нельзя изменить. Где-то это даже удобно.Если посмотреть в структуре файла (часть окна слева), тут перечислены все функции файла, но уже в том виде, в котором они будут после прекомпиляции макросов. На скриншоте видно, что наша функция my_array_fill на самом деле будет zif_my_array_fill.
Аргументы из недр php в нашу Си функцию мы получаем макросом. Подробнее об этом макросе можно посмотреть в файле:
/usr/local/src/php-7.2.11/README.PARAMETER_PARSING_API
Ниже приведен код нашей функции-аналога с подробными пояснениями.
PHP_FUNCTION(my_array_fill)
{
//сначала объявляем все переменные, которые нам тут понадобятся
//в любую функцию передаются 2 аргумента:
//указатели zend_execute_data *execute_data, zval *return_value
//через первый указатель функция получает аргументы, а через второй отдает данные
//zend_long это int64_t на x64 системах и int32_t на x86 системах
//число передается в функцию с типом zend_long
zend_long start_index; //1 аргумент число,
zend_long num; //2 тоже число
zval *value; //поскольку у нас mixed тип, то берем zval, которые может хранить любой тип
//получаем аргументы в объявленные переменные, тут сразу проходит проверка на количество и тип аргументов
if (zend_parse_parameters(ZEND_NUM_ARGS(), "llz",
&start_index, &num, &value) == FAILURE) {
/*наши функции ничего не выводят
* поэтому все макросы RETURN_ просто пишут в
* return_value результат и прерывают функцию */
RETURN_FALSE;
}
//проводим проверку второго аргумента, где задается кол-во выводимых элементов массива
if (num <= 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "argument 2 must be > 0"); //очередной макрос для вывода ошибки
RETURN_FALSE;
}
//zval *return_value уже есть, поэтому сразу в нем и инициализируем массив
//этот макрос принимает на входе указатель на zval, в котором надо сделать массив, и кол-во элементов
//приводим тип из zend_long в unsigned int32.
// Размер ключей массива первое значение + кол-во. Т.е. если первое 1, а надо всего 3, то массив будет из 4 элементов
array_init_size(return_value, (uint32_t)(start_index + num));
//добавляем через цикл, начиная с начального, заканчивая последним
for(zend_long i = start_index, last = start_index + num; i < last; ++i) {
//копируем указатель нашего zval со входа в каждый элемент массива
add_index_zval(return_value, i, value);
}
//функция ничего не возвращает, а массив уже записан в return_value
return;
}
Сначала запускаем phpize, который сделает нам configure файл.
phpize
Теперь запускаем ./configure, который сделает makefile.
./configure
Наконец запускаем make, который соберет нам наше расширение.
make
Проверим, что у нас получилось.
# Это заставит наш php, подключить наше скомпилированное расширение из каталога
# modules. Ключ -a заставит php работь в режиме командной строки
php -d extension=modules/libtrie.so -a
Вводим в консоли php:
print_r(my_array_fill(50, 2, "hello, baby!"));
Наслаждаемся результатом.
Кто-то спросит, а где же тут trie? О функциях, реализующих работу trie, я напишу во второй части. Stay tuned.
Автор: johovich
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/tutorial/297976
Ссылки в тексте:
[1] libtrie: http://github.com/legale/libtrie
[2] отсюда: https://github.com/c9s/php-ext-skeleton
[3] здесь: http://php.net/manual/en/internals2.buildsys.configunix.php
[4] Источник: https://habr.com/post/428626/?utm_source=habrahabr&utm_medium=rss&utm_campaign=428626
Нажмите здесь для печати.