5. Extension Skeleton File Content
Let’s review the contents of extension skeleton files.
“config.m4” is an extension configuration script, used during generation of “configure” script by “phpize” or “buildconf” commands. It’s written in M4 macro-processing language. Very basic knowledge is enough for PHP extension configuration. You can copy-paste blocks from this tutorial or other extension configuration files.
PHP_ARG_ENABLE([test],
[whether to enable test support],
[AS_HELP_STRING([--enable-test],
[Enable test support])],
[no])
if test "$PHP_TEST" != "no"; then
AC_DEFINE(HAVE_TEST, 1, [ Have test support ])
PHP_NEW_EXTENSION(test, test.c, $ext_shared)
fi
PHP_ARG_ENABLE(...) – macro adds a configuration option “--enable-test”. It may get three values “yes”, “no”, and “shared”.
When you run “phpize”, the default value is “shared” which means we are going to build a dynamically loadable PHP extension. However, it is possible to copy the “test” extension directory into the main PHP distribution (“ext/test”) and re-run “./buildconf” and “./configure … –enable-test” to re-build the whole PHP with extension “test”, statically linked in.
It’s possible to enable extension by default, replacing “no” to “yes” at line 5. In this case, it’s possible to disable “test” extension by “./configure --disable-test”.
Following “if” is just a regular UNIX shell code that tests the value defined by “--enable-test”, “--disable-test”, or “--enable-test=shared”.
AC_DEFINE(HAVE_TEST) adds C macro HAVE_TEST into “config.h”, so you can use conditional compilation directives (#ifdef, #ifndef) to skip useless code, if necessary.
Finally, the PHP_NEW_EXTENSION (test, test.c, $ext_shared) macro states that we are going to build extension “test” from “test.c” file. It’s possible to specify few files. Depending on the value of $ext_shared variable, the extension could be built as shared object or linked statically. (It’s taken from the same “--enable-test” option.)
This file might need to be extended in case you add new source files or need to link some external libraries. I’ll show how to link libraries later. Just, don’t forget to rerun “phpize”/”buildconf” + “configure” after you make any changes in this file.
Windows PHP uses a different build system. For Windows, file “config.w32” is a replacement of “config.m4”. The two are almost the same. They use similar macros, just a different language: on Windows PHP build system uses JavaScript instead of M4 and Shell. I won’t repeat the explanation of the macros. You should be able to guess.
ARG_ENABLE('test', 'test support', 'no');
if (PHP_TEST != 'no') {
AC_DEFINE('HAVE_TEST', 1, 'test support enabled');
EXTENSION('test', 'test.c', null, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
}
“php_test.h” is a C header file with common definitions. In our very basic case, it defines:
- test_module_entry — an extension description structure. (It’s an entry point to the extension.)
- PHP_TEST_VERSION — a version of the extension.
- ZEND_TSRMLS_CACHE_EXTERN — a thread-local storage cache entry, if the extension was built for a thread-safe build (ZTS) and compiled as shared object ( COMPILE_DL_TEST).
/* test extension for PHP */
#ifndef PHP_TEST_H
# define PHP_TEST_H
extern zend_module_entry test_module_entry;
# define phpext_test_ptr &test_module_entry
# define PHP_TEST_VERSION "0.1.0"
# if defined(ZTS) && defined(COMPILE_DL_TEST)
ZEND_TSRMLS_CACHE_EXTERN()
# endif
#endif /* PHP_TEST_H */
“test.c” is the main (and in our case, single) extension source file. It’s too big to fit into one page/screen, so I’ll split it into small parts and explain each part separately.
/* test extension for PHP */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "php.h"
#include "ext/standard/info.h"
#include "php_test.h"
Include necessary C header files. You may add additional “#include” directives if necessary.
/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
ZEND_PARSCE_PARAMETERS_START(0, 0) \
ZEND_PARSE_PARAMETERS_END()
#endif
Some forward compatibility macro, to make it possible to compile the extension for older PHP-7 versions.
/* {{{ void test_test1()
*/
PHP_FUNCTION(test_test1)
{
ZEND_PARSE_PARAMETERS_NONE();
php_printf("The extension %s is loaded and working!\r\n", "test");
}
/* }}} */
A C code for function test_test1() provided by our PHP extension. The argument of PHP_FUNCTION() macro is the function name. ZEND_PARSE_PARAMETERS_NONE() tells that this function doesn’t require any arguments. php_printf(...) is just a C function call that prints the string into the output stream, similar to PHP printf() function.
/* {{{ string test_test2( [ string $var ] )
*/
PHP_FUNCTION(test_test2)
{
char *var = "World";
size_t var_len = sizeof("World") – 1;
zend_string *retval;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_STRING(var, var_len)
ZEND_PARSE_PARAMETERS_END();
retval = strpprintf(0, "Hello %s", var);
RETURN_STR(retval);
}
/* }}}*/
Another, more complex function uses “Fast Parameter Parsing API” to describe its arguments.
ZEND_PARSE_PARAMETERS_START(0, 1) starts the parameter description section. Its first argument (0) defines the number of required arguments.
The second argument (1) defines the maximum number of arguments. So, our function may be called without arguments, or with a single argument.
Inside this section, we should define all parameters, their types, and where they will be copied. For our case:
- Z_PARAM_OPTIONAL separates required parameters from optional ones.
- Z_PARAM_STRING() defines a string parameter that value is going to be copied to variable “var” and the length into variable “var_len.”
Note that our argument is optional and therefore may be omitted. In this case a default value “World” is used. See initializers for variables “var” and “var_len” above ZEND_PARSE_PARAMETERS_START.
The code creates a “zend_string” value and returns it though macro RETURN_STR() similar to PHP sprintf() function:
/* {{{ PHP_RINIT_FUNCTION
*/
PHP_RINIT_FUNCTION(test)
{
#if defined(ZTS) && defined(COMPILE_DL_TEST)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
return SUCCESS;
}
/* }}} */
PHP_RINIT_FUNCTION() defines a callback function that is going to be called at each request start-up. In our case, it only initializes thread-local storage cache. It would be much better to do this early (in MINIT or GINIT callbacks). I predict this will be fixed in the PHP 8 extension skeleton.
/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(test)
{
php_info_print_table_start();
php_info_print_table_header(2, "test support", "enabled");
php_info_print_table_end();
}
/* }}} */
PHP_MINFO_FUNCTION() defines a callback function that is going to be called from PHP phpinfo() function, to print information about the extension.
/* {{{ arginfo
*/
ZEND_BEGIN_ARG_INFO(arginfo_test_test1, 0)
ZEND_END_ARG_INFO()
Information about arguments of the first function. There are no arguments.
ZEND_BEGIN_ARG_INFO(arginfo_test_test2, 0)
ZEND_ARG_INFO(0, str)
ZEND_END_ARG_INFO()
/* }}} */
Information about arguments of the second function. The single optional argument with name “str” is passed by value.
/* {{{ test_functions[]
*/
static const zend_function_entry test_functions[] = {
PHP_FE(test_test1, arginfo_test_test1)
PHP_FE(test_test2, arginfo_test_test2)
PHP_FE_END
};
/* }}} */
“test_functions” is a list of all extension functions with information about their arguments. The list is terminated by PHP_FE_END macro.
/* {{{ test_module_entry
*/
zend_module_entry test_module_entry = {
STANDARD_MODULE_HEADER,
"test", /* Extension name */
test_functions, /* zend_function_entry */
NULL, /* PHP_MINIT - Module initialization */
NULL, /* PHP_MSHUTDOWN - Module shutdown */
PHP_RINIT(test), /* PHP_RINIT - Request initialization */
NULL, /* PHP_RSHUTDOWN - Request shutdown */
PHP_MINFO(test), /* PHP_MINFO - Module info */
PHP_TEST_VERSION, /* Version */
STANDARD_MODULE_PROPERTIES
};
/* }}} */
test_module_entry is the main extension entry structure. PHP core takes all information about extensions from such structures. It defines:
- Extension name (“test”).
- A list of declared PHP functions (“test_functions”).
- A few callback functions and extension version (PHP_TEST_VERSION - defined in the header file).
The callbacks occur when PHP started (MINIT), on PHP termination (MSHUTDOWN), at start of each request processing (RINIT), at the end of each request processing (RSHUTDOWN) and from phpinfo() (MINFO).
#ifdef COMPILE_DL_TEST
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(test)
#endif
Finally, a couple of definitions for dynamic linking.