Writing PHP Extensions1. Setting up Your PHP Build Environment on Linux2. Generating a PHP Extension Skeleton3. Building and Installing a PHP Extension4. Rebuilding Extensions for Production5. Extension Skeleton File Content6. Running PHP Extension Tests7. Adding New Functionality8. Basic PHP Structures9. PHP Arrays10. Catching Memory Leaks11. PHP Memory Management12. PHP References13. Copy on Write14. PHP Classes and Objects15. Using OOP in our Example Extension16. Embedding C Data into PHP Objects17. Overriding Object Handlers18. Answers to Common Extension Questions7. Adding New FunctionalityAt this point you should already know the basic structure of a PHP extension and the building process. Now, we are going to learn how to implement new basic PHP extension features. Starting from this section, I’ll write new code in red and keep existing code black. FUNCTIONS Functions are the simplest primitives to add new functionality. To implement a new function, we first have to write the function code itself. This is regular C code that starts with the PHP_FUNCTION() macro and name of the function. This code requires a single double number argument and it returns an argument that is scaled by factor 2. I will describe parameter parsing API and most macros for manipulating values later. PHP_FUNCTION(test_scale) { double x; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_DOUBLE(x) ZEND_PARSE_PARAMETERS_END(); RETURN_DOUBLE(x * 2); }We also have to define the arguments’ description block. This block is started from ZEND_BEGIN_ARG_INFO() macro (or its variant) and terminated by ZEND_END_ARG_INFO() macro. The first argument of ZEND_BEGIN_ARG_INFO() is the name of the arg_info structure. The same name should be reused in PHP_FE() macro. The second argument is ignored. (In PHP 5, it meant pass rest of arguments by reference.) Each argument is defined by the ZEND_ARG_ INFO() macro that takes the “pass by reference” value and the argument name. ZEND_BEGIN_ARG_INFO(arginfo_test_scale, 0) ZEND_ARG_INFO(0, x) ZEND_END_ARG_INFO()It’s possible to use extended variants of ARG_INFO macros to specify additional arguments and return type hints, null-ability, returning by reference, number of required arguments, functions with variable number of arguments, etc. Finally, we have to add our new function into the list of extension functions: static const zend_function_entry test_functions[] = { PHP_FE(test_test1, arginfo_test_test1) PHP_FE(test_test2, arginfo_test_test2) PHP_FE(test_scale, arginfo_test_scale) PHP_FE_END };After extension rebuild and installation, the new function should start working. $ php -r ‘var_dump(test_scale(5));’ float(10)To declare functions inside a namespace, it is possible to use ZEND_NS_FUNCTION(ns, name) instead of PHP_FUNCTION(name) — and ZEND_NS_FE(ns, name, arg_info) instead of PHP_FE(name, arg_info). It’s also possible to add some function flags (e.g. deprecate function adding ZEND_ACC_DEPRECATED flag), using ZEND_FENTRY() instead if PHP_FE(). See Zend/zend_API.h for the main extension API. EXTENSION CALLBACKS Only PHP extension functions may be implemented in a pure declarative manner. All other extension features may be implemented calling special API functions during PHP start-up. To do this, the extension should implement MINIT() callback. This is, again, a regular C function, starting with PHP_MINIT_FUNCTION() macro and the extension name as an argument. The function should return SUCCESS to link the PHP extension into the core and enable all its functions and other features. Our MINIT function just initializes thread-local storage cache. Previously, this code was called from RINIT, but in case you execute something in MINIT, and directly or indirectly access module global variables or common global variables, it’s better to move this code into the beginning of MINIT. PHP_MINIT_FUNCTION(test) { #if defined(ZTS) && defined(COMPILE_DL_TEST) ZEND_TSRMLS_CACHE_UPDATE(); #endif return SUCCESS; }The MINIT callback address should be added into the module entry structure. You can also remove RINIT callback if it was used only for thread-local storage and is now empty.zend_module_entry test_module_entry = { STANDARD_MODULE_HEADER, “test”, /* Extension name */ test_functions, /* zend_function_entry */ PHP_MINIT(test), /* PHP_MINIT - Module initialization */ NULL, /* PHP_MSHUTDOWN - Module shutdown */ NULL, /* PHP_RINIT - Request initialization */ NULL, /* PHP_RSHUTDOWN - Request shutdown */ PHP_MINFO(test), /* PHP_MINFO - Module info */ PHP_TEST_VERSION, /* Version */ STANDARD_MODULE_PROPERTIES };Similar to MINIT, you may implement other callbacks like MSHUTDOWN, when you need to free up some resources before PHP termination.CONSTANTS MINIT callback is suitable to add various extension entities like a new internal constant. This is done using REGISTER_LONG_CONSTANT() macro, where the first argument is the constant name, the second is the constant value, and the third is the constant flags: CONST_CS refers to the case sensitive constant name. CONST_PERSISTENT refers to the persistent constant. (All internal extension constants should be persistent.) PHP_MINIT_FUNCTION(test) { #if defined(ZTS) && defined(COMPILE_DL_TEST) ZEND_TSRMLS_CACHE_UPDATE(); #endif REGISTER_LONG_CONSTANT(“TEST_SCALE_FACTOR”, 2, CONST_CS | CONST_PERSISTENT); return SUCCESS; }You can access a new constant after the extension rebuild and reinstallation. $ php -r ‘var_dump(TEST_SCALE_FACTOR);’ int(2)Of course, there are various other API macros to declare constants of different value types: REGISTER_NULL_CONSTANT(name, flags): Constant with value NULL. REGISTER_BOOL_CONSTANT(name, bval, flags): FALSE or TRUE. REGISTER_LONG_CONSTANT(name, lval, flags): Any long number. REGISTER_DOUBLE_CONSTANT(name, dval, flags): A double number. REGISTER_STRING_CONSTANT(name, str, flags): A zero terminated string. REGISTER_STRINGL_CONSTANT(name, str, len, flags): A string (with length). It’s also possible to use the similar REGISER_NS_...() group of macros to declare constants in some namespaces. See Zend/zend_constants.h for complete PHP constants API. MODULE GLOBAL VARIABLES For now, our new scale() function is purely functional. It doesn’t depend on any internal state and it always returns a value that’s only based on input arguments. However, some real-world functions access or update global state. In C, it’s usually stored in global variables, but multi-threaded software has to use some tricks to make a distinction between states of different threads. PHP is usually built to work in context of single thread, but it also may be configured for multi-threading. In any case, to be portable across different configurations, PHP recommends declaring global variables as a specially declared “module globals” structure. The following code should be added at the end of “php_test.h” file: ZEND_BEGIN_MODULE_GLOBALS(test) zend_long scale; ZEND_END_MODULE_GLOBALS(test) ZEND_EXTERN_MODULE_GLOBALS(test) #define TEST_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(test, v)The module global variables are declared between ZEND_BEGIN_MODULE_GLOBALS and ZEND_END_MODULE_GLOBALS macros. These are just plain C declarations. Actually, the C preprocessor converts them into “zend_tests_globals” C structure definition. ZEND_ EXTERN_MODULE_GLOBALS() defines an external C name to access the structure and TEST_G() macro provides a way to access our module global variables. So instead of global “scale”, we will use TEST_G(scale). In the “test.c” file we should declare the real module global variable: ZEND_DECLARE_MODULE_GLOBALS(test)We will also define a GINIT callback to initialize this structure. This callback is called before MINIT, so we have to move the thread-local storage cache initialization code here: static PHP_GINIT_FUNCTION(test) { #if defined(COMPILE_DL_BCMATH) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif test_globals->scale= 1; }We should also add information about module global variables and their initialization callback into the extension entry structure: zend_module_entry test_module_entry = { STANDARD_MODULE_HEADER, “test”, /* Extension name */ test_functions, /* zend_function_entry */ PHP_MINIT(test), /* PHP_MINIT - Module initialization */ NULL, /* PHP_MSHUTDOWN - Module shutdown */ NULL, /* PHP_RINIT - Request initialization */ NULL, /* PHP_RSHUTDOWN - Request shutdown */ PHP_MINFO(test), /* PHP_MINFO - Module info */ PHP_TEST_VERSION, /* Version */ PHP_MODULE_GLOBALS(test), /* Module globals */ PHP_GINIT(test), /* PHP_GINIT – Globals initialization */ NULL, /* PHP_GSHUTDOWN – Globals shutdown */ NULL, STANDARD_MODULE_PROPERTIES_EX };And now we can update our “scale” function to use the module global variable: PHP_FUNCTION(test_scale) { double x; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_DOUBLE(x) ZEND_PARSE_PARAMETERS_END(); RETURN_DOUBLE(x * TEST_G(scale)); }It works after extension recompilation and reinstallation: $ php -r ‘var_dump(test_scale(5));’ float(5)CONFIGURATION DIRECTIVES What else can we do? We may implement a way to define the value of our “scale” factor through configuration directive in php.ini. This is done by two additional pieces of code in “test.c.” The first defines configuration directives, their names, default values, types, and storage locations: PHP_INI_BEGIN() STD_PHP_INI_ENTRY(“test.scale”, “1”, PHP_INI_ALL, OnUpdateLong, scale, zend_test_globals, test_globals) PHP_INI_END()STD_PHP_INI_ENTRY() declares a configuration directive named “test.scale,” with default value “1.” PHP_INI_ALL indicates that it may be modified at any time (in php.ini, in per-directory configuration files and by ini_set() function during script excution). PHP_INI_SYSTEM (instead) would allow modification only during PHP startup (in php.ini). PHP_INI_PERDIR would allow modification only in php.ini and per-directory configuration files. OnUpdateLong is a common callback that sets the integer value of the directive. (There are few other common callbacks like OnUpdateString.) “scale” is the module global variable name. “zend_test_globals” is the name of the structure (C type name) that keeps module global variables. “test_globals” is the global variable that keeps module global variables for a non-thread-safe build. The complete PHP.ini API is defined at Zend/zend_ini.h and main/php_ini.h. The second piece calls an API function that registers the directives declared in the previous block:PHP_MINIT_FUNCTION(test) { REGISTER_INI_ENTRIES(); return SUCCESS; }PHP configuration directives may be set through a “-d” command line argument: $ php -d test.scale=4 -r ‘var_dump(test_scale(5));’ float(20)COMMON PHP GLOBALS Except for our own global variables, we may also need to get some values from common PHP global variables that are wrapped into similar module global structures and may be accessed through similar macros: CG(name): Compiler global variables. EG(name): Executor global variables. All declarations can be viewed at Zend/zend_globals.h. Request PDF VersionBook traversal links for 7. Adding New Functionality‹ 6. Running PHP Extension TestsWriting PHP Extensions8. Basic PHP Structures ›