PHP Extension: тонкости

    Публикую топик, за который получил инвайт на Хабр =)

    Давно подумывал поделиться опытом разработки расширений для 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 указанный индекс.
    Остальное должно быть понятно.
    Приведенный код разумеется некрасив и неоптимален, он тут исключительно для примера =)

    Заключение


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

    Буду рад услышать любые замечания по топику и по стилю, все таки это мой первый хабратопик =)
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 15
    • +1
      Хорошая статья, спасибо, по разработке расширений на русском мало статей, а тема интересная.
      • 0
        Буду рад продолжить тему.
        Только уточните в каком направлении углубляться лучше =)
        • 0
          Можно ли создать расширение в VC++ EE? Не подскажете как это сделать?
          • 0
            Я к сожалению занимался разработкой расширений исключительно под *nix, полагаю, вам лучше обратиться к автору топика о создании расширения под VS2008, он публиковал готовый проект-основу. Думаю он больше знает по этому вопросу.
            • 0
              Это ко мне) Жмем мой профиль и смотрим 2 последних топика.
      • –4
        и здесь без мелкомягких не обошлось…
        Неужели нельзя создать php расширение используя например только eclipse?
        • +2
          Вы топик то читали?
          Ни слова про MS, я разрабатываю под *nix.
          • 0
            Под виндой — нельзя.
            PHP компилится только VC6. А с 5.3 версии еще и VC9.
            • 0
              Под виндой да.
              Под *nix нужен любой текстовый редактор, хоть vi, подойдет и eclipse =)
              Ну и gcc разумеется.
          • 0
            Порадовал тэг к статье — «с» :)
            • 0
              мм разработка идет на Си. А что не так?
              • –1
                Скорее всего автор не знал, что расширения пишутся на сях :)
            • 0
              Мне вот, что интересно. Можно ли написав подобный экстеншен перехватить запись апачем (или другим веб сервером) файла из данных POST в темповое место и записать его сразу куда мне нужно. А то для больщих файлов операция записи в темповое место а потом перенос этого чуда в нужное занимает много времени
              • 0
                Записывает файл, если я не ошибаюсь, именно php, когда разбирает входные параметры.
                Поэтому я думаю да, это возможно. Но детальнее сейчас не скажу, надо разбираться =)
                • 0
                  Не знаю как на счет модуля, но для вашей проблем уже есть готовое средство. Правда для Nginx
                  Nginx upload module. Записывает файл в tmp каталог и передает, путь до этого файла в POST переменной. Работает у нас в продакшене — очень довольны.
                  Даже если у вас не стоит nginx, то поставить его, прицепить модуль и использовать nginx только для аплоуда, все равно будет дешевле чем писать и отлаживать модуль самому…

                  Кроме того, php-fpm имело спецальную возможность для решение той же проблемы. Но прошу прощения, не могу найти описание на новом их новом сайта, так что гуглите…

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.