Pull to refresh

PHP Extension: тонкости

Reading time 4 min
Views 6.3K
Публикую топик, за который получил инвайт на Хабр =)

Давно подумывал поделиться опытом разработки расширений для PHP, но все время забывал =)
Сейчас, увидев хабратопик об основах создания расширений для PHP в VS2008, решил наконец это сделать.
Поскольку основы были изложены в этом топике, я сразу перейду к более тонким моментам.



Вывод текста


Если необходимо вывести текст, вместо стандартной функции printf() следует пользоваться функцией zend_printf(). Если вызвавший нас скрипт запущен для обработки HTTP-запроса, выводимая через zend_printf() информация будет отправлена напрямую клиенту. При запуске же php в режиме standalone — выведет текст на экран.

Передача параметров функции по ссылке


Поскольку начиная с PHP 5.3.0 передача аргумента функции по ссылке при вызове функции — deprecated, аргумент следует объявлять как передаваемый по ссылке в объявлении функции. В случае, когда функцию мы объявляем в PHP — все просто — достаточно перед именем аргумента поставить амперсанд, например вот так:

function ReadData($id,&$data)


Если же функцию мы объявляем в расширении — все несколько сложнее.
Для начала следует описать аргументы функции:

ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args)
ZEND_ARG_INFO(pass_by_ref, name)
...
ZEND_END_ARG_INFO()


Параметры макроса ZEND_BEGIN_ARG_INFO_EX():
name — Имя структуры, описывающей данный список аргументов. Должно быть уникальным. Например, для функции ReadData() структуру можно назвать arginfo_readdata.
required_num_args — Количество обязательных аргументов функции.
pass_rest_by_reference — Следует ли остальные (необязательные) аргументы передавать по ссылке (0 — нет, 1 — да).
return_reference — Передается ли возвращаемое значение по ссылке.

Параметры макроса ZEND_ARG_INFO():
pass_by_ref — Передавать ли этот аргумент по ссылке.
name — Имя аргумента.

Все аргументы перечисляются в том же порядке, в каком они передаются функции.
Пример объявления той же функции ReadData:

ZEND_BEGIN_ARG_INFO_EX(arginfo_readdata,0,0,2)
ZEND_ARG_INFO(0,id)
ZEND_ARG_INFO(1,data)
ZEND_END_ARG_INFO()


После этого при объявлении функции достаточно указать в качестве второго параметра макроса PHP_FE() название нашей структуры описания аргументов. Например:

PHP_FE(readdata,arginfo_readdata)


Что такое zval и с чем его едят


zval — это структура, содержащая представление PHP-переменной. В ней может содержаться как скалярное значение (число, строка, и т.п.), так и массив или handle ресурса.
Если мы хотим принять в качестве аргумента функции расширения или вернуть из функции массив — нам придется работать именно с этим zval.

Получаем zval


К примеру мы хотим получить в качестве аргумента функции ассоциативный массив и получить из него значения по некоторым индексам.
Для этого следует: объявить указатель на zval; при вызове функции zend_parse_parameters указать тип аргумента «a»; передать ей же указатель на объявленный указатель на zval.
Сразу пример кода:

PHP_FUNCTION(func)
{
zval *arr;
char buf[128];
unsigned long x,y;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr)==FAILURE) {
  WRONG_PARAM_COUNT;
}
...
}


* This source code was highlighted with Source Code Highlighter.


Получаем значения из массива


Продолжим функцию из предыдущего примера, получим из переданного нам массива значения по индексам x и y, после чего сформируем и выведем строку вида «x = ..., y = ...». В данном примере эти значения будут типа long, однако с незначительными переделками можно будет так же получать и другие типы.

PHP_FUNCTION(func)
{
zval *arr,**val;
char buf[128];
unsigned long x,y;
HashTable *hash;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr)==FAILURE) {
  WRONG_PARAM_COUNT;
}
if (Z_TYPE_P(arr)!=IS_ARRAY) {
  zend_error(E_ERROR,"%s(): Supplied argument is not an array.",get_active_function_name(TSRLM_C));
  RETURN_BOOL(0);
}
hash=Z_ARRVAL_P(arr);
if (zend_hash_find(hash,"x",1,(void **)&val)!=SUCCESS) {
  zend_error(E_ERROR,"%s(): There is no x index.",get_active_function_name(TSRLM_C));
  RETURN_BOOL(0);
}
if (Z_TYPE_PP(val)!=IS_LONG) {
  zend_error(E_ERROR,"%s(): Wrong type of x.",get_active_function_name(TSRLM_C));
  RETURN_BOOL(0);
}
x=Z_LVAL_PP(val);
if (zend_hash_find(hash,"y",1,(void **)&val)!=SUCCESS) {
  zend_error(E_ERROR,"%s(): There is no y index.",get_active_function_name(TSRLM_C));
  RETURN_BOOL(0);
}
if (Z_TYPE_PP(val)!=IS_LONG) {
  zend_error(E_ERROR,"%s(): Wrong type of y.",get_active_function_name(TSRLM_C));
  RETURN_BOOL(0);
}
y=Z_LVAL_PP(val);
zend_printf("x = %u, y = %u\n",x,y);
}


* This source code was highlighted with Source Code Highlighter.


В данном примере широко используются макросы Zend.
Z_TYPE_*() возвращает тип, содержащийся в переданном ему zval. Значения, как можно догадаться, имеют вид IS_тип. Например, IS_ARRAY или IS_STRING.
Z_ARRVAL_*() возвращает указатель на структуру HashTable, которая представляет собой внутреннее представление массива PHP.
Z_LVAL_*() возвращает число, содержащееся в переданном zval.
* — может быть P или PP, в зависимости от того, передаем мы указатель на zval или указатель на указатель на zval.
Функция zend_hash_find(zval *) ищет в переданном HashTable указанный индекс.
Остальное должно быть понятно.
Приведенный код разумеется некрасив и неоптимален, он тут исключительно для примера =)

Заключение


Не знаю, насколько интересна эта тема хабрасообществу, поэтому пока не буду расписывать дальше.
Если тема окажется интересной, буду рад рассказать о других тонкостях создания функций в расширениях.

Буду рад услышать любые замечания по топику и по стилю, все таки это мой первый хабратопик =)
Tags:
Hubs:
+28
Comments 16
Comments Comments 16

Articles