这里了解一下扩展开发中PHP函数是如何实现的,包括参数解析、返回结果等。
0. 函数相关宏
开始之前,先了解一下PHP中一些与函数相关的结构或宏。
函数定义:PHP_FUNCTION(name)
PHP_FUNCTION
是一个用于定义PHP函数头的宏,使用方法:
PHP_FUNCTION(func_name)
{
// 函数体
}
这里顺便讲一下
PHP_FUNCTION
,ZEND_FUNCTION
,PHP_METHOD
,ZEND_METHOD
的关系。
PHP_FUNCTION
用于定义PHP函数,PHP_METHOD
用于定义类的方法。ZEND_FUNCTION
与PHP_FUNCTION
是等效的,PHP_FUNCTION
只是ZEND_FUNCTION
的别名,PHP_METHOD
与ZEND_METHOD
也是这样的关系。
函数参数:ZEND_BEGIN_ARG_INFO(name, _unused)
、ZEND_END_ARG_INFO()
、ZEND_ARG_INFO(pass_by_ref, name)
ZEND_BEGIN_ARG_INFO
与ZEND_END_ARG_INFO
是配对使用的,用于声明参数组,它们的宏展开为:
// ZEND_BEGIN_ARG_INFO(name, _unused)展开(_unused其实没有被使用到)
static const zend_internal_arg_info name[] = { \
{ (const char*)(zend_uintptr_t)(required_num_args), 0, return_reference, 0 },
// ZEND_END_ARG_INFO()展开
};
可以看到,它们在一起实际是声明了一个zend_internal_arg_info数组,用于存储一系列参数信息。
而ZEND_ARG_INFO
则是在ZEND_BEGIN_ARG_INFO
与ZEND_END_ARG_INFO
之间,用于初始化一个zend_internal_arg_info,定义某个具体的参数,它的宏定义为:
#define ZEND_ARG_INFO(pass_by_ref, name) { #name, 0, pass_by_ref, 0},
ZEND_ARG_INFO用于声明普通参数,复杂参数需要使用另外的宏:
- ZEND_ARG_OBJ_INFO —— 对象
- ZEND_ARG_ARRAY_INFO —— 数组
...
函数入口:zend_function_entry
结构体、PHP_FE(name, arg_info)
zend_function_entry
是用于向Zend引擎中注册函数的载体,每个需要提供给PHP的函数都对应一个zend_function_entry
。
使用:
// 这是一段ext_sket脚手架生成的代码
// zend_function_entry数组
static const zend_function_entry ext_test_functions[] = {
// 函数
PHP_FE(ext_test_test1, arginfo_ext_test_test1)
PHP_FE_END
};
zend_module_entry ext_test_module_entry = {
STANDARD_MODULE_HEADER,
"ext_test", /* Extension name */
ext_test_functions, // 向Zend引擎中注册一系列函数
NULL, /* PHP_MINIT - Module initialization */
NULL, /* PHP_MSHUTDOWN - Module shutdown */
PHP_RINIT(ext_test), /* PHP_RINIT - Request initialization */
NULL, /* PHP_RSHUTDOWN - Request shutdown */
PHP_MINFO(ext_test), /* PHP_MINFO - Module info */
PHP_EXT_TEST_VERSION, /* Version */
STANDARD_MODULE_PROPERTIES
};
PHP_FE
就是用于初始化一个zend_function_entry
结构体的。需要传入两个参数:函数名及参数信息zend_internal_arg_info[]。
参数解析:ZEND_PARSE_PARAMETERS_NONE()
、ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args)
、ZEND_PARSE_PARAMETERS_END()
、Z_PARAM_OPTIONAL
、Z_PARAM_*
这一类宏用在函数体内,即PHP_FUNCTION
中。
ZEND_PARSE_PARAMETERS_NONE()
: 无需要参数ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args)
: 开始参数解析,min_num_args最少参数数量,max_num_args最大参数数量ZEND_PARSE_PARAMETERS_END()
: 结束参数解析,与ZEND_PARSE_PARAMETERS_START
成对使用Z_PARAM_OPTIONAL
: 表示参数可选Z_PARAM_*
: 具体参数
使用方法:
// 无参数函数
PHP_FUNCITON(test_func)
{
ZEND_PARSE_PARAMETERS_NONE();
zend_printf("test"); // zend_printf与php_printf等效,PHP在启动时会将php_printf赋值给zend_printf指针
}
PHP_FUNCTION(test_func2)
{
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_S
Z_PARAM_OPTIONAL()
ZEND_PARSE_PARAMETERS_END();
}
1. 无参无返回值函数
// ...
// test_func1(): void
PHP_FUNCTION(test_func1)
{
ZEND_PARSE_PARAMETERS_NONE();
php_printf("Hello world!");
}
// ...
ZEND_BEGIN_ARG_INFO(arginfo_test_func1, 0)
ZEND_END_ARG_INFO()
static const zend_function_entry ext_test_functions[] = {
PHP_FE(test_func1, arginfo_test_func1)
PHP_FE_END
};
// ...
test_func1(); // 输出Hello world!
2. 带固定类型参数,且有返回值的函数
// test_func2(int id, string name="anhoder"): string
PHP_FUNCTION(test_func2)
{
zend_long id;
zend_string *name, *res;
name = zend_string_init("anhoder", strlen("anhoder"), 0);
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_LONG(id)
Z_PARAM_OPTIONAL
Z_PARAM_STR(name)
ZEND_PARSE_PARAMETERS_END();
res = zend_strpprintf(0, "%lld: %s\n", id, ZSTR_VAL(name));
RETVAL_STR(res);
zend_string_release(name);
zend_string_release(res);
}
// ...
ZEND_BEGIN_ARG_INFO(arginfo_test_func2, 0)
ZEND_ARG_TYPE_INFO(0, id, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
ZEND_END_ARG_INFO()
static const zend_function_entry ext_test_functions[] = {
PHP_FE(test_func2, arginfo_test_func2)
PHP_FE_END
};
// ...
3. 参数传引用的函数
// test_func3(array &$arr): void
PHP_FUNCTION(test_func3)
{
zval *arr;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_EX2(arr, 0, 1, 0)
ZEND_PARSE_PARAMETERS_END();
add_assoc_long(arr, "key", 123);
add_index_string(arr, 4, "name");
}
ZEND_BEGIN_ARG_INFO(arginfo_test_func3, 0)
ZEND_ARG_TYPE_INFO(1, arr, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
static const zend_function_entry ext_test_functions[] = {
PHP_FE(test_func3, arginfo_test_func3)
PHP_FE_END
};
4. 参数传对象、callable的函数
// mixed test_func4(callable, ...args)
PHP_FUNCTION(test_func4)
{
zval result;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
ZEND_PARSE_PARAMETERS_START(2, -1)
Z_PARAM_FUNC(fci, fcc)
Z_PARAM_VARIADIC('*', fci.params, fci.param_count)
ZEND_PARSE_PARAMETERS_END();
fci.retval = &result;
if (zend_call_function(&fci, &fcc) != SUCCESS) {
return;
}
RETURN_ZVAL(&result, 1, 0);
}
ZEND_BEGIN_ARG_INFO(arginfo_test_func4, 0)
ZEND_ARG_TYPE_INFO(0, callable, IS_CALLABLE, 0)
ZEND_ARG_INFO(0, args)
ZEND_END_ARG_INFO()
static const zend_function_entry ext_test_functions[] = {
PHP_FE(test_func4, arginfo_test_func4)
PHP_FE_END
};
以上几个函数的PHP测试代码
<?php
test_func1();
var_dump(test_func2(1));
var_dump(test_func2(2, 'test'));
$arr = [1,2];
test_func3($arr);
var_dump($arr);
$callback = function (...$args) {
foreach ($args as $arg) {
var_dump($arg);
}
};
test_func4($callback, 1, 2, 3, 'str1', 'str2');