PHP扩展开发——函数

这里了解一下扩展开发中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_FUNCTIONPHP_FUNCTION是等效的,PHP_FUNCTION只是ZEND_FUNCTION的别名,PHP_METHODZEND_METHOD也是这样的关系。

函数参数:ZEND_BEGIN_ARG_INFO(name, _unused)ZEND_END_ARG_INFO()ZEND_ARG_INFO(pass_by_ref, name)

ZEND_BEGIN_ARG_INFOZEND_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_INFOZEND_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_OPTIONALZ_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');