antonio's blog

antonio's blog


Блог о всяком разном, связанном с разработкой ПО. Пишу редко, когда есть время и желание.

antonio
Author

Share


Tags


Разработка расширений для PHP. Массивы

antonioantonio

В PHP массив - это хеш-таблица заключенная в zval контейнер. API хэш-таблиц был рассмотрен в предыдущих постах. Помимо API хэш-таблиц Zend Engine также предоставляет упрощенный API для создания и манипуляции массивами, данный API является оберткой над API хэш-таблиц

Рассмотрим примеры создания простого нумерованного и ассоциативного массивов с использованием API хэш-таблиц.

Создание простого нумерованного массива с помощью API хэш-таблиц:

zval *array = NULL;  
zval *array_item = NULL;  
unsigned int i = 0;

MAKE_STD_ZVAL(array);  
Z_TYPE_P(array) = IS_ARRAY;

ALLOC_HASHTABLE(Z_ARRVAL_P(array));  
zend_hash_init(Z_ARRVAL_P(array), 64, NULL, ZVAL_PTR_DTOR, 1);

/* Создаем нумерованный массив из 10-ти элементов с типом int */
for(i; i < 10; i++ ) {  
    MAKE_STD_ZVAL(array_item);
    ZVAL_LONG(array_item, 10 * i);
    zend_hash_next_index_insert(Z_ARRVAL_P(array),(void **)&array;_item, sizeof(zval*), NULL);
}

zend_hash_add(&EG;(symbol_table), "my_array", sizeof("my_array"), (void **)&array;, sizeof(zval*), NULL);  

Приведенный код эквивалентен PHP коду:

<php  
$GLOBALS['my_array'] =  array(0, 10, 20, 30, 40, 50, 60, 70, 80, 90);
?>   

Создание ассоциативного массива:

zval *array = NULL;  
zval *array_item = NULL;  
unsigned int i = 0;  
unsigned int str_len = 0;  
char *item_name = NULL;

MAKE_STD_ZVAL(array);  
Z_TYPE_P(array) = IS_ARRAY;

ALLOC_HASHTABLE(Z_ARRVAL_P(array));  
zend_hash_init(Z_ARRVAL_P(array), 16, NULL, ZVAL_PTR_DTOR, 1);

/* Создаем ассоциативный массив из 10-ти элементов с типом int */
for(i; i < 10; i++ ) {  
    str_len = snprintf( NULL, 0, "item_%d", i);
    item_name = emalloc(str_len+1);
    if(!item_name) {
       continue;
    }
    snprintf( item_name, str_len+1, "item_%d", i);
    MAKE_STD_ZVAL(array_item);
    ZVAL_LONG(array_item, 10 * i);
    zend_hash_add(Z_ARRVAL_P(array), item_name, str_len+1, (void **)& array_item, sizeof(zval*), NULL);
    efree(item_name);
}

zend_hash_add(&EG;(symbol_table), "my_array", sizeof("my_array"), (void **)&array;, sizeof(zval*), NULL);  

Приведенный код эквивалентен PHP коду:

<php  
$GLOBALS['my_array'] =  array(
     'item_0' => 0, 
     'item_1' => 10, 
     'item_2' => 20, 
     'item_3' => 30, 
     'item_4' => 40, 
     'item_5' => 50, 
     'item_6' => 60, 
     'item_7' => 70, 
     'item_8' => 80, 
     'item_9' => 90
);
?>

В примерах первым делом создается zval контейнер. Массивы в zval контейнере хранятся в элементе value->ht, поэтому инициализируем хэш-таблицу как элемент zval контейнера, используя макрос Z_ARRVAL_P(array) _для доступа к этому элементу. Далее в цикле заполняем хэш-таблицу десятью элементами. И последним шагом связываем полученный массив с меткой my_array в глобальной таблице, которая доступна в пользовательском пространстве как массив $_GLOBALS. Схематично наш нумерованный массив выглядит так:

Простой API для работы с массивами

Для облегчения создания и управления массивами Zend Engine предоставляет упрощенный API - набор макросов и вспомогательных функции, которые являются обертками над HashTables API. Данное API избавляет от многих рутинных операций делая работу с массивами более легкой. Функции данного API объявлены в Zend/zend_API.h

Инициализация

array_init(zval *arrval) - Функция инициализирует пустой массив. В качестве единственного параметра принимает указатель на zval контейнер. По сути функция выделяет память под хэш-таблицу (элемент value->ht zval контейнера) и производит ее инициализацию, вызывая zend_hash_init(). В качестве деструктора, вызываемого при обновлении или удалении элементов из хэш-таблиц, используется внутренняя функция. Zend Engine автоматически вызывает zend_hash_destroy() и FREE_HASHTABLE() для уничтожения хэш-таблицы, как только данные будут более не нужны.

Добавление нумерованных элементов

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

ФункцияОписание
int add_index_long(zval *arg, ulong idx, long n)Добавляет новый элемент в массив с целочисленным значением. Третьим параметром функция принимает целочисленное значение.
int add_index_null(zval *arg, ulong idx)Добавляет новый элемент в массив с NULL-значением
int add_index_resource(zval *arg, ulong idx, int r)Добавляет новый элемент в массив значением которого является номер ресурса (ресурс). Третьим параметром функция принимает номер ресурса.
int add_index_bool(zval *arg, ulong idx, int b)Добавляет новый элемент в массив с булевым значением. Третьим параметром функция принимает целое число. Если передается не 0, то значение элемента будет TRUE, иначе FALSE
int add_index_double(zval *arg, ulong idx, double d)Добавляет новый элемент в массив значением которого является число с плавающей запятой. Третьим параметром принимает значение - число с плавающей запятой
int add_index_string(zval *arg, ulong idx, const char *str, int dup)Добавляет новый элемент в массив значением которого является строка. Третьим параметром функция принимает строку-значение, четвертым флаг дублирования. Если в качестве четвертого параметра передан 1, то строка будет сдублирована, под строку будет выделен новый блок памяти
int add_index_stringl(zval *arg, ulong idx, const char *str, uint length, int dup)Добавляет новый элемент в массив значением которого является строка. Третьим параметром функция принимает строку-значение, четвертым размер строки и пятым - флаг дублирования. Если в качестве пятого параметра передан 1, то строка будет сдублирована, под строку будет выделен новый блок памяти. Данная функция безопасна для бинарных данных
int add_index_zval(zval *arg, ulong index, zval *value)Добавляет новый элемент в массив значением которого является другой zval контейнер. Третьим параметром функция принимает указатель на zval контейнер. Данная функция полезна для добавления других массивов, объектов, потоков и т.д..

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

ФункцияОписание
int add_next_index_long(zval *arg, long n)Добавляет новый элемент в массив с целочисленным значением. Третьим параметром функция принимает целочисленное значение.
int add_next_index_null(zval *arg)Добавляет новый элемент в массив с NULL-значением
int add_next_index_resource(zval *arg, int r)Добавляет новый элемент в массив значением которого является номер ресурса (ресурс). Третьим параметром функция принимает номер ресурса.
int add_next_index_bool(zval *arg, int b) -

Добавляет новый элемент в массив с булевым значением. Третьим параметром функция принимает целое число. Если передается не 0, то значение элемента будет TRUE, иначе FALSE
int add_next_index_double(zval *arg, double d)Добавляет новый элемент в массив значением которого является число с плавающей запятой. Третьим параметром принимает значение - число с плавающей запятой
int add_next_index_string(zval *arg, const char *str, int dup)Добавляет новый элемент в массив значением которого является строка. Третьим параметром функция принимает строку-значение, четвертым флаг дублирования. Если в качестве четвертого параметра передан 1, то строка будет сдублирована, под строку будет выделен новый блок памяти
int add_next_index_stringl(zval *arg, const char *str, uint length, int dup)Добавляет новый элемент в массив значением которого является строка. Третьим параметром функция принимает строку-значение, четвертым размер строки и пятым - флаг дублирования. Если в качестве пятого параметра передан 1, то строка будет сдублирована, под строку будет выделен новый блок памяти. Данная функция безопасна для бинарных данных
int add_next_index_zval(zval *arg, zval *value)Добавляет новый элемент в массив значением которого является другой zval контейнер. Третьим параметром функция принимает указатель на zval контейнер. Данная функция полезна для добавления других массивов, объектов, потоков и т.д..

Пример создания нумерованного массива:

zval *array = NULL;  
zval *array2 = NULL;

MAKE_STD_ZVAL(array);  
MAKE_STD_ZVAL(array2);

array_init(array2);  
add_next_index_string(array, "String2", 1);  
add_next_index_null(array);

array_init(array);  
add_index_long(array, 1, 184);  
add_index_long(array, 5, 26);  
add_index_double(array, 3, 1.24);  
add_index_string(array, 2, "String", 1);  
add_index_null(array, 4);  
add_index_zval(array, 6, array2);

zend_hash_add(&EG;(symbol_table), "my_array", sizeof("my_array"), (void **)&array;, sizeof(zval*), NULL);  
<php  
var_dump( $GLOBALS["my_array"] );

/* Вывод:
  array(5) {
    [1] => int(184)
    [5] => int(26)
    [3] => float(1.24)
    [2] => string(6) "String"
    [4] => NULL
    [6] => array(2) {
         [0] => string(7) "String2"
         [1] => NULL
    }
  }
*/
?>

Добавление ассоциативных элементов

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

ФункцияОписание
int add_assoc_long(zval *arg, const char *key, long n)Добавляет новый элемент в массив с целочисленным значением. Третьим параметром функция принимает целочисленное значение.
int add_assoc_null(zval *arg, const char *key)Добавляет новый элемент в массив с NULL-значением
int add_assoc_resource(zval *arg, const char *key, int r)Добавляет новый элемент в массив значением которого является номер ресурса (ресурс). Третьим параметром функция принимает номер ресурса.
int add_assoc_bool(zval *arg, const char *key, int b)Добавляет новый элемент в массив с булевым значением. Третьим параметром функция принимает целое число. Если передается не 0, то значение элемента будет TRUE, иначе FALSE
int add_assoc_double(zval *arg, const char *key, double d)Добавляет новый элемент в массив значением которого является число с плавающей запятой. Третьим параметром принимает значение - число с плавающей запятой
int add_assoc_string(zval *arg, const char *key, const char *str, int dup)Добавляет новый элемент в массив значением которого является строка. Третьим параметром функция принимает строку-значение, четвертым флаг дублирования. Если в качестве четвертого параметра передан 1, то строка будет сдублирована, под строку будет выделен новый блок памяти
int add_assoc_stringl(zval *arg, const char *key, const char *str, uint length, int dup)Добавляет новый элемент в массив значением которого является строка. Третьим параметром функция принимает строку-значение, четвертым размер строки и пятым - флаг дублирования. Если в качестве пятого параметра передан 1, то строка будет сдублирована, под строку будет выделен новый блок памяти. Данная функция безопасна для бинарных данных
int add_assoc_zval(zval *arg, const char *key, zval *value)Добавляет новый элемент в массив значением которого является другой zval контейнер. Третьим параметром функция принимает указатель на zval контейнер. Данная функция полезна для добавления других массивов, объектов, потоков и т.д..

Ниже приведены функции имеющие тоже назначение, что и их, выше приведенные, аналоги. Отличие заключается в том, что эти функции принимают третьим параметром длину ассоциативного ключа. Функции возвращают SUCCESS в случае успеха иначе FAILURE

ФункцияОписание
int add_assoc_long_ex(zval *arg, const char *key, uint key_len, long n)Добавляет новый элемент в массив с целочисленным значением. Четвертым параметром функция принимает целочисленное значение.
int add_assoc_null_ex(zval *arg, const char *key, uint key_len)Добавляет новый элемент в массив с NULL-значением
int add_assoc_resource_ex(zval *arg, const char *key, uint key_len, int r)Добавляет новый элемент в массив значением которого является номер ресурса (ресурс). Четвертым параметром функция принимает номер ресурса.
int add_assoc_bool_ex(zval *arg, const char *key, uint key_len, int b)Добавляет новый элемент в массив с булевым значением. Четвертым параметром функция принимает целое число. Если передается не 0, то значение элемента будет TRUE, иначе FALSE
int add_assoc_double_ex(zval *arg, const char *key, uint key_len, double d)Добавляет новый элемент в массив значением которого является число с плавающей запятой. Четвертым параметром принимает значение - число с плавающей запятой
int add_assoc_string_ex(zval *arg, const char *key, uint key_len, const char *str, int dup)Добавляет новый элемент в массив значением которого является строка. Четвертым параметром функция принимает строку-значение, четвертым флаг дублирования. Если в качестве четвертого параметра передан 1, то строка будет сдублирована, под строку будет выделен новый блок памяти
int add_assoc_stringl_ex(zval *arg, const char *key, uint key_len, const char *str, uint length, int dup)Добавляет новый элемент в массив значением которого является строка. Четвертым параметром функция принимает строку-значение, пятым размер строки и шестым - флаг дублирования. Если в качестве значения шестого параметра передана 1, то строка будет сдублирована, под строку будет выделен новый блок памяти. Данная функция безопасна для бинарных данных
int add_assoc_zval_ex(zval *arg, const char *key, uint key_len, zval *value)Добавляет новый элемент в массив значением которого является другой zval контейнер. Четвертым параметром функция принимает указатель на zval контейнер. Данная функция полезна для добавления других массивов, объектов, потоков и т.д..

Пример создания ассоциативного массива:

zval *array = NULL;

MAKE_STD_ZVAL(array);

array_init(array);  
add_assoc_string(array, "string_item", "String2", 1);  
add_assoc_null(array, "null_item");  
add_assoc_long(array, "int_item", 184);

zend_hash_add(&EG;(symbol_table), "my_array", sizeof("my_array"), (void **)&array;, sizeof(zval*), NULL);  
<php  
var_dump( $GLOBALS["my_array"] );

/* Вывод:
array(3) {  
    ["string_item"] => string(7) "String2"
    ["null_item"] => NULL
    ["int_item"] => int(184)
  }
*/
?>

В некоторой литературе и на просторах сети Интернет также можно встретить функции add_assoc_unset(), add_next_index_unset() и add_index_unset(). В PHP 5.3 данные функции являются алиасами для функций add_assoc_null(), add_next_index_null() и add_index_null(), соответственно. Использовать в расширениях данные функции не рекомендуется.

Получение хэш-таблицы из массива

Зачастую из zval контейнера, который хранит массив, необходимо получить указатель на хэш-таблицу. Для этого, конечно же можно обратиться напрямую к элементу value->ht структуры zval:

zval *array = NULL;  
HashTable *mht = NULL;

MAKE_STD_ZVAL(array);  
array_init(array);  
add_assoc_string(array, "string_item", "String2", 1);  
add_assoc_null(array, "null_item");  
add_assoc_long(array, "int_item", 184);

/* какой-то код */

if(Z_TYPE_P(array) == IS_ARRAY && array->value->ht != NULL ) {  
   mht = array->value->ht;
   /* какой-то код */
}

Данный подход не рекомендуется к использованию так как он не обеспечивает совместимость с будущем версиями, нет гарантии того, что хэш-таблицы будут храниться в элементе value->ht в будущем. Для обеспечения совместимости рекомендуется использовать, ранее уже упоминавшиеся, макросы: ZVAL_ARRVAL(zval pzv), ZVAL_ARRVAL_P(zval *pzv), ZVAL_ARRVAL_PP(zval **ppzv). Помимо макросов ZVAL_ARRVAL_** в Zend Engine есть универсальный макрос HASH_OF(zval *pzv), который позволяет также получить доступ и к свойствам объектов (которые также хранятся в хэш-таблицах, но об этом чуть позже). В макросе осуществляется проверка типа данных хранимых в zval контейнере.

zval *array = NULL;  
HashTable *mht = NULL;

MAKE_STD_ZVAL(array);  
array_init(array);  
add_assoc_string(array, "string_item", "String2", 1);  
add_assoc_null(array, "null_item");  
add_assoc_long(array, "int_item", 184);

/* какой-то код */

if(Z_TYPE_P(array) == IS_ARRAY && ZVAL_ARRVAL_P(array) != NULL ) {  
   mht = array->value->ht;
   /* какой-то код */
}

/* или */

mht = HASH_OF(array);  

Макрос HASH_OF() обявлен в Zend/zend_API.h:

#define HASH_OF(p) ( Z_TYPE_P(p) == IS_ARRAY ? Z_ARRVAL_P(p) : \
   ( ( Z_TYPE_P(p) ==IS_OBJECT ? Z_OBJ_HT_P(p)->get_properties( (p) TSRMLS_CC ) : NULL ) ) )
antonio
Author

antonio

Comments