Макросы 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)

На этом все. Спасибо за отнятое время и потерянные деньги на мобильном трафике.

© Habrahabr.ru