关于zend_parse_parameters函数

时间:2021-03-20 11:11:10

 

PHP_FUNCTION(set_time_limit)
{
    long new_timeout;
    char *new_timeout_str;
    int new_timeout_strlen;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &new_timeout) == FAILURE) {
        return;
    }

    new_timeout_strlen = zend_spprintf(&new_timeout_str, 0, "%ld", new_timeout);

    if (zend_alter_ini_entry_ex("max_execution_time", sizeof("max_execution_time"), new_timeout_str, new_timeout_str

len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0 TSRMLS_CC) == SUCCESS) {
        RETVAL_TRUE;
    } else {
        RETVAL_FALSE;
    }
    efree(new_timeout_str);
}

 

这是一个PHP函数  set_time_limit,zend_parse_parameters()函数的前几个参数我们直接用内核里宏来生成的

ZEND_NUM_ARGS() TSRMLS_CC 注意两者之间有个空格,但是没有逗号。ZEND_NUM_ARGS()代表着参数的个数。

紧接着需要传递给zend_parse_parameters()函数的参数是一个用于格式化的字符串,就像printf的第一个参数一样

type_spec是格式化字符串,其常见的含义如下:


 参数代表着的类型

b Boolean
l Integer 整型
d Floating point 浮点型
s String 字符串
r Resource 资源
a Array 数组
o Object instance 对象
O Object instance of a specified type 特定类型的对象
z Non-specific zval 任意类型~
Z zval**类型
f 表示函数、方法名称,PHP5.3之前没有的

 
这个函数就像printf()函数一样,后面的参数是与格式化字符串里的格式一一对应的。一些基础类型的数据会直接映射成C语言里的类型。

参数对应C里的数据类型
b   zend_bool
l   long
d   double
s   char*, int 前者接收指针,后者接收长度
r   zval*
a   zval*
o   zval*
O   zval*, zend_class_entry*
z   zval*
Z   zval**

 

下面的一些字符在类型说明字符串(就是那个 char *type_spec)中具有特别的含义:

 

| - 表明剩下的参数都是可选参数。如果用户没有传进来这些参数值,那么这些值就会被初始化成默认值。

/ - 表明参数解析函数将会对剩下的参数以 SEPARATE_ZVAL_IF_NOT_REF() 的方式来提供这个参数的一份拷贝,除非这些参数是一个引用。

! - 表明剩下的参数允许被设定为 NULL(仅用在 a、o、O、r和z身上)。如果用户传进来了一个 NULL 值,则存储该参数的变量将会设置为 NULL。

 

当然啦,熟悉这个函数的最好的方法就是举个例子来说明。下面我们就来看一个例子:

/* 取得一个长整数,一个字符串和它的长度,再取得一个 zval 值 */
long l;
char *s;
int s_len;
zval *param;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"lsz", &l, &s, &s_len, &param) == FAILURE) {
   return;
}

/* 取得一个由 my_ce 所指定的类的一个对象,另外再取得一个可选的双精度的浮点数 */
zval *obj;
double d = 0.5;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"O|d", &obj, my_ce, &d) == FAILURE) {
   return;
}

/* 取得一个对象或空值,再取得一个数组 如果传递进来一个空对象,则 obj 将被设置为 NULL */
zval *obj;
zval *arr;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O!a", &obj, &arr) == FAILURE) {
   return;
}

/* 取得一个分离过的数组 */
zval *arr;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/", &arr) == FAILURE) {
   return;
}

/* 仅取得前 3 个参数(这对可变参数的函数很有用) */
zval *z;
zend_bool b;
zval *r;
if (zend_parse_parameters(3, "zbr!", &z, &b, &r) == FAILURE) {
   return;
}

 


注意,在最后的一个例子中,我们直接用了数值 3 而不是 ZEND_NUM_ARGS() 来作为想要取得参数的个数。
这样如果我们的 PHP 函数具有可变参数的话我们就可以只接收最小数量的参数。当然,如果你想操作剩下的参数,你可以用 zend_get_parameters_array_ex() 来得到

这个参数解析函数还有一个带有附加标志的扩展版本,这个标志可以让你控制解析函数的某些动作。

 

int zend_parse_parameters_ex(int flags, int num_args TSRMLS_DC, char *type_spec, ...);

 

这个标志(flags)目前仅接受 ZEND_PARSE_PARAMS_QUIET 这一个值,它表示这个函数不输出任何错误信息。这对那些可以传入完全不同类型参数的函数非常有用,但这样你也就不得不自己输出错误信息

下面就是一个如何既可以接收 3 个长整形数又可以接收一个字符串的例子:

 

long l1, l2, l3;
char *s;
if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
                            ZEND_NUM_ARGS() TSRMLS_CC,
                            “lll”, &l1, &l2, &l3) == SUCCESS) {
   /* manipulate longs */
} else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
                                   ZEND_NUM_ARGS(), “s”, &s, &s_len) == SUCCESS) {
   /* manipulate string */
} else {
   php_error(E_WARNING, “%s() takes either three long values or a string as argument”,
             get_active_function_name(TSRMLS_C));
   return;
}

 

我想你通过上面的那些例子就可以基本掌握如何接收和处理参数了。如果你想看更多的例子,请翻阅 PHP 源码包中那些自带的扩展的源代码,那里面包含了你可能遇到的各种情况。

以前的老式的获取参数的的方法(不推荐)

获取函数参数这件事情我们还可以通过 zend_get_parameters_ex() 来完成(不推荐使用这些旧式的 API,我们推荐您使用前面所述的新式的参数解析函数):

 

zval **parameter;
if(zend_get_parameters_ex(1, &parameter) != SUCCESS)
 WRONG_PARAM_COUNT;

 

所有的参数都存储在一个二次指向的 zval 容器里面(其实就是一个 zval* 数组,译者注)。上面的这段代码尝试接收 1 个参数并且将其保存在 parameter 所指向的位置。

zend_get_parameters_ex() 至少需要两个参数。第一个参数表示我们想要接收参数的个数(这个值通常是对应于 PHP 函数参数的个数,由此也可以看出事先对调用语法正确性的检查是多么重要)。第二个参数(包括剩下的所有参数)指向一个二次指向 zval 的指针。(即 ***zval,是不是有点糊涂了?^_^)这些指针是必须的,因为 Zend 内部是使用 **zval 进行工作的。为了能被在我们函数内部定义的 **zval 局部变量所访问,我们就必须在用一个指针来指向它。

zend_get_parameters_ex() 的返回值可以是 SUCCESSFAILURE,分别表示参数处理的成功或失败。如果处理失败,那最大的可能就是由于没有指定一个正确的参数个数。如果处理失败,则应该使用宏 WRONG_PARAM_COUNT 来退出函数。

如果想接收更多的的参数,可以用类似下面一段的代码来处理:

zval **param1, **param2, **param3, **param4;
if(zend_get_parameters_ex(4, &param1, &param2, &param3, &param4) != SUCCESS)
   WRONG_PARAM_COUNT;

 

zend_get_parameters_ex() 仅检查你是否在试图访问过多的参数。如果函数有 5 个参数,而你仅仅接收了其中的 3 个,那么你将不会收到任何错误信息,zend_get_parameters_ex() 仅返回前三个参数的值。再次调用 zend_get_parameters_ex() 也不会获得剩下两个参数的值,而还是返回前三个参数的值。

接收可变(可选)参数

如果你想接收一些可变参数,那用前面我们刚刚讨论的方法就不太合适了,主要是因为我们将不得不为每个可能的参数个数来逐行调用 zend_get_parameters_ex(),显然这很不爽。

为了解决这个问题,我们可以借用一下 zend_get_parameters_array_ex() 这个函数。它可以帮助我们接收不定量的参数并将其保存在我们指定的地方:

zval **parameter_array[4];

 

/* 取得参数个数 */
argument_count = ZEND_NUM_ARGS();

/* 看一下参数个数是否满足我们的要求:最少 2 个,最多 4个。 */
if(argument_count < 2 || argument_count > 4)
   WRONG_PARAM_COUNT;
/* 参数个数正确,开始接收。 */
if(zend_get_parameters_array_ex(argument_count, parameter_array) != SUCCESS)
   WRONG_PARAM_COUNT;

 

让我们来看看这几行代码。首先代码检查了传入参数的个数,确保在我们可接受的范围内;然后就调用 zend_get_parameters_array_ex() 把所有有效参数值的指针填入 parameter_array

我们可以在 fsockopen() 函数(位于ext/standard/fsock.c )中找到一个更为漂亮的实现。代码大致如下,你也不用担心还没有弄懂全部的函数,因为我们很快就会谈到它们。

 

PHP中带有可变参数的 fsockopen() 函数的实现
pval **args[5];
int *sock=emalloc(sizeof(int));
int *sockp;
int arg_count=ARG_COUNT(ht);
int socketd = -1;
unsigned char udp = 0;
struct timeval timeout = { 60, 0 };
unsigned short portno;
unsigned long conv;
char *key = NULL;
FLS_FETCH();
if (arg_count > 5 || arg_count < 2 || zend_get_parameters_array_ex(arg_count,args)==FAILURE) {
   CLOSE_SOCK(1);
   WRONG_PARAM_COUNT;
}
switch(arg_count) {
   case 5:
       convert_to_double_ex(args[4]);
       conv = (unsigned long) (Z_DVAL_PP(args[4]) * 1000000.0);
       timeout.tv_sec = conv / 1000000;
       timeout.tv_usec = conv % 1000000;
       /* fall-through */
   case 4:
       if (!PZVAL_IS_REF(*args[3])) {
           php_error(E_WARNING,”error string argument to fsockopen not passed by reference”);
       }
       pval_copy_constructor(*args[3]);
       ZVAL_EMPTY_STRING(*args[3]);
       /* fall-through */
   case 3:
       if (!PZVAL_IS_REF(*args[2])) {
           php_error(E_WARNING,”error argument to fsockopen not passed by reference”);
           return;
       }
       ZVAL_LONG(*args[2], 0);
       break;
}
convert_to_string_ex(args[0]);
convert_to_long_ex(args[1]);
portno = (unsigned short) Z_LVAL_P(args[1]);
key = emalloc(Z_STRLEN_P(args[0]) + 10);

 

 

fsockopen() 可以接收 2-5 个参数。在必需的变量声明之后便开始检查参数的数量范围。然后在一个 switch 语句中使用了贯穿(fall-through)法来处理这些的参数。这个 switch 语句首先处理最大的参数个数(即 5),随后依次处理了参数个数为 4 和 3 的情况,最后用 break 关键字跳出 switch 来忽略对其他情况下参数(也就是只含有 2 个参数情况)的处理。这样在经过 switch 处理之后,就开始处理参数个数为最小时(即 2)的情况。

这种像楼梯一样的多级处理方法可以帮助我们很方便地处理一些可变参数。

存取参数

为 了存取一些参数,让每个参数都具有一个明确的(C)类型是很有必要的。但 PHP是一种动态语言,PHP 从不做任何类型检查方面的工作,因此不管你想不想,调用者都可能会把任何类型的数据传到你的函数里。比如说,如果你想接收一个整数,但调用者却可能会给你 传递个数组,反之亦然-PHP 可不管这些的。

为了避免这些问题,你就必须用一大套 API 函数来对传入的每一个参数都做一下强制性的类型转换。(见表3.4 参数类型转换函数)

注意: 所有的参数转换函数都以一个 **zval 来作为参数。