深入理解PHP内核

Thinking In PHP Internal

扩展中参数的解析

在前面 tipi_hello_world 的扩展例子中,通过 zend_parse_parameters 获取到了 tipi_hello_world() 函数传入的字符串参数。下面我们首先对 API 进行讲解,然后说明其原理。 最后说明 Fast Parameter Parsing API。

zend_parse_parameters 介绍

END_API int zend_parse_parameters(int num_args, const char *type_spec, ...)

zend_parse_parameters 解析参数,第一个参数是传递的参数个数。通常使用 ZEND_NUM_ARGS() 来获取。 第二个参数是一个字符串,指定了函数期望的各个参数的类型,后面紧跟着需要随参数值更新的变量列表。 因为PHP采用松散的变量定义和动态的类型判断,这样做就使得把不同类型的参数转化为期望的类型成为可能。

下表列出了可能指定的类型。我们从完整性考虑也列出了一些没有讨论到的类型。

类型指定符 对应的C类型 描述
l long 符号整数
d double 浮点数
s char *, int 二进制字符串,长度
b zend_bool 逻辑型(1或0)
r zval * 资源(文件指针,数据库连接等)
a zval * 联合数组
o zval * 任何类型的对象
O zval * 指定类型的对象。需要提供目标对象的类类型
z zval * 无任何操作的zval

例如下面的例子

zend_parse_parameters(ZEND_NUM_ARGS(), "sl", &str, &str_len, &n)

该表达式则是获取两个参数 strn,字符串的类型是s,需要两个参数 char * 字符串和 int 长度;数字的类型 l ,只需要一个参数。

zend_parse_parameters 动态获取参数的实现原理

由其源码可知

ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...) /* {{{ */
{
   va_list va;
   int retval;
   int flags = 0;
 
   va_start(va, type_spec);
   retval = zend_parse_va_args(num_args, type_spec, &va, flags);
   va_end(va);
 
   return retval;
}

主要使用到了 va_list, va_start, va_argva_end 来获取可变个数的参数。 通过如下代码的练习即可明白其原理了

#include <stdarg.h>
#include <stdio.h>
 
int zend_parse_parameters(int num_args, const char *type_spec, ...);
 
int main() {
 
    zend_parse_parameters(4, "abcd", 1, 2, 3, 4);
    return 0;
}
 
int zend_parse_parameters(int num_args, const char *type_spec, ...) {
    va_list va;
    const char *spec_walk;
    char c;
 
    va_start(va, type_spec);
    for (spec_walk = type_spec; *spec_walk; spec_walk++) {
        c = *spec_walk;
        printf("参数类型为 %c 值为: %d\n", c, va_arg(va, int));
    }
    va_end(va);
 
    return 0;
}

输出结果为

参数类型为 a 值为: 1
参数类型为 b 值为: 2
参数类型为 c 值为: 3
参数类型为 d 值为: 4

使用 Fast Parameter Parsing API 取代 zend_parse_parameters

官方推荐在 PHP7 中使用 Fast Parameter Parsing API,不仅效率上有所提升,并且代码表意更加明确,易读性强。 下面引用官方的说明,简短翻译说明(完整地内容请参考本节末尾的参考链接)

PHP_FUNCTION(array_slice) 为例

PHP_FUNCTION(array_slice)
{
 
/* 省略一系列的参数声明 */
 
#ifndef FAST_ZPP
    if (zend_parse_parameters(ZEND_NUM_ARGS(), "al|zb", &input, &offset, &z_length, &preserve_keys) == FAILURE) {
        return;
    }
#else
    ZEND_PARSE_PARAMETERS_START(2, 4)
        Z_PARAM_ARRAY(input)
        Z_PARAM_LONG(offset)
        Z_PARAM_OPTIONAL
        Z_PARAM_ZVAL(z_length)
        Z_PARAM_BOOL(preserve_keys)
    ZEND_PARSE_PARAMETERS_END();
#endif
 
/* 省略后面的逻辑代码 */
 
}

在没有定义 FAST_ZPP 的情况下,使用 zend_parse_parameters 来解析, al|zb 表示第一个参数为 array,第二个参数为 long,第三个参数为 zval,第四个参数为 bool。 并且后面两个参数为可选。

如果定了 FAST_ZPP (该定义在 PHP7 Zend/zend_API.h 中),则使用 Fast Parameter Parsing API 方式来解析参数。代码本身已经足以解释其作用了。 ZEND_PARSE_PARAMETERS_START() 的两个参数分别为最少参数数和最多参数数。 Z_PARAM_ARRAY() 则将参数视为数组,Z_PARAM_LONG() 将参数视为长整型。 而 Z_PARAM_OPTIONAL 则表示后面的参数为可选参数。完整地映射表关系,可参考本节最后的参考链接。

参考资料