扩展PHP:Zend API中对参数的处理

来源:互联网 发布:25n16aa数据 编辑:程序博客网 时间:2024/05/20 20:43
 

判断参数的个数

使用ZEND_NUM_ARGS()宏定义可以得到参数的个数。(宏定义一般都是大写字母)

例如:

if(ZEND_NUM_ARGS() != 2) WRONG_PARAM_COUNT;

这里,WRONG_PARAM_COUNT同样是一个宏定义(zend_API.h):

ZEND_API void wrong_param_count(void);
#define WRONG_PARAM_COUNT { wrong_param_count(); return; }

如果参数个数错误,WRONG_PARAM_COUNT会打印出相应的错误信息。

得到参数

这个参数解析函数的声明大致如下:

int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, …); 

第一个参数num_args表明了我们想要接收的参数个数,我们经常使用ZEND_NUM_ARGS() 来表示对传入的参数“有多少要多少”

。第二参数应该总是宏 TSRMLS_CC 。第三个参数 type_spec 是一个字符串,用来指定我们所期待接收的各个参数的类型,有

点类似于 printf 中指定输出格式的那个格式化字符串。剩下的参数就是我们用来接收PHP参数值的变量的指针。

zend_parse_parameters() 在解析参数的同时会尽可能地转换参数类型,这样就可以确保我们总是能得到所期望的类型的变量

。任何一种标量类型都可以转换为另外一种标量类型,但是不能在标量类型与复杂类型(比如数组、对象和资源等)之间进行

转换。 

如果成功地解析和接收到了参数并且在转换期间也没出现错误,那么这个函数就会返回 SUCCESS,否则返回 FAILURE。如果这

个函数不能接收到所预期的参数个数或者不能成功转换参数类型时就会抛出一些类似下面这样的错误信息: 

Warning - ini_get_all() requires at most 1 parameter, 2 given
Warning - wddx_deserialize() expects parameter 1 to be string, array given 

当然,每个错误信息都会带有错误发生时所在的文件名和行数的。 

在一个PHP扩展函数(foobar)里,相应的调用方式如下:

PHP_FUNCTION(foobar)
{
    long num1;
    long num2;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ll", &num1, &num2) == FAILURE) {
        return;
    }

    // ...
}

在zend_parse_parameters的参数中,"ZEND_NUM_ARGS() TSRMLS_CC"部分基本是固定打法,接着的部分代表参数的相应类型(多个参数则连续声明),这里"l"代表类型是long,"ll"则代表两个long类型的参数,再后面(&num1, &num2)就是参数变量指针了。

下面是一个完整的参数类型列表:

b Boolean (zend_bool)
l Integer (long)
d Floating point (double)
s String (char*, int)
r Resource (zval*)
a Array (zval*)
o Object instance (zval*)
O Object instance of a specified type (zval*, zend_class_entry*)
z Non-specific zval (zval*)

需要说明的一点是:zend_parse_parameters中如果声明了一个字符串类型,那么后面对应的参数将是两个,第一个代表字符串,第二个代表字符串长度。如果声明了一个指定类型的对象实例,那么后面对应的参数也是两个,第一个代表对象,第二个代表对象的类型。

此外,Zend API还规定了很多参数修饰符,以完成更灵活的参数设置,如下:

| - 表明此修饰符后面的参数是可选的,它们通常由扩展赋予缺省初始值。

/ - 表明此修饰符前面的参数将会被SEPARATE_ZVAL_IF_NOT_REF()调用。

! - 表明此修饰符前面的参数既可以是一个指定的类型也可以是一个NULL(仅被a, o, O, r和z修饰符支持) 

下面举几个简单的例子:

/* Gets a long, a string and its length, and a 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;
}

/* Gets an object of class specified by my_ce, and an optional double. */
zval *obj;
double d = 0.5;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
                          "O|d", &obj, my_ce, &d) == FAILURE) {
    return;
}

/* Gets an object or null, and an array.
   If null is passed for object, obj will be set to NULL. */
zval *obj;
zval *arr;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O!a", &obj, &arr) == FAILURE) {
    return;
}

/* Gets a separated array. */
zval *arr;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/", &arr) == FAILURE) {
    return;
}

/* Get only the first three parameters (useful for varargs functions). */
zval *z;
zend_bool b;
zval *r;
if (zend_parse_parameters(3, "zbr!", &z, &b, &r) == FAILURE) {
    return;
}

最后一个例子使用在参数个数不固定的函数的情况下,在本例中,是解析前3个参数,如果想操作剩下的参数的话,则可以使用下面的Zend API来搞定。

#include <zend_API.h>
int zend_get_parameters_array_ex ( int $param_count, zval*** $argument_array )

例子:

zval **parameter_array[4]; 

/* get the number of arguments */ 
argument_count = ZEND_NUM_ARGS(); 

/* see if it satisfies our minimal request (2 arguments) */ 
/* and our maximal acceptance (4 arguments) */ 
if(argument_count < 2 || argument_count > 5) 
WRONG_PARAMETER_COUNT; 

/* argument count is correct, now retrieve arguments */ 
if(zend_get_parameters_array_ex(argument_count, parameter_array) != SUCCESS) 
WRONG_PARAMETER_COUNT;

如上,相应的参数就被保存到了parameter_array数组中。

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

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;
}

存取参数    

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

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

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

表3.4 参数类型转换函数     函数说明convert_to_boolean_ex()强制转换为布尔类型。若原来是布尔值则保留,不做改动。长整型值0、双精度型值0.0、空字符串或字符串‘0’还有空值 NULL 都将被转换为 FALSE(本质上是一个整数 0)。数组和对象若为空则转换为 FALSE,否则转为 TRUE。除此之外的所有值均转换为 TRUE(本质上是一个整数 1)。convert_to_long_ex()强制转换为长整型,这也是默认的整数类型。如果原来是空值NULL、布尔型、资源当然还有长整型,则其值保持不变(因为本质上都是整数 0)。双精度型则被简单取整。包含有一个整数的字符串将会被转换为对应的整数,否则转换为 0。空的数组和对象将被转换为 0,否则将被转换为 1。 convert_to_double_ex()强制转换为一个双精度型,这是默认的浮点数类型。如果原来是空值 NULL 、布尔值、资源和双精度型则其值保持不变(只变一下变量类型)。包含有一个数字的字符串将被转换成相应的数字,否则被转换为 0.0。空的数组和对象将被转换为 0.0,否则将被转换为 1.0。convert_to_string_ex()强制转换为字符串。空值 NULL 将被转换为空字符串。布尔值 TRUE 将被转换为 ‘1’,FALSE 则被转为一个空字符串。长整型和双精度型会被分别转换为对应的字符串,数组将会被转换为字符串‘Array’,而对象则被转换为字符串‘Object’。 convert_to_array_ex(value)强制转换为数组。若原来就是一数组则不作改动。对象将被转换为一个以其属性为键名,以其属性值为键值的数组。(方法将会被转化为一个‘scalar’键,键值为方法名?待验证)空值 NULL 将被转换为一个空数组。除此之外的所有值都将被转换为仅有一个元素(下标为0)的数组,并且该元素即为该值。convert_to_object_ex(value)强制转换为对象。若原来就是对象则不作改动。空值NULL 将被转换为一个空对象。数组将被转换为一个以其键名为属性,键值为其属性值的对象。其他类型则被转换为一个具有‘scalar’属性的对象,‘scalar’属性的值即为该值本身。convert_to_null_ex(value)强制转换为空值 NULL。

在你的参数上使用这些函数可以确保传递给你的数据都是类型安全的。如果提供的类型不是需要的类型,PHP 就会强制性地返回一个相应的伪值(比如空字符串、空的数组或对象、数值0或布尔值的 FALSE 等)来确保结果是一个已定义的状态。

下面的代码是从前面讨论过的模块中摘录的,其中用到了这些转换函数:

zval **parameter;

if((ZEND_NUM_ARGS() != 1) || (zend_get_parameters_ex(1, &parameter) != SUCCESS))
{
   WRONG_PARAM_COUNT;
}

convert_to_long_ex(parameter);

RETURN_LONG(Z_LVAL_P(parameter));

引用传递参数

PHP_FUNCTION(sample_byref_calltime)
{
    zval *a;
    int addtl_len = sizeof(" (modified by ref!)") - 1;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &a) == FAILURE) {
        RETURN_NULL();
    }
    if (!a->is_ref) {
        /* parameter was not passed by reference,
         * leave without doing anything
         */
        return;
    }
    /* Make sure the variable is a string */
    convert_to_string(a);
    /* Enlarge a's buffer to hold the additional data */
    Z_STRVAL_P(a) = erealloc(Z_STRVAL_P(a),
        Z_STRLEN_P(a) + addtl_len + 1);
    memcpy(Z_STRVAL_P(a) + Z_STRLEN_P(a),
    " (modified by ref!)", addtl_len + 1);
    Z_STRLEN_P(a) += addtl_len;
}

主要就是zval *a; a->is_ref的使用。