添加新功能
到此为止,你应该已经了解了 PHP 扩展的基本结构和构建过程。现在,我们将学习如何实现新的基本 PHP 扩展功能。从本节开始,我将用红色写入新代码,并将现有代码保留为黑色。
函数
函数是添加新功能的最简单基元。要实现一个新函数,我们首先要编写函数代码本身。这是一段以 PHP_FUNCTION()
宏和函数名开头的普通 C 代码。这段代码需要一个双精度数值参数,并返回一个按因子 2 缩放的参数。稍后我将介绍参数解析 API 和大多数用于操作数值的宏。
PHP_FUNCTION(test_scale)
{
double x;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_DOUBLE(x)
ZEND_PARSE_PARAMETERS_END();
RETURN_DOUBLE(x * 2);
}
我们还必须定义参数描述块。该代码块从 ZEND_BEGIN_ARG_INFO()
宏(或其变体)开始,以 ZEND_END_ARG_INFO()
宏结束。
ZEND_BEGIN_ARG_INFO()
的第一个参数是 arg_info
结构的名称。该名称应在 PHP_FE()
宏中重复使用。
第二个参数被忽略。(在 PHP 5 中,它意味着通过引用传递其余参数。) 每个参数都由 ZEND_ARG_INFO()
宏定义,该宏接收 "通过引用传递" 值和参数名称。
ZEND_BEGIN_ARG_INFO(arginfo_test_scale, 0)
ZEND_ARG_INFO(0, x)
ZEND_END_ARG_INFO()
还可以使用 ARG_INFO
宏的扩展变量来指定额外的参数和返回类型提示、空值、通过引用返回、所需参数数、参数数可变的函数等。
最后,我们必须将新函数添加到扩展函数列表中:
static const zend_function_entry test_functions[] = {
PHP_FE(test_test1, arginfo_test_test1)
PHP_FE(test_test2, arginfo_test_test2)
//--begin-- 新增
PHP_FE(test_scale, arginfo_test_scale)
//--end-- 新增
PHP_FE_END
};
在 PHP 的 Zend 引擎中,ZEND_BEGIN_ARG_INFO() 和 ZEND_PARSE_PARAMETERS_START() 都是用于处理函数参数的宏,但它们的用途和作用范围不同。下面详细解释它们的区别和用途。
区别总结
示例总结 ZEND_BEGIN_ARG_INFO()
ZEND_PARSE_PARAMETERS_START()
|
扩展重建和安装后,新功能应该开始工作。
$ php -r 'var_dump(test_scale(5));'
float(10)
要在命名空间中声明函数,可以使用 ZEND_NS_FUNCTION(ns, name)
代替 PHP_FUNCTION(name)
,也可以使用 ZEND_NS_FE(ns, name, arg_info)
代替 PHP_FE(name, arg_info)
。
还可以添加一些函数标志(例如,添加 ZEND_ACC_DEPRECATED
标志的废弃函数),使用 ZEND_FENTRY()
代替 PHP_FE()
。主扩展 API 参见 Zend/zend_API.h。
扩展回调
只有 PHP 扩展函数可以纯声明方式实现。所有其他扩展功能都可以在 PHP 启动时调用特殊的 API 函数来实现。为此,扩展应实现 MINIT()
回调。这也是一个普通的 C 语言函数,以 PHP_MINIT_FUNCTION()
宏和扩展名称作为参数。该函数应返回 SUCCESS,以便将 PHP 扩展连接到内核,并启用其所有功能和其它特性。我们的 MINIT
函数只是初始化线程本地存储缓存。以前,这段代码是从 RINIT
中调用的,但如果在 MINIT
中执行某些操作,并直接或间接访问模块全局变量或通用全局变量,最好将这段代码移到 MINIT
的开头。
PHP_MINIT_FUNCTION(test)
{
#if defined(ZTS) && defined(COMPILE_DL_TEST)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
return SUCCESS;
}
MINIT
回调地址应添加到模块入口结构中。如果 RINIT
回调仅用于线程本地存储,且现在为空,也可以将其删除。
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
};
与 MINIT
类似,当需要在 PHP 终止前释放一些资源时,也可以执行其他回调,如 MSHUTDOWN
。
常量
MINIT
回调适用于添加各种扩展实体,如新的内部常量。这需要使用 REGISTER_LONG_CONSTANT()
宏,其中第一个参数是常量名称,第二个参数是常量值,第三个参数是常量标志:
-
CONST_CS
指区分大小写的常量名称。 -
CONST_PERSISTENT
指持久常量。(所有内部扩展常量都应是持久常量)。
PHP_MINIT_FUNCTION(test)
{
#if defined(ZTS) && defined(COMPILE_DL_TEST)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
// --- start 新增
REGISTER_LONG_CONSTANT("TEST_SCALE_FACTOR", 2, CONST_CS | CONST_PERSISTENT);
// --- end
return SUCCESS;
}
扩展重建和重新安装后,您可以访问新的常量。
$ php -r 'var_dump(TEST_SCALE_FACTOR);'
int(2)
当然,还有其他各种 API 宏来声明不同值类型的常量:
-
REGISTER_NULL_CONSTANT(name, flags):值为 NULL 的常量。
-
REGISTER_BOOL_CONSTANT(name, bval, flags):FALSE 或 TRUE。
-
REGISTER_LONG_CONSTANT(name, lval, flags):任意长数字。
-
REGISTER_DOUBLE_CONSTANT(name, dval, flags):一个双数值。
-
REGISTER_STRING_CONSTANT(name, str, flags):以零结尾的字符串。
-
REGISTER_STRINGL_CONSTANT(name,str,len,flags):字符串(带长度)。
也可以使用类似的 REGISER_NS_…()
宏来声明某些命名空间中的常量。有关完整的 PHP 常量 API,请参见 Zend/zend_constants.h。
模块全局变量
目前,我们的新 scale()
函数是纯功能性的。它不依赖于任何内部状态,并且始终只根据输入参数返回一个值。然而,现实世界中有些函数会访问或更新全局状态。在 C 语言中,它通常存储在全局变量中,但多线程软件必须使用一些技巧来区分不同线程的状态。
PHP 通常是在单线程环境中运行的,但也可以配置为多线程。无论如何,为了使全局变量能够在不同的配置中移植,PHP 建议将全局变量声明为一个特别声明的 "module globals" 结构。
应在 php_test.h
文件末尾添加以下代码:
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)
模块全局变量是在 ZEND_BEGIN_MODULE_GLOBALS
和 ZEND_END_MODULE_GLOBALS
宏之间声明的。这些只是普通的 C 声明。实际上,C 预处理器会将它们转换为 zend_tests_globlas
C 结构定义。ZEND_EXTERN_MODULE_GLOBLAS()
定义了访问该结构的外部 C 语言名称,而 TEST_G()
宏则提供了访问模块全局变量的方法。因此,我们将使用 TEST_G(scale)
来代替全局变量 scale
。
我们应该在 test.c
文件中声明真正的模块全局变量:
ZEND_DECLARE_MODULE_GLOBALS(test)
我们还将定义一个 GINIT
回调来初始化该结构。该回调会在 MINIT
之前调用,因此我们必须将线程本地存储缓存的初始化代码移至此处:
static PHP_GINIT_FUNCTION(test)
{
#if defined(COMPILE_DL_BCMATH) && defined(ZTS)
ZEND_TSRMLS_CACHE_UPDATE();
#endif
test_globals->scale= 1;
}
我们还应在扩展入口结构中添加有关模块全局变量及其初始化回调的信息:
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 /
// ----start 新增
PHP_MODULE_GLOBALS(test), /* Module globals */
PHP_GINIT(test), /* PHP_GINIT – Globals initialization */
NULL, /* PHP_GSHUTDOWN – Globals shutdown */
NULL,
STANDARD_MODULE_PROPERTIES_EX
// ----end
};
现在我们可以更新 scale
函数,使用模块全局变量:
PHP_FUNCTION(test_scale)
{
double x;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_DOUBLE(x)
ZEND_PARSE_PARAMETERS_END();
//新增 TEST_G(scale)
RETURN_DOUBLE(x * TEST_G(scale));
}
重新编译和重新安装扩展后,它就能正常工作了:
$ php -r 'var_dump(test_scale(5));'
float(5)
配置指令
我们还能做什么?我们可以通过 php.ini
中的配置指令来定义 scale
因子的值。这可以通过 test.c.
中的两段附加代码来实现。
第一段定义了配置指令、其名称、默认值、类型和存储位置:
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()
声明了一个名为 test.scale
的配置指令,默认值为 "1"。PHP_INI_ALL
表示可以随时修改该配置指令(在 php.ini、每个目录的配置文件和脚本执行过程中的 ini_set()
函数中)。而 PHP_INI_SYSTEM
则只允许在 PHP 启动时(php.ini 中)修改。PHP_INI_PERDIR
只允许在 php.ini 和每个目录的配置文件中修改。
OnUpdateLong
是一个常用的回调,用于设置指令的整数值。(scale
"是模块的全局变量名称。zend_test_globals
是保存模块全局变量的结构名(C 类型名)。test_globals
是全局变量,用于保存非线程安全构建的模块全局变量。
完整的 PHP.ini API 定义在 Zend/zend_ini.h 和 main/php_ini.h。
第二部分调用了一个 API 函数,用于注册前一个代码块中声明的指令:
PHP_MINIT_FUNCTION(test)
{
//---start 新增
REGISTER_INI_ENTRIES();
//---end
return SUCCESS;
}
PHP 配置指令可以通过 -d
命令行参数设置:
$ php -d test.scale=4 -r 'var_dump(test_scale(5));'
float(20)
常见的 php 全局
除了我们自己的全局变量外,我们可能还需要从常见的 PHP 全局变量中获取一些值,这些全局变量被封装在类似的模块全局结构中,并可以通过类似的宏来访问:
-
CG(name):编译器全局变量。
-
EG(name):执行器全局变量。
所有声明可在 Zend/zend_globals.h 中查看。