Два типа расширений PHP. Zend extension VS PHP module

image


Какие расширения вообще бывают

PHP module — оно же обычное расширение PHP
К этому типу относится подавляющее число расширений в PHP. Все то, что подключается в php.ini с помощью инструкции extension=some_library.so — это они и есть.

Zend extension
Расширений такого типа крайне мало, однако они ничуть не менее востребованы.

В статье я обзорно, совсем по верхам, расскажу, чем же эти два типа расширений отличаются.


С точки зрения конечного пользователя.

Отличаются только способом подключения.
Обычные расширения подключаются через php.ini с помощью инструкции:
extension=some_extension.so
Расширения zend с помощью:
zend_extension=some_extension.so.

Если хочется подключить через аргумент командной строки, то, для обычных:
php -d extension=/path/extension.so
А для расширений zend:
php -z /path/zend_extension.so

Однако под капотом они очень разные.

Тут очень подходит аналогия с бензиновым и дизельным двигателем. Для пользователя вся разница заключается только в типе топлива, которое он заливает в бак, но по факту это две совершенно разных конструкции, с разными принципами работы и со своими плюсами и минусами.


С точки зрения решаемых задач

Стандартные расширения, в подавляющем числе случаев, используются для расширения функциональных возможностей языка, таких как добавления новых классов, функций, констант и т.д. Крайне редко используются для решения других задач. Например, PECL расширение Vulcan Logic Disassembler (vld) позволяет посмотреть сгенерированный opcode для скрипта.

Расширения zend используются в случаях, когда нужно максимально глубоко залезть внутрь виртуальной машины. Например для отладки или профилирования скрипта, либо для изменения логики работы PHP.


С точки зрения разработчика, который раньше не писал расширений для PHP и вдруг сподобился

Написание обычных расширений хорошо документировано и описано во множестве статей. Для них даже есть инструмент генерации скелета проекта, включенный в исходные коды PHP.

В случае с Zend extension ничего этого нет. Хороших статей практически нет. Плохих тоже. Будьте готовы к длительному и вдумчивому изучению исходных кодов как самого PHP, так и немногих существующих расширений данного типа.


С точки зрения жизненного цикла

К сожалению, тут не обойтись без кода на С, поскольку жизненный цикл расширения целиком и полностью является отражением определяющей его структуры. (Структуры привожу в сокращенном виде. Только то, что необходимо в рамках статьи)

Стандартное расширение задается структурой _zend_module_entry (описывается в zend_module.h)

struct _zend_module_entry {
/* skipped */
        int (*module_startup_func)(INIT_FUNC_ARGS);        /* MINIT() */
        int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);   /* MSHUTDOWN() */
        int (*request_startup_func)(INIT_FUNC_ARGS);       /* RINIT() */
        int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);  /* RSHUTDOWN() */
        void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);     /* PHPINFO() */
/* skipped */
        void (*globals_ctor)(void *global);                /* GINIT() */
        void (*globals_dtor)(void *global);                /* GSHUTDOWN */
        int (*post_deactivate_func)(void);                 /* PRSHUTDOWN() */
/* skipped */
};

Расширение Zend задается структурой _zend_extension (описывается в zend_extensions.h)

struct _zend_extension {
/* skipped */
    startup_func_t startup;                               /* STARTUP() */
    shutdown_func_t shutdown;                             /* SHUTDOWN() */
    activate_func_t activate;                             /* ACTIVATE() */
    deactivate_func_t deactivate;                         /* DEACTIVATE() */

    message_handler_func_t message_handler;               /* MESSAGE_HANDLER() Вызывается при регистрации расширения*/

    op_array_handler_func_t op_array_handler;             /* Вызывается после компиляции корневого пространства имен скрипта и для каждой функции. Принимает массив опкодов */

    /* Данные хуки, если заданы, приводят к добавлению специфичных опкодов, которые 
     * вызывают эти функции в процессе работы скрипта
     */
    statement_handler_func_t statement_handler;           /* Перед каждым оператором */
    fcall_begin_handler_func_t fcall_begin_handler;       /* Перед вызовом функции */
    fcall_end_handler_func_t fcall_end_handler;           /* После вызова функции */ 

    op_array_ctor_func_t op_array_ctor;                   /* Вызывается при создании OPArray */
    op_array_dtor_func_t op_array_dtor;                   /* Вызывается при уничтожении OPArray */

    int (*api_no_check)(int api_no);                      /* API_NO_CHECK() */
    int (*build_id_check)(const char* build_id);          /* BUILD_ID_CHECK()  */
/* skipped */
};

А вот теперь уже можно показывать картинку с жизненным циклом.

image


Бонус. Гибридные расширения

Да. Такая возможность есть.

Зачем оно может понадобиться?


  1. Вам нужен полный контроль, предоставляемый расширением zend и, помимо этого, хочется зарегистрировать новые функции.
  2. Вам, зачем-то, понадобилось использовать вообще все возможные хуки.
  3. Вам необходимо управлять порядком загрузки своего расширения. К примеру надо загрузиться не раньше загрузки OPCache.


Полезные ссылки

Пример написания простого расширения Zend
Крайне полезный ресурс по внутреннему устройству PHP
Исходники PHP

© Habrahabr.ru