Макросы Zend обхода циклов (HashTable Iteration)
Продолжая своё поверхностное изучение исходников PHP (7.0.7) и написания простейшего расширения к нему, хотел бы в этот раз немного углубится и описать приемы обхода массива через принятый аргумент функции, с которыми я познакомился при реализации простой PHP функции median (). Задача этой функции проста — вернуть средне-арифметическое значение. Возможна данная публикация будет полезной другим разработчикам PHP, таким же как и я, которые решили в свободное время немного изучить архитектуру любимого языка, на котором зарабатывают деньги. В предыдущей публикации я на «скорую руку» описал прием быстрого создания расширения в PHP с реализаций функции расчета факториала. Она проста в той степени, что принимает простой параметр целого типа и затем рекурсивно вызывается. Реализация функции median () усложнена тем, что принимаемый параметр — массив, по нему нужно пройтись, для суммирования общего значения, а также просчитать общее число элементов в массиве.
В данный момент я упростил задачу еще и тем, что заведо считаю, что все принятые элементы массива — числа. Исходники расширений PHP удивительны тем, что здесь «все пишется» через использование макросов. По крайней мере создается такое первоначальное мнение. Оказывается, для прохода по списку элементов в массиве тоже используются макросы. Для наглядности приведу сразу код функции с последующим небольшим описанием.
Функция описана все в том же файле — mathstat.c расширения mathstat. Ссылка на github.
Занесение в список функций расширения mathstat:
const zend_function_entry mathstat_functions[] = {
PHP_FE(confirm_mathstat_compiled, NULL) /* For testing, remove later. */
PHP_FE(ms_factorial, arginfo_ms_factorial)
PHP_FE(ms_median, NULL)
PHP_FE_END /* Must be the last line in mathstat_functions[] */
};
Само определение тела функции:
PHP_FUNCTION(ms_median)
{
int argc = ZEND_NUM_ARGS();
double total = 0;
int count = 0;
zval *array,
*value;
if (zend_parse_parameters(argc, "a", &array) == FAILURE) {
RETURN_DOUBLE(0);
}
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), value) {
total = total + zval_get_double (value);
count += 1;
} ZEND_HASH_FOREACH_END();
if (count == 0 || total == 0) {
RETURN_DOUBLE(0);
}
RETURN_DOUBLE(total/count);
}
Если смотреть тело функции, то как и в прошлый раз, вызывается функция проверки параметра, где в качестве шаблона принимаемого типа аргумента задаем значение «a» (array)
if (zend_parse_parameters(argc, "a", &array) == FAILURE) {
RETURN_DOUBLE(number);
}
Теперь самое интересное, проход по циклу реализован через макрос ZEND_HASH_FOREACH_VAL. Всего макросов которые проходят по массиву я нашел в справочках 7 штук. При этом, везде используется вместо массива термин HashTable. Для нашего случая я выбрал самый простой макрос. Первым аргументом он получает сам принятый массив через функцию, а вторым zval (базовая структура данных, которая хранить себе значение и тип данных — видео по этой части Дмитрия Стогова). В данном случае, я просто вызываю функцию zval_get_double, которая грубо говоря, мне и возвращает самое значение из массива. Если переписать это на обычный код PHP, то получится:
1
То есть, по сути ничего сложного, таже запись, только с использованием макроса. Если посмотреть на другой более расширенный макрос,
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val)
то без кода уже понятно, что это аналог php цикла:
foreach($array as $key => $value) {
}
Для наглядности приведу из справочника все макросы:
ZEND_HASH_FOREACH_VAL(ht, val)
ZEND_HASH_FOREACH_KEY(ht, h, key)
ZEND_HASH_FOREACH_PTR(ht, ptr)
ZEND_HASH_FOREACH_NUM_KEY(ht, h)
ZEND_HASH_FOREACH_STR_KEY(ht, key)
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val)
ZEND_HASH_FOREACH_KEY_VAL(ht, h, key, val)
На этом все. Спасибо за отнятое время и потерянные деньги на мобильном трафике.