internals:extensions

This is an old revision of the document!


Extensions

This sheet explains how extensions work internally in the engine. We'll see how the get loaded, and what different hooks they provide.

We won't talk about how and why to write an extension.

PHP vs Zend extensions

As you should know, we distinguish between “PHP extensions” and “Zend extensions”. Consider this vocabulary to follow the article, as internally, the sources prefer talking about PHP extensions as “modules” and Zend extensions as “extensions”. We'll keep the more clear “PHP extension” vs “Zend extension” wordings.

Both extension kinds share lots of stuff. The difference between both types is mainly in hooks they register into the Engine. Remember that, despite it is very uncommon, an extension can be both a PHP extension and a Zend extension at the same time. Xdebug is a good example.

Here are the PHP extension structures :

typedef struct _zend_module_entry zend_module_entry;
typedef struct _zend_module_dep zend_module_dep;
 
struct _zend_module_entry {
	unsigned short size;
	unsigned int zend_api;
	unsigned char zend_debug;
	unsigned char zts;
	const struct _zend_ini_entry *ini_entry;
	const struct _zend_module_dep *deps;
	const char *name;
	const struct _zend_function_entry *functions;
	int (*module_startup_func)(INIT_FUNC_ARGS);
	int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	int (*request_startup_func)(INIT_FUNC_ARGS);
	int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
	const char *version;
	size_t globals_size;
#ifdef ZTS
	ts_rsrc_id* globals_id_ptr;
#else
	void* globals_ptr;
#endif
	void (*globals_ctor)(void *global TSRMLS_DC);
	void (*globals_dtor)(void *global TSRMLS_DC);
	int (*post_deactivate_func)(void);
	int module_started;
	unsigned char type;
	void *handle;
	int module_number;
	const char *build_id;
};
 
struct _zend_module_dep {
	const char *name;		/* module name */
	const char *rel;		/* version relationship: NULL (exists), lt|le|eq|ge|gt (to given version) */
	const char *version;	/* version */
	unsigned char type;		/* dependency type */
};

And here are the Zend extension structures :

/* Typedef's for zend_extension function pointers */
typedef int (*startup_func_t)(zend_extension *extension);
typedef void (*shutdown_func_t)(zend_extension *extension);
typedef void (*activate_func_t)(void);
typedef void (*deactivate_func_t)(void);
 
typedef void (*message_handler_func_t)(int message, void *arg);
 
typedef void (*op_array_handler_func_t)(zend_op_array *op_array);
 
typedef void (*statement_handler_func_t)(zend_op_array *op_array);
typedef void (*fcall_begin_handler_func_t)(zend_op_array *op_array);
typedef void (*fcall_end_handler_func_t)(zend_op_array *op_array);
 
typedef void (*op_array_ctor_func_t)(zend_op_array *op_array);
typedef void (*op_array_dtor_func_t)(zend_op_array *op_array);
 
typedef struct _zend_extension {
	char *name;
	char *version;
	char *author;
	char *URL;
	char *copyright;
 
	startup_func_t startup;
	shutdown_func_t shutdown;
	activate_func_t activate;
	deactivate_func_t deactivate;
 
	message_handler_func_t 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;
	op_array_dtor_func_t op_array_dtor;
 
	int (*api_no_check)(int api_no);
	int (*build_id_check)(const char* build_id);
	void *reserved3;
	void *reserved4;
	void *reserved5;
	void *reserved6;
	void *reserved7;
	void *reserved8;
 
	DL_HANDLE handle;
	int resource_number;
} zend_extension;
 
typedef struct _zend_extension_version_info {
	int zend_extension_api_no;
	char *build_id;
} zend_extension_version_info;

Statically compiled extensions

Lots of extensions may be built statically or dynamically. Some require one or the other compilation mode, but they are uncommon. When built statically, the extension is loaded earlier than when it's built dynamically (loaded throught php.ini). So, the first thing to remember is that an extension may behave differently if it is loaded statically against dynamically, at least if the maintainer has not taken care of such a behavior.

When you prepare your sources, part of the prepare work is to write a file main/internal_functions.c This files contains an array with the pointers to all the to-be-statically-compiled extensions. Here is an example :

static zend_module_entry *php_builtin_extensions[] = {
	phpext_date_ptr,
	phpext_ereg_ptr,
	phpext_pcre_ptr,
	phpext_foo_ptr,
	phpext_reflection_ptr,
	phpext_spl_ptr,
	phpext_standard_ptr,
};

Extensions such as standard are mandatory, they are always compiled and built statically. This is because further dynamically loaded extensions may depend on stuff provided by standard, this happens sometimes as standard provides lots of functionnality. Only PHP extensions may be built statically, Zend extensions may not. Extensions which are both PHP and Zend, may be built statically

The first thing PHP does when it loads, is to get those pointers out, and load those extensions. The pointers are pointers to the zend_module_entry, so they are PHP extensions.

Example:

#define phpext_standard_ptr &basic_functions_module
 
zend_module_entry basic_functions_module = { /* {{{ */
	STANDARD_MODULE_HEADER_EX,
	NULL,
	standard_deps,
	"standard",					/* extension name */
	basic_functions,			/* function list */
	PHP_MINIT(basic),			/* process startup */
	PHP_MSHUTDOWN(basic),		/* process shutdown */
	PHP_RINIT(basic),			/* request startup */
	PHP_RSHUTDOWN(basic),		/* request shutdown */
	PHP_MINFO(basic),			/* extension info */
	PHP_VERSION,				/* extension version */
	STANDARD_MODULE_PROPERTIES
};

To register a new PHP extension, the engine calls for zend_register_module_ex(zend_module_entry *module). This does few things :

  • Checks for extension dependencies, but only against conflicts, so it does not load any other extension than the one it's been called with
  • Checks if the extension has already been registered, if it is the case, warns
  • Registers the PHP extension functions into the global function table, calling zend_register_functions(module->functions)

Parsing the ini file to get more extensions

php_init_config() is called, it parses the different ini files (mainly php.ini) and looks for the tokens “extension=” and “zend_extension=”. It then adds the names of the extensions to two different lists, one for PHP exts : extension_lists.functions, and one for Zend exts : extension_lists.engine. Both are zend_llist type. You can find the code by looking for the function php_ini_parser_cb()

Before PHP5.5, Zend extensions needed to be provided the full path to the extension (ini token "zend_extension="), such as /usr/lib/php/foo.so, whereas PHP extensions (using the ini token "extension=") just needed the file name and used the "extension_dir" ini directive to build the full path to load the extension.
As of 5.5, this in note the case any more and both PHP and Zend extension are loaded providing only the file name, not the full path.

At this stage, PHP and Zend extensions have been parsed from ini files, but nothing more has been done.

Registering additionnal extensions from the SAPI module

Again before loading/registering anything from ini files, any SAPI may call php_module_startup() passing it an array of PHP extensions to load (pointers to zend_module_entry) Those get registered here, the same way statically compiled got registered just few steps before, to recall :

  • Checks for extension dependencies, but only against conflicts, so it does not load any other extension than the one it's been called with
  • Checks if the extension has already been registered, if it is the case, warns
  • Registers the PHP extension functions into the global function table, calling zend_register_functions(module->functions)

For example, the Apache SAPI (apxs2), registers a PHP extension that provides functions related to Apache. Each SAPI may register one or more PHP extension providing additionnal functionnality for itself. Apache is a good example.

The CLI SAPI doesn't register any extension

Loading the ini parsed extensions

internals/extensions.1365585563.txt.gz · Last modified: 2017/09/22 13:28 (external edit)