资讯专栏INFORMATION COLUMN

PHP7扩展开发(三):参数、数组和Zvals

hufeng / 1609人阅读

摘要:告诉引擎要取的参数的信息,用来确保线程安全,返回值检测是还是。数组遍历假设我们需要一个取代以下功能的扩展的遍历数组和差很多,提供了一些专门的宏来遍历元素或。是一个关于线程安全的动作,用于避免各线程的作用域被其他的侵入。

起步

到这已经能声明简单函数,返回静态或者动态值了。定义INI选项,声明内部数值或全局数值。本章节将介绍如何接收从调用脚本(php文件)传入参数的数值,以及 PHP内核Zend引擎 如何操作内部变量。

接收参数

与用户控件的代码不同,内部函数的参数实际上并不是在函数头部声明的,函数声明都形如: PHP_FUNCTION(func_name) 的形式,参数声明不在其中。参数的传入是通过参数列表的地址传入的,并且是传入每一个函数,不论是否存在参数。

通过定义函数hello_str()来看一下,它将接收一个参数然后把它与问候的文本一起输出。

PHP_FUNCTION(hello_greetme)
{
    char *name = NULL;
    size_t name_len;
    zend_string *strg;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
        RETURN_NULL();
    }

    strg = strpprintf(0, "你好: %s", name);
    RETURN_STR(strg);
}

大多数 zend_parse_parameters() 块看起来都差不多。 ZEND_NUM_ARGS() 告诉Zend引擎要取的参数的信息, TSRMLS_CC 用来确保线程安全,返回值检测是SUCCESS还是FAILURE。通常情况下返回是SUCCESS的。除非传入的参数太少或太多或者参数不能被转为适当的类型,Zend会自动输出一条错误信息并将控制权还给调用脚本。

指定 "s" 表明此函数期望只传入一个参数,并且该参数被转化为string数据类型,地址传入char * 变量。

此外,还有一个int变量通过地址传递到 zend_parse_parameters() 。这使Zend引擎提供字符串的字节长度,如此二进制安全的函数不再依赖strlen(name)来确定字符串的长度。因为实际上使用strlen(name)甚至得不到正确的结果,因为name可能在字符串结束之前包含了NULL字符。

在php7中,提供另一种获取参数的方式FAST_ZPP,是为了提高参数解析的性能。

#ifdef FAST_ZPP
ZEND_PARSE_PARAMETERS_START(1, 2)
    Z_PARAM_STR(type)
    Z_PARAM_OPTIONAL
    Z_PARAM_ZVAL_EX(value, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
参数类型表
类型 代码 变量类型
Boolean b zend_bool
Long l long
Double d double
String s char*, int
Resource r zval *
Array a zval *
Object o zval *
zval z zval *

最后四个类型都是zvals *.这是因为在php的实际使用中,zval数据类型存储所有的用户空间变量。三种“复杂”数据类型:资源、数组、对象。当它们的数据类型代码被用于zend_parse_parameters()时,Zend引擎会进行类型检查,但是因为在C中没有与它们对应的数据类型,所以不会执行类型转换。

Zval

一般而言,zval和php用户空间变量是很伤脑筋的,概念很难懂。到了PHP7,它的结构在Zend/zend_types.h中有定义:

struct _zval_struct {
    zend_value        value;            /* value */
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
        uint32_t     access_flags;         /* class constant access flags */
        uint32_t     property_guard;       /* single property guard */
    } u2;
};

可以看到,变量是通过_zval_struct结构体存储的,而变量的值是zend_value类型的:

typedef union _zend_value {
    zend_long         lval;             /* long value */
    double            dval;             /* double value */
    zend_refcounted  *counted;
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

虽然结构体看起来很大,但细细看,其实都是联合体,value的扩充,u1是type_info,u2是其他各种辅助字段。

zval 类型

变量存储的数据是有数据类型的,php7中总体有以下类型,Zend/zend_types.h中有定义:

/* regular data types */
#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10

/* constant expressions */
#define IS_CONSTANT                 11
#define IS_CONSTANT_AST             12

/* fake types */
#define _IS_BOOL                    13
#define IS_CALLABLE                 14
#define IS_ITERABLE                 19
#define IS_VOID                     18

/* internal types */
#define IS_INDIRECT                 15
#define IS_PTR                      17
#define _IS_ERROR                   20
测试

书写一个类似gettype()来取得变量的类型的hello_typeof():

PHP_FUNCTION(hello_typeof)
{
    zval *userval = NULL;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &userval) == FAILURE) {
        RETURN_NULL();
    }
    switch (Z_TYPE_P(userval)) {
        case IS_NULL:
            RETVAL_STRING("NULL");
            break;

        case IS_TRUE:
            RETVAL_STRING("true");
            break;

        case IS_FALSE:
            RETVAL_STRING("false");
            break;

        case IS_LONG:
            RETVAL_STRING("integer");
            break;

        case IS_DOUBLE:
            RETVAL_STRING("double");
            break;

        case IS_STRING:
            RETVAL_STRING("string");
            break;

        case IS_ARRAY:
            RETVAL_STRING("array");
            break;

        case IS_OBJECT:
            RETVAL_STRING("object");
            break;

        case IS_RESOURCE:
            RETVAL_STRING("resource");
            break;

        default:
            RETVAL_STRING("unknown type");
    }
}

这里使用RETVAL_STRING()与之前的RETURN_STRING()差别并不大,它们都是宏。只不过RETURN_STRING中包含了RETVAL_STRING的宏代替,详细在 Zend/zend_API.h 中有定义:

#define RETVAL_STRING(s)                ZVAL_STRING(return_value, s)
#define RETVAL_STRINGL(s, l)            ZVAL_STRINGL(return_value, s, l)

#define RETURN_STRING(s)                { RETVAL_STRING(s); return; }
#define RETURN_STRINGL(s, l)            { RETVAL_STRINGL(s, l); return; }
创建zval

前面用到的zval是由Zend引擎分配空间,也通过同样的途径释放。然而有时候需要创建自己的zval,可以参考如下代码:

{
    zval temp;
    ZVAL_LONG(&temp, 1234);
}
数组

数组作为运载其他变量的变量。内部实现上使用了众所周知的 HashTable .要创建将被返回PPHP的数组,最简单的方法:

PHP语法 C语法(arr是zval*) 意义
$arr = array(); array_init(arr); 初始化一个新数组
$arr[] = NULL; add_next_index_null(arr); 向数字索引的数组增加指定类型的值
$arr[] = 42; add_next_index_long(arr, 42);
$arr[] = true; add_next_index_bool(arr, 1);
$arr[] = 3.14; add_next_index_double(arr, 3.14);
$arr[] = "foo"; add_next_index_string(arr, "foo", 1);
$arr[] = $myvar; add_next_index_zval(arr, myvar);
$arr[0] = NULL; add_index_null(arr, 0); 向数组中指定的数字索引增加指定类型的值
$arr[1] = 42; add_index_long(arr, 1, 42);
$arr[2] = true; add_index_bool(arr, 2, 1);
$arr[3] = 3.14; add_index_double(arr, 3, 3.14);
$arr[4] = "foo"; add_index_string(arr, 4, "foo", 1);
$arr[5] = $myvar; add_index_zval(arr, 5, myvar);
$arr["abc"] = NULL; add_assoc_null(arr, "abc");
$arr["def"] = 711; add_assoc_long(arr, "def", 711); 向关联索引的数组增加指定类型的值
$arr["ghi"] = true; add_assoc_bool(arr, "ghi", 1);
$arr["jkl"] = 1.44; add_assoc_double(arr, "jkl", 1.44);
$arr["mno"] = "baz"; add_assoc_string(arr, "mno", "baz", 1);
$arr["pqr"] = $myvar; add_assoc_zval(arr, "pqr", myvar);

做一个测试:

PHP_FUNCTION(hello_get_arr)
{
    array_init(return_value);
    add_next_index_null(return_value);
    add_next_index_long(return_value, 42);
    add_next_index_bool(return_value, 1);
    add_next_index_double(return_value, 3.14);
    add_next_index_string(return_value, "foo");
    add_assoc_string(return_value, "mno", "baz");
    add_assoc_bool(return_value, "ghi", 1);
}

add_*_string()函数参数从四个改为了三个。

数组遍历

假设我们需要一个取代以下功能的扩展:


php7的遍历数组和php5差很多,7提供了一些专门的宏来遍历元素(或keys)。宏的第一个参数是HashTable,其他的变量被分配到每一步迭代:

ZEND_HASH_FOREACH_VAL(ht, val)
ZEND_HASH_FOREACH_KEY(ht, h, key)
ZEND_HASH_FOREACH_PTR(ht, ptr)
ZEND_HASH_FOREACH_NUM_KEY(ht, h)
ZEND_HASH_FOREACH_STR_KEY(ht, key)
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val)
ZEND_HASH_FOREACH_KEY_VAL(ht, h, key, val)

因此它的对应函数实现如下:

PHP_FUNCTION(hello_array_strings)
{
    ulong num_key;
    zend_string *key;
    zval *val, *arr;
    HashTable *arr_hash;
    int array_count;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE) {
        RETURN_NULL();
    }

    arr_hash = Z_ARRVAL_P(arr);
    array_count = zend_hash_num_elements(arr_hash);
    php_printf("The array passed contains %d elements
", array_count);
    ZEND_HASH_FOREACH_KEY_VAL(arr_hash, num_key, key, val) {
        //if (key) { //HASH_KEY_IS_STRING
        //}
        PHPWRITE(Z_STRVAL_P(val), Z_STRLEN_P(val));
        php_printf("
");
    }ZEND_HASH_FOREACH_END();
}

因为这是新的遍历方法,而我看的还是php5的处理方式,调试出上面的代码花了不少功夫,总的来说,用宏的方式遍历大大减少了编码体积。哈希表是php中很重要的一个内容,有时间再好好研究一下。

遍历数组的其他方式

遍历 HashTable 还有其他方法。Zend引擎针对这个任务展露了三个非常类似的函数:zend_hash_apply(), zend_hash_apply_with_argument(), zend_hash_apply_with_arguments。第一个形式仅仅遍历HashTable,第二种形式允许传入单个void*参数,第三种形式通过var arg列表允许数量不限的参数。hello_array_walk()展示个他们各自的行为。

static int php_hello_array_walk(zval *ele TSRMLS_DC)
{
    zval temp = *ele; // 临时zval,避免convert_to_string 污染原元素
    zval_copy_ctor(&temp);  // 分配新 zval 空间并复制 ele 的值
    convert_to_string(&temp); // 字符串类型转换

    //简单的打印
    PHPWRITE(Z_STRVAL(temp), Z_STRLEN(temp));
    php_printf("
");
    zval_dtor(&temp); //释放临时的 temp
    return ZEND_HASH_APPLY_KEEP;
}

static int php_hello_array_walk_arg(zval *ele, char *greeting TSRMLS_DC)
{
    php_printf("%s", greeting);
    php_hello_array_walk(ele TSRMLS_CC);
    return ZEND_HASH_APPLY_KEEP;
}

static int php_hello_array_walk_args(zval *ele, int num_args, va_list args, zend_hash_key *hash_key)
{
    char *prefix = va_arg(args, char*);
    char *suffix = va_arg(args, char*);

    TSRMLS_FETCH();
    php_printf("%s", prefix);
    // 打印键值对结果
    php_printf("key is : [ ");
    if (hash_key->key) {
        PHPWRITE(ZSTR_VAL(hash_key->key), ZSTR_LEN(hash_key->key));
    } else {
        php_printf("%ld", hash_key->h);
    }
    php_printf(" ]");
    php_hello_array_walk(ele TSRMLS_CC);
    php_printf("%s
", suffix);
    return ZEND_HASH_APPLY_KEEP;
}

用户调用的函数:

PHP_FUNCTION(hello_array_walk)
{
    zval *arr;
    HashTable *arr_hash;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE) {
        RETURN_NULL();
    }

    arr_hash = Z_ARRVAL_P(arr);

    //第一种遍历 简单遍历各个元素
    zend_hash_apply(arr_hash, (apply_func_t)php_hello_array_walk TSRMLS_CC);
    //第二种遍历 带一个参数的简单遍历各个元素
    zend_hash_apply_with_argument(arr_hash, (apply_func_arg_t)php_hello_array_walk_arg, "Hello " TSRMLS_CC);
    //第三种遍历 带多参数的遍历key->value
    zend_hash_apply_with_arguments(arr_hash, (apply_func_args_t)php_hello_array_walk_args, 2, "Hello ", "Welcome to my extension!");

    RETURN_TRUE;
}

为了复用,在输出值时调用php_hello_array_walk(ele TSRMLS_CC)。传入hello_array_walk()的数组被遍历了三次,一次不带参数,一次带单个参数,一次带两给参数。三个遍历的函数返回了ZEND_HASH_APPLY_KEEP。这告诉zend_hash_apply()函数离开HashTable中的(当前)元素,继续处理下一个。

这儿也可以返回其他值:ZEND_HASH_APPLY_REMOVE
除当前元素并继续应用到下一个;ZEND_HASH_APPLY_STOP在当前元素中止数组的遍历并退出zend_hash_apply()函数。

TSRMLS_FETCH() 是一个关于线程安全的动作,用于避免各线程的作用域被其他的侵入。因为zend_hash_apply()的多线程版本用了vararg列表,tsrm_ls标记没有传入walk()函数。

"888", "key2"=>"aaa"];
hello_array_walk($arr);

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/22065.html

相关文章

  • [译]将PHP扩展从PHP5升级到NG(PHP7)

    摘要:在这些情况下,应将相应的变量转换为纯,使用此变量从到和相应的创建宏从,到,。因此使用标志的宏被移除,和。宏可以用于在析构函数中达到实际的指针值。另外,如果使用添加元素,析构函数也负责指针本身的解除分配。应使用最佳的宏,而不是旧的和功能。 许多经常使用的API函数已经更改,例如HashTable API; 这个页面致力于记录尽可能多的实际影响扩展和核心代码的更改。 强烈建议在阅读本指南之...

    Chaz 评论0 收藏0
  • 【天赢金创】PHP7与Swoole

    摘要:但在密集计算方面比等静态编译语言差几十倍甚至上百倍。一使用栈内存在引擎和扩展中,经常要创建一个的变量,底层就是一个指针。代码中创建的变量也进行了优化,直接在栈内存上预分配。应用层与底层在错误抛出的方式全部统一为异常。 原文:http://rango.swoole.com/archives/440最近PHP官方终于发布了传说中的PHP7,虽然只是alpha版。PHP7号称是新一代的PHP...

    MingjunYang 评论0 收藏0
  • PHP 7 新特征

    摘要:本次发布标志着新的重要的系列的开始。经过社区投票,新项目命名为。结果如下结果如下四新特性标量类型声明有两种模式强制默认和严格模式。已废弃的和函数已被移除。在中,如果发生这种情况,会引发错误,并且返回。 最好的语言发布了新的版本,一个划时代的大版本:PHP7。 PHP7修复了大量BUG,新增了功能和语法糖。这些改动涉及到了核心包、GD库、PDO、ZIP、ZLIB等熟悉和不熟悉的核心功能与...

    Channe 评论0 收藏0
  • PHP 7 修改了什么呢 --1

    摘要:此版本被认为是在年发布后最重要的变化。标量类型声明有两种选择强制强制性是默认模式,不需要指定。严格严格模式有明确的暗示。 PHP7是什么鬼? PHP7是PHP编程语言的一个主要版本,并号称是开发Web应用程序的一次革命,可开发和交付移动企业和云应用。此版本被认为是PHP在2004年发布PHP5后最重要的变化。 新功能 PHP7有加入几十个功能,最显著的是下面提到 - 改进的性能 - P...

    svtter 评论0 收藏0
  • Badoo 告诉你切换到 PHP7 节省了 100 万美元

    摘要:我们为了处理这些挑战,提出了一个新的引用测试框架当然,也是开源的,并且在整个过程中节省了上百万美元。另一方面,被证实有一些严重的缺点部署困难而且慢。在缓存刷新期间,当可用于别的进程的已缓存的文件字节码在此时损坏,就会导致崩溃。 How Badoo saved one million dollars switching to PHP7 我们成功的把我们的应用迁移到了php7上面(数百台机...

    biaoxiaoduan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<