Пишем расширения для PHP Часть I: Введение в PHP и Zend
Пятница, Январь 14th, 2011 | Программирование
Это моя первая переводная статья
Начать я решил именно с перевода серии англоязычных статей посвященных написанию расширения для PHP на С, потому что именно этим в данный момент я и занимаюсь, а так как информации в рунете об этом практически нет, я думаю, мой труд кому-нибудь обязательно пригодиться.
Ссылка на оригинальную статью.
Введение
Если Вы читаете это, то скорее всего Вам по какой-либо причине надо реализовать свое собственное расширение (extension) для PHP или Вам это просто интересно.
Данное руководство предполагает, что вы знакомы с двумя языками: PHP и С (на нем написан интерпретатор для PHP).
Начнем с того, что попытаемся представить для чего может понадобиться написать собственное расширение для PHP:
- Есть какая-то библиотека или системная функция, которую Вам необходимо использовать/вызвать, но из-за «высокой» степени абстракции PHP Вы этого сделать просто не в состоянии.
- Вы хотите заставить PHP вести себя каким-то необычным образом.
- У Вас уже есть какой-то написанный PHP код, но вы знаете, что он мог бы работать быстрее и требовать меньше памяти, если его переписать как расширение PHP на C.
- У Вас есть какой-то код на PHP, который вы хотите продать, и при этом не хотите отдавать исходники.
Для того чтобы создать свое расширение PHP, для начала, необходимо понять, что такое расширение для PHP.
Что такое расширение для PHP?
Если вы использовали раньше PHP, то вы, почти со 100% вероятностью, пользовались расширениями. Лишь небольшое количество функции в PHP реализовано не в расширениях. Большинство функций – часть стандартного расширения – их около 400. PHP в базовой комплектации поставляется с 86 расширениями, каждое из которых, в среднем, содержит около 30 функций (около 2500 функций). Если этого недостаточно, то репозиторий PECL предлагает еще около 100 дополнительных расширений, не говоря уже о том, что их можно просто найти Интернете.
Ядро PHP сделано в виде двух отделенных друг от друга частей.
На нижнем уровне – the Zend Engine (ZE). ZE парсит PHP скрипт в последовательность понимаемых машиной токенов, а затем исполняет эти токены в текущем процессе. Также в ZE есть менеджер памяти (он занимается выделением/высвобождением памяти), пространство переменных (контекст), и обработчик вызовов функций.
Другая часть – ядро PHP. PHP осуществляет взаимодействие и реализует связи со слоем SAPI (Server Application Programming Interface, чаще всего употребляется для указания сервера, на котором выполняются скрипты – Apache, IIS, CLI, CGI, и т.д). Также ядро реализует унифицирующую прослойку для контроля safe_mode и open_basedir, операций с потоками (файлы + сеть), т.к. они используют единые функции чтения ( fread() ), записи( fwrite() ), открытия потока ( fopen() ).
Жизненный цикл скрипта
Когда SAPI запускается, например, в ответ на команду /usr/local/apache/bin/apachectl start, PHP начинает инициализировать подсистемы ядра. Перед окончанием начальной(стартовая) инициализации, ядро загружает расширения, вызывая при этом функцию инициализации расширения Module Initialization (MINIT). Это дает возможность каждому из них провести инициализацию: внутренних переменных, занять требуемые ресурсы, зарегистрировать хэндлы (handlers) ресурсов, зарегистрировать собственные функции в ZE, чтобы при вызове какой-либо функции расширения в скрипте ZE знал какой код необходимо выполнить.
Далее, PHP ожидает пока SAPI примет и обработает запрос от браузера на получение страницы. В случае если PHP запускается как CGI или CLI SAPI, то это происходит мгновенно и только один раз. В случае если речь идет о Apache, IIS или другом полноценном веб-сервере SAPI, то это происходит тогда, когда пользователи запрашивают страницы. К тому же, запросы повторяется любое количество раз, и, разумеется, могут приходить одновременно. Не важно каким образом пришел запрос, PHP запрашивает ZE, чтобы тот в свою очередь настроил окружение, в котором будет запущен скрипт, вызвал у каждого из расширений функцию инициализации Request Initialization (RINIT) (здесь идет речь не о стартовой инициализации, а о той, что выполняется при каждом запросе пришедшем от браузера к серверу, который, в свою очередь, обработал и перенаправил его к нам в скрипт).
Вызов функции RINIT дает возможность расширению настроить значения каких-то переменных окружения, занять какие-то специфические ресурсы, или выполнить другие задачи, например, аудит. Ярким примером, того что можно включить в функцию инициализации RINIT – небольшая автоматизация усилий, если флаг session.auto_start поднят, то RINIT автоматически вызывает функцию session_start() и предварительно заполняет переменную $_SESSION.
Как только функции RINIT всех подключенных в данный момент расширений будут выполнены, ZE переводит PHP код в токены, а затем в коды операций (opcodes), по которым он cможет пройти и выполнить их. Если одной из этих операций потребуется вызвать функцию расширения, то ZE упакует переданные аргументы для данной функции и передаст ей управление до тех пор пока она не завершится.
После того как выполнение скрипта будет завершено, PHP вызовет для каждого загруженного расширения функцию очистки Request Shutdown (RSHUTDOWN) (здесь, например, может выполняться сохранение сессионных переменных на диск). Затем, ZE запустит сборщик мусора, который вызовет для каждой переменной использованной в предыдущем запросе функцию unset().
Как только сборщик мусора сделает свое грязное дело, PHP перейдет в режим ожидания следующего запроса от SAPI на обработку скрипта веб-страницы, или сигнала отключения. В случае CGI или CLI SAPI, не существует «следующего запроса», поэтому SAPI инициирует отключение незамедлительно. В течении отключения, PHP снова проходит по списку загруженных расширений и вызывает для каждого из них функцию деинициализации Module Shutdown (MSHUTDOWN). И, наконец, PHP выгружает подсистемы ядра.
Этот процесс может показаться Вам, на первый взгляд, сложным, но как только Вы погрузитесь в разработку расширений, все для Вас постепенно прояснится.
Операции с памятью
Для того чтобы избежать утечек памяти в плохо написанных расширениях, ZE реализует свой собственный менеджер памяти, используя при этом дополнительный флаг, необходимый для определения времени жизни отведенного участка памяти. Суть этого флага в том, чтобы эффективно и корректно удалять отведенную вашим расширением память, а если быть точным, то менеджер памяти должен знать когда «подчистить за вами мусор» (если вы этого сами не сделали) после выполнения генерации страницы или в момент завершения SAPI и деинициализации расширений.
Перманентное отведение памяти означает, что память будет высвобождена при деинициализации PHP перед завершением работы SAPI. Напротив, не перманентно отведенная память (не путать с обычными функциями отведения памяти в C) будет освобождена в конце запроса, в котором она была отведена, в независимости от того была ли вызвана функция высвобождения памяти или нет (если нет, то за вас это сделает сборщик мусора, являющийся частью менеджера памяти ZE). Под пользовательские переменные, определенные в скрипте, память отводятся не перманентно, так как их значения к концу запроса становятся бесполезными, то память отведенная под них будет высвобождена сборщиком сборщиком мусора по окончании обработки данного запроса.
Расширения могут «положиться» на то, что ZE сам высвободит не перманентную память, которую они отвели в рамках запроса, но это не рекомендуется. Врядли участки памяти не востребованные в течении «длительных» периодов времени и ресурсы ассоциированные с этой памятью будут высвобождены корректно, поэтому рекомендуется «прибираться за собой» высвобождать отведенную в пределах запроса память. Как вы поймете позднее, достаточно легко убедиться в том, что вся отведенная память была высвобождена корректно.
Давайте сравним традиционные функции отведения памяти (эти функции должны быть использованы только при взаимодействии с внешними библиотеками) с функциями отведения постоянной (перманентной) и не постоянной памяти.
| Традиционные | Не постоянные | Постоянные |
|---|---|---|
malloc(count)calloc(count, num) |
emalloc(count)ecalloc(count, num) |
pemalloc(count, 1)*pecalloc(count, num, 1) |
strdup(str)strndup(str, len) |
estrdup(str)estrndup(str, len) |
pestrdup(str, 1)pemalloc() & memcpy() |
free(ptr) |
efree(ptr) |
pefree(ptr, 1) |
realloc(ptr, newsize) |
erealloc(ptr, newsize) |
perealloc(ptr, newsize, 1) |
malloc(count * num + extr)** |
safe_emalloc(count, num, extr) |
safe_pemalloc(count, num, extr) |
* Семейство функций pemalloc() включает флаг «постоянства», который позволяет им вести себя как их «не постоянные коллеги».
Например, emalloc(1234) это тоже самое что и pemalloc(1234, 0).
**safe_emalloc() и (в PHP 5) safe_pemalloc() осуществляют дополнительную проверку, чтобы избежать переполнения integer’а.
Подготавливаем среду для разработки
Теперь, когда мы рассмотрели немного теории о том как работает PHP и Zend Engine, я могу поспорить, что Вам уже хочется начать что-то писать. Но перед тем как вы что-либо будете делать, вам необходимо собрать определенный набор инструментов для разработки и настроить окружение сообразно вашим целям.
Первое, что вам понадобиться – сам PHP, и набор инструментов необходимых, чтобы его скомпилировать. Если вам никогда не доводилось собирать PHP из исходников, то я вам предлагаю взглянуть на http://www.php.net/install.unix (Разработка расширений PHP под Windows будет рассмотрена в одно из следующих статей). «Почему бы не использовать установленный скомпилированный PHP?» – спросите вы, дело в том, что при его построении небыли переданы два очень важных для процесса разработки ./configure ключа.
Первый –enable-debug, эта опция соберет PHP с дополнительной символической информацией, таким образом в случае возникновения ошибки segmentation fault, вы сможете получить дамп и воспользоваться gdb, чтобы определить где и почему произошла ошибка.
Следующая ./configure опция различается для разных версий PHP. В PHP 4.3 эта опция называется –enable-experimental-zts, в PHP 5 и более поздних это --enable-maintainer-zts. Эта опция заставит PHP думать, что он запущен в много поточной среде и Вы сможете отловить ошибки, которые были безвредны в одно поточном режиме, но сделали ваше расширение непригодным в многозадачной среде.
После того как вы скомпилировали PHP используя эти дополнительные опции и установили его на свой компьютер, вы можете начинать писать свое первое расширение для PHP.
«Hello world»
Как же можно обойтись без пресловутого Hello World?! В нашем случае мы сделаем небольшое расширение с единственной экспортируемой функцией, которая возвращает строку «hello world». Вот как это бы выглядело в PHP:
<?php
function hello_world() {
return 'Hello World';
}
?>А теперь давайте превратим это в расширение для PHP. Во-первых, создайте папку с именем hello в директории ext исходников PHP и перейдите в нее. На самом деле эта папка может располагаться где угодно – как в дереве каталогов исходников PHP, так и вне его, однако я хочу чтобы вы поместили его именно туда, т.к. это немного упростит нам жизнь
. Здесь вам необходимо создать 3 файла:
- Собственно исходник (hello.c) с реализацией функции hello_world;
- Заголовочный файл (php_hello.h), содержащий необходимые PHP ссылки для того чтобы он смог загрузить его как расширение;
- Конфигурационный файл (config.m4) для phpize, которое займется подготовкой вашего расширения к компиляции.
Итак, содержимое вышеописанных файлов:
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_FUNCTION(hello_world);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_hello.h"
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
{NULL, NULL, NULL}
};
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
NULL,
NULL,
NULL,
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_FUNCTION(hello_world)
{
RETURN_STRING("Hello World", 1);
}Как вы видите, большая часть кода – это настройка своеобразного протокола, который PHP использует для взаимодействия с собственными расширениями. И только последние четыре строчки – та самая функция hello_world, представленная в слегка непривычном виде (из-за использованных макросов PHP_FUNCTION и RETURN_STRING). Эти макросы призваны упростить написание расширений для PHP, поэтому их не стоит бояться к ним надо просто привыкнуть( в последствии вы ими будете пользоваться очень часто).
В принципе, из названия макросов следует их предназначение (я бы даже сказал, что они максимально пытаются приблизить нас к PHP реализации функции hello_world), однако, я думаю вас мучает вопрос «Что значит последний параметр макроса RETURN_STRING и почему он равен единице?«.
Напомним, что ZE включает в себя сложный механизм управления памятью, который следит за тем, чтобы вся выделенная память была освобождена сразу после того как скрипт завершит свое выполнение. Самой большой проблемой при использовании менеджеров памяти является двойное освобождение одного и того же участка памяти. Это наиболее частая причина появление «segmentation faults» в Linux.
Аналогично, я не думаю, что вы хотите чтобы ZE освободил статический буфер, в котором содержится строка «Hello world», сразу после того как выполнение данной функции завершиться, а переданный указатель на строку окажется недействительным, из-за того что он указывает на область памяти не принадлежащую ни одному из процессов. Поэтому, макрос RETURN_STRING может произвести копирование переданной ему строки, чтобы впоследствии корректно высвободить память, в которой находилась копия строки. Именно это и означает последний параметр макроса RETURN_STRING :
- 1 – копировать строку;
- 0 – не копировать строку, а вернуть указатель.
Но так как не принято, чтобы внутренние функций отводили память для строки, заполняли ее содержимым, а потом возвращали ее, поэтому и был создан макрос RETURN_STRING, который позволяет сделать или не сделать копию возвращаемой строки.
В следующем примере приведены действия идентичные вызову макроса RETURN_STRING с параметрами («Hello World», 1):
PHP_FUNCTION(hello_world)
{
char *str;
str = estrdup("Hello World");
RETURN_STRING(str, 0);
}В данном случае вы вручную отводите память для строки «Hello World» и заполняете ее с помощью функции estrdup (которая, фактически отводит память, при этом помечая ее на удаление сразу после завершения выполнения скрипта, и дублирует в нее строку). Затем, посредством вызова макроса RETURN_STRING() с переданными ему параметрами: указателем на отведенную память и 0 (это означает, что мы не делаем копию строки, а просто «пробрасываем» указатель), передаем строку в вызвавший данную функцию скрипт.
Собираем расширение
Итак, наконец-то мы добрались до последнего круга ада
. Последним шагом в нашем упражнении станет компиляция нашего расширения в библиотеку. Если вы скопировали содержимое трех описанных ранее файлов, то компиляция должна пройти всего в 3 команды (запускать их надо из директории ext/hello):
- $ phpize
- $ ./configure --enable-hello
- $ make
После выполнения всех трех команд, в папке ext/hello/modules/ должен лежать фаил hello.so. Теперь, как и для других расширений для PHP, вы нужно просто скопировать ваше расширение в папку расширений PHP (по-умолчанию это /usr/local/lib/php/extensions/, если ее там нет то путь до нее вы можете узнать в php.ini) и добавьте строку «extension=hello.so» в ваш php.ini, чтобы при старте PHP без проблем мог ее «подхватить». Для CGI/CLI SAPIs это означает просто следующий запуск PHP интерпретатора, а для веб-серверов, таких как Apache, это означает время его следующего перезапуска. Давайте проверим подключилось наше расширение или нет:
$ php -r 'echo hello_world();'Признаком того, что чудо все-таки произошло является выведенное на экран сообщение «Hello World«, так как наша функция hello_world() возвращает именно эту строку.
Для того чтобы вернуть в качестве результата функции другой скаляр существуют соответствующие макросы:
- целочисленные значения – RETURN_LONG();
- вещественные числа – RETURN_DOUBLE();
- true / false – RETURN_BOOL();
- NULL (кстати, это отдельный тип в PHP) – RETURN_NULL().
Давайте рассмотрим на примере как работать с каждым из них. Для каждого из макросов создадим по функции, которая будет доступна из PHP, делается это посредством добавления нескольких записей PHP_PE() в структуру function_entry в файле hello.c и добавлением реализаций новых функций в конец все того же hello.c, заголовок которых «обернут» в макрос PHP_FUNCTION().
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
PHP_FE(hello_long, NULL)
PHP_FE(hello_double, NULL)
PHP_FE(hello_bool, NULL)
PHP_FE(hello_null, NULL)
{NULL, NULL, NULL}
};
PHP_FUNCTION(hello_long)
{
RETURN_LONG(42);
}
PHP_FUNCTION(hello_double)
{
RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
RETURN_NULL();
}Помимо этого необходимо добавить прототипы для новых функции в php_hello.h, так как мы это сделали для hello_world(), вот так это должно выглядеть:
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);Так как мы ничего не меняли в файле config.m4, то технически можно пропустить шаги phpize и ./configure, и перейти сразу к make. Однако, в данном случае я вам рекомендую пройти все эти три шага снова, чтобы точно быть уверенным в том, что у вас все удачно собралось. Также стоит добавить, что надо вызвать не просто make, а make clean all, дабы убедиться в том, что все файлы пересобрались корректно. Опять таки, повторюсь, что это в этом нет необходимости (так как изменения, которые мы внесли не должны сказаться на процессе компиляции), но так будет надежнее. Как только новая библиотека будет собрана, вам надо будет снова скопировать ее в папку с расширениями PHP, заменив ею старую.
Теперь запустите интерпретатор PHP и проверьте подключилась ли ваше расширение или нет. Я подожду…
Закончили? Отлично. Если вы использовали var_dump() вместо echo, то скорее всего вы заметили, что функция hello_bool() вернула true. Вот, что такое была та самая единичка, переданная RETURN_BOOL() в качестве параметра. Как и в PHP, 0 эквивалентно FALSE, 1 – TRUE.
Многие авторы расширений используют 1, и вас призывают делать тоже самое, но не стоит зацикливаться на этом. Для повышения читабельности кода существуют два макроса RETURN_TRUE и RETURN_FALSE ( Я думаю, вы догадались, что они делают
). Вот как бы выглядела функция hello_bool(), если бы мы воспользовались RETURN_TRUE:
PHP_FUNCTION(hello_bool)
{
RETURN_TRUE;
}Обратите внимание, не требуется никаких круглых скобок, именно этим RETURN_TRUE и RETURN_FALSE отличаются от других RETURN_*() макросов.
Возможно вы обратили внимание и на то, что ни в одной из новых функций в макрос RETURN_*() мы не передали флага, которых бы «говорил» нужно ли делать копию данного значения или нет. Это потому, что для возвращения результата в виде скалярной величины дополнительного выделения памяти не требуются (исключение составляют переменные, которые сами по себе являются контейнерами, но об этом мы поговорим в следующей статье).
Также существуют еще три типа данных, используемых в PHP:
- RESOURCE - возвращается например mysql_connect(), fsockopen() и т.д.;
- ARRAY - также известный как HASH;
- OBJECT - возвращаемый при вызове new;
Их мы рассмотрим в следующей статье.
Параметры расширения в php.ini
ZE позволяет работать с параметрами определенными в php.ini двумя способами. Сначала мы рассмотрим простой вариант работы с INI-параметрами. Более сложный способ мы рассмотрим чуть позже, когда разберемся как работать с глобальными переменными.
Предположим вы хотите определить параметр hello.greeting для вашего расширения в php.ini, который будет содержать текст приветственного сообщения для функции hello_world(). Итак, для этого вам придется внести несколько дополнений в hello.c и php_hello.h, а также внести пару ключевых изменений в определение структуры hello_module_entry.
Начнем с добавления следующих прототипов для новых функций в файл php_hello.h:
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);Теперь давайте подкорректируем определение hello_module_entry в файле hello.c:
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
NULL,
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
PHP_INI_END()
PHP_MINIT_FUNCTION(hello)
{
REGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(hello)
{
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}Затем добавьте еще один #include в файл hello.c, дабы обеспечить доступ к INI значениям в php.ini:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"Наконец, давайте немного изменим нашу функцию, чтобы приветственное сообщение, которое она возвращает она брала из определенного нами параметра в php.ini:
PHP_FUNCTION(hello_world)
{
RETURN_STRING(INI_STR("hello.greeting"), 1);
}Заметьте, мы не просто возвращаем значение параметра из php.ini (как вы уже наверно догадались, делаем мы это при помощи макроса INI_STR(), у него единственный аргумент – имя параметра), мы его копируем, и только потом возвращаем в качестве результата (вот что означает 1, переданная в качестве последнего параметра RETURN_STRING). Если вы попытаетесь изменить строку, которую вернул макрос INI_STR(), то скорее всего PHP начнет себя вести нестабильно и, вероятно, упадет.
Первый набор изменений в этой части представлен двумя новыми методами, с которыми вы позднее познакомитесь более детально: MINIT и MSHUTDOWN. Как я уже упоминал ранее, эти методы вызываются при начальной инициализации слоя SAPI и при его окончательной деинициализации. Они не вызываются ни между запросами, ни в течении них. В этом примере мы их используем для того, чтобы зарегистрировать параметры php.ini в нашем расширении.
В нашей функции hello_world() мы воспользовались INI_STR(), чтобы получить текущее значение записи hello.greeting из php.ini в виде строки. Для получения целочисленных значений, значений с плавающей точкой или булевских значений из php.ini существуют соответствующие макросы: INI_INT(), INI_FLT(), INI_BOOL().
Не для кого не секрет, что значения загруженные из php.ini могут быть изменены как во время выполнения скрипта, посредством вызова функции ini_set(), так и в файле .htaccess. Для того чтобы получить оригинальные значения из php.ini существует набор скриптов: INI_ORIG_STR(), INI_ORIG_INT(), INI_ORIG_FLT(), INI_ORIG_BOOL().
Для наглядности я объединил макросы для получения текущего значения, макросы для получения оригинального значения и тип значений, которые они возвращают, в единую таблицу:
| Текущее значение | Оригинальное значение | Тип |
| INI_STR(name) | INI_ORIG_STR(name) | char * |
| INI_INT(name) | INI_ORIG_INT(name) | long |
| INI_FLT(name) | INI_ORIG_FLT(name) | double |
| INI_BOOL(name) | INI_ORIG_BOOL(name) | zend_bool |
Первый параметр переданный в PHP_INI_ENTRY() – строка с именем записи использованной в php.ini. Чтобы избежать коллизий имен записей в php.ini вы должны следовать тем же правилам, что и при именовании функции – добавлять префикс к имени(имя вашего расширения, так как мы, например, сделали с «hello.greeting«).
Второй параметр – начальное значение, оно подается всегда в виде строки вне зависимости от того число это или нет. В любом случае, значения в php.ini хранятся в текстовом виде, а макросы INI_INT(), INI_FLT(), INI_BOOL() лишь конвертируют значения из строкового в соответствующий тип.
Третий параметр – уровень доступа, на деле – битовая маска, ее содержимое определяет где и когда значения данной переменной может быть изменено.
Например, абсолютно бессмысленно разрешать менять значение register_globals во время выполнения скрипта функцией ini_set(), потому что значение этой переменной используется только при инициализации запроса к PHP скрипту, т.е. до момента начала его выполнения. В тоже время allow_url_fopen – скорее административная опция, поэтому вы врятли захотите, чтобы пользователи shared-хостинга могли ее изменять функцией ini_set() из скрипта или из файла .htaccess.
Чаще всего этот параметр выставляют в PHP_INI_ALL – это значит, что данное значение может менять кто угодно и где угодно. Также существует маска PHP_INI_SYSTEM|PHP_INI_PERDIR, позволяющая изменять данную опцию в php.ini или через файл .htaccess, но не через ini_set(). Ну и как вы уже наверно догадались, PHP_INI_SYSTEM – можно изменять только в php.ini.
Мы пока пропустим четвертый параметр, упомянем лишь то, что он используется для передачи ссылки на callback функцию играющую роль триггера, и вызываемую при изменении значения данной записи с помощью функции ini_set(). Зачем это нужно? Например, в триггере можно осуществить контроль устанавливаемого значения, подготовиться к корректной обработке последующих вызовов функции данного расширения.
Глобальные переменные
Часто возникает необходимость объявить переменную с временем жизни равным времени выполнения веб-запроса и чтобы изменять ее можно было только в пределах этого запроса (несколько запросов могут выполняться параллельно, поэтому необходимо, чтобы эта переменная была для каждого запроса своя). В контексте расширений PHP такая переменная называется глобальной. Если бы скрипт выполнялся в однопоточной среде SAPI, то достаточно объявить переменную в исходном коде расширения и обращаться к ней когда нужно. Проблема в том, что PHP спроектирован так, чтобы работать на многопоточных веб-серверах (таких как Apache2 и IIS), поэтому необходимо изолировать глобальные переменные используемые одним потоком от глобальных переменных другого потока.
В PHP существует специальный слой абстракции основная задача, которого – упростить процесс разделения глобальных переменных для разных потоков его называют TSRM (Thread Safe Resource Manager), иногда его называют ZTS (Zend Thread Safety). На самом деле вы уже пользовались TSRM и даже не знали этого:).
Давайте рассмотрим процесс создания глобальной переменой типа long с начальным значение 0. При каждом вызове hello_long() мы будем ее увеличивать на 1 и возвращать в качестве результата. Добавьте следующий код в php_hello.h сразу после #define PHP_HELLO_H:
#ifdef ZTS
#include "TSRM.h"
#endif
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
ZEND_END_MODULE_GLOBALS(hello)
#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endifТакже нам потребуется метод RINIT, поэтому добавим его в хедер:
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);Теперь давайте переместимся в hello.c и добавим одну строчку кода сразу после инклудов:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
ZEND_DECLARE_MODULE_GLOBALS(hello)Добавьте PHP_RINIT(hello) в определение hello_module_entry:
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
PHP_RINIT(hello),
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};Немного изменим код функции MINIT и заодно добавим парочку новых функции:
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
}
PHP_RINIT_FUNCTION(hello)
{
HELLO_G(counter) = 0;
return SUCCESS;
}
PHP_MINIT_FUNCTION(hello)
{
ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);
REGISTER_INI_ENTRIES();
return SUCCESS;
}Ну а теперь немного изменим функцию hello_long(), так чтобы она могла использовать нашу глобальную переменную:
PHP_FUNCTION(hello_long)
{
HELLO_G(counter)++;
RETURN_LONG(HELLO_G(counter));
}Давайте разберемся что и зачем мы изменили/добавили.
Внося добавления в php_hello.h мы воспользовались двумя ранее не рассмотренными макросами ZEND_BEGIN_MODULE_GLOBALS() и ZEND_END_MODULE_GLOBALS(), а нужны они нам были для того, чтобы объявить структуру zend_hello_globals, содержащую единственное поле типа long. Затем мы определили макрос HELLO_G(), который отвечает за унификацию доступа к глобальными переменным как в многопоточном так и в однопоточном окружении.
В hello.c мы использовали макрос ZEND_DECLARE_MODULE_GLOBALS(), который фактически объявляет структуру zend_hello_globals как «настоящую» глобальную переменную (в случае если это однопоточное окружение), или как член пула ресурсов текущего потока (если мы находимся в многопоточном окружении).
Нам, как авторам расширения, не стоит беспокоиться о различиях при обращении к глобальным переменным, которые скомпилированы в однопоточной или многопоточной среде, Zend Engine позаботится об этом.
Наконец в MINIT мы использовали макрос ZEND_INIT_MODULE_GLOBALS(), который получает потокобезопасный идентификатор ресурса.
Возможно вы заметили, что php_hello_init_globals() ничего не делает, а нашу глобальную переменную (счетчик) мы обнуляем в RINIT. Почему?
Ответ на этот вопрос кроется в порядке вызова эти двух функций. php_hello_init_globals() вызывается только тогда, когда запускается новый поток, так как каждый поток может обслуживать более одного запроса, то использовать данную функцию для обнуления нашего счетчика нельзя – при следующем запросе он будет содержать старое значение, оставшееся после обработки предыдущего запроса.
Обнулять счетчик, по условию, нам надо при каждом запросе страницы, поэтому мы будем это делать в функции RINIT, которая, как вы уже знаете, вызывается при каждом веб-запросе. Так зачем же мы определили функцию php_hello_init_globals()? Дело в том, что при компиляции расширения в однопоточном окружении передача NULL в макрос ZEND_INIT_MODULE_GLOBALS() вместо ссылки на функцию php_hello_init_globals() приведет к ошибке сегментации.
INI параметры как глобальные переменные
Как вы уже знаете, параметры в php.ini, определенные при помощи макроса PHP_INI_ENTRY(), считываются как строки, а затем, по необходимости, конвертируются при помощи макросов INI_INT(), INI_FLT() и INI_BOOL(). Для некоторых параметров, это означает бессмысленное повторение одних и тех же действий по чтению содержимого переменной в процессе выполнения скрипта, приведения ее к требуемому типу.
К счастью, есть возможность сказать ZE, чтобы он связал INI-параметр с определенным типом данных, и производил приведения типов только тогда, когда значение параметра изменилось.
Давайте испробуем данную возможность, на этот раз мы определим параметр-флаг в php.ini, означающий какое из действий (инкрементирование или декрементирование) необходимо производить с нашим счетчиком.
Для начала внесем следующие изменения в блок MODULE_GLOBALS в php_hello.h:
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
zend_bool direction;
ZEND_ENG_MODULE_GLOBALS(hello)Затем, определим сам параметр, подкорректировав содержимое между PHP_INI_BEGIN() … PHP_INI_END():
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()Теперь необходимо инициализировать значение нового параметра в методе php_hello_init_globals():
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
hello_globals->direction = 1;
}Ну и наконец, использовать значение только что определенного нами флага в фукнции hello_long() для того чтобы определить, что делать со счетчиком – увеличивать или уменьшать на единицу:
PHP_FUNCTION(hello_long)
{
if (HELLO_G(direction)) {
HELLO_G(counter)++;
} else {
HELLO_G(counter)--;
}
RETURN_LONG(HELLO_G(counter));
}Вот и все. Теперь метод OnUpdateBool определенный в секции INI_ENTRY автоматически конвертирует значение установленное в php.ini, .htaccess или посредством вызова метода ini_set() в скрипте в соответствующее значение TRUE/FALSE, к которому вы без проблем сможете обратиться напрямую из скрипта.
Последние три параметра STD_PHP_INI_ENTRY сообщат PHP какую глобальную переменную необходимо изменять, какая структура в нашем расширении содержит глобальные переменные, и имя контейнера в глобальном пространстве имен, в котором они находятся.
Сверим часы
Пришло время сверить исходники, если вы последовательно читали данную статью, то у вас должны были получиться файлы со следующим содержимым:
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#ifdef ZTS
#include "TSRM.h"
#endif
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)
#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
ZEND_DECLARE_MODULE_GLOBALS(hello)
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
PHP_FE(hello_long, NULL)
PHP_FE(hello_double, NULL)
PHP_FE(hello_bool, NULL)
PHP_FE(hello_null, NULL)
{NULL, NULL, NULL}
};
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
PHP_RINIT(hello),
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
hello_globals->direction = 1;
}
PHP_RINIT_FUNCTION(hello)
{
HELLO_G(counter) = 0;
return SUCCESS;
}
PHP_MINIT_FUNCTION(hello)
{
ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);
REGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(hello)
{
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_FUNCTION(hello_world)
{
RETURN_STRING("Hello World", 1);
}
PHP_FUNCTION(hello_long)
{
if (HELLO_G(direction)) {
HELLO_G(counter)++;
} else {
HELLO_G(counter)--;
}
RETURN_LONG(HELLO_G(counter));
}
PHP_FUNCTION(hello_double)
{
RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
RETURN_NULL();
}Что дальше?
В этой статье мы рассмотрели структуру простого PHP расширения с экспортируемыми функциями, вернули результат работы функций, добавили параметры в php.ini, научились отслеживать их изменения в процессе выполнения запроса.
В следующей статье мы рассмотрим как устроены переменные в PHP, как они хранятся, как отслеживать и поддерживать их в окружении скрипта. Мы научимся использовать zend_parse_parameters, чтобы получить параметры переданные функции из скрипта при ее вызове, и рассмотрим способы возврата в качестве результата функции более сложных типов данных, включая массивы, объекты и ресурсы.
Свежие записи
Наиболее интересные
- Взлом паролей пользователей ОС Windows - 62 голосов,




(4.82) - Как поставить Windows и Linux Ubuntu на нетбук - 28 голосов,




(4.86) - Чтение и запись в XML фаил (C#) - 26 голосов,




(4.73) - Решение СЛАУ. Метод Гаусса с выбором главного элемента - 22 голосов,




(4.45) - Можно ли играя в Линейку (Lineage II) заработать реальных денег? - 16 голосов,




(3.44) - Назначение клавиши Scroll Lock! А вы знаете зачем она? - 15 голосов,




(4.67) - Нахождение НОД и НОК без лишних слов - 15 голосов,




(4.27) - Раздавая файлы через торренты можно заработать?! - 13 голосов,




(3.15) - Генерация лабиринта - 11 голосов,




(5.00) - Битовые операции. Как быстро проверить является ли число степенью двойки? - 11 голосов,




(4.91)
Рубрики
- Закладки (4)
- Из жизни (34)
- Linux (6)
- Заработок (6)
- Игры (3)
- Тайм менеджмент (2)
- Программирование (52)
- Юмор (7)
Архивы
- Январь 2011 (3)
- Декабрь 2010 (2)
- Сентябрь 2010 (13)
- Август 2010 (4)
- Июль 2010 (5)
- Июнь 2010 (7)
- Апрель 2010 (6)
- Март 2010 (11)
- Февраль 2010 (24)
- Январь 2010 (12)
- Октябрь 2009 (1)
- Сентябрь 2009 (1)





