当前位置:Gxlcms > PHP教程 > PHP7参数、数组和Zvals

PHP7参数、数组和Zvals

时间:2021-07-01 10:21:17 帮助过:4人阅读

起步

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

接收参数

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

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

  1. PHP_FUNCTION(hello_greetme)
  2. { char *name = NULL;
  3. size_t name_len;
  4. zend_string *strg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE) {
  5. RETURN_NULL();
  6. }
  7. strg = strpprintf(0, "你好: %s", name);
  8. RETURN_STR(strg);
  9. }

大多数 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,是为了提高参数解析的性能。

  1. #ifdef FAST_ZPPZEND_PARSE_PARAMETERS_START(1, 2)
  2. Z_PARAM_STR(type) Z_PARAM_OPTIONAL Z_PARAM_ZVAL_EX(value, 0, 1)
  3. ZEND_PARSE_PARAMETERS_END();#endif

参数类型表

154420_BzEY_1450051.png

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

Zval

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

  1. struct _zval_struct {
  2. zend_value value; /* value */
  3. union {
  4. struct {
  5. ZEND_ENDIAN_LOHI_4(
  6. zend_uchar type, /* active type */
  7. zend_uchar type_flags,
  8. zend_uchar const_flags,
  9. zend_uchar reserved) /* call info for EX(This) */
  10. } v;
  11. uint32_t type_info;
  12. } u1;
  13. union {
  14. uint32_t next; /* hash collision chain */
  15. uint32_t cache_slot; /* literal cache slot */
  16. uint32_t lineno; /* line number (for ast nodes) */
  17. uint32_t num_args; /* arguments number for EX(This) */
  18. uint32_t fe_pos; /* foreach position */
  19. uint32_t fe_iter_idx; /* foreach iterator index */
  20. uint32_t access_flags; /* class constant access flags */
  21. uint32_t property_guard; /* single property guard */
  22. } u2;
  23. };

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

  1. typedef union _zend_value {
  2. zend_long lval; /* long value */
  3. double dval; /* double value */
  4. zend_refcounted *counted;
  5. zend_string *str;
  6. zend_array *arr;
  7. zend_object *obj;
  8. zend_resource *res;
  9. zend_reference *ref;
  10. zend_ast_ref *ast;
  11. zval *zv;
  12. void *ptr;
  13. zend_class_entry *ce;
  14. zend_function *func;
  15. struct {
  16. uint32_t w1;
  17. uint32_t w2;
  18. } ww;
  19. } zend_value;

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

zval 类型

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

  1. /* regular data types */
  2. #define IS_UNDEF 0
  3. #define IS_NULL 1
  4. #define IS_FALSE 2
  5. #define IS_TRUE 3
  6. #define IS_LONG 4
  7. #define IS_DOUBLE 5
  8. #define IS_STRING 6
  9. #define IS_ARRAY 7
  10. #define IS_OBJECT 8
  11. #define IS_RESOURCE 9
  12. #define IS_REFERENCE 10
  13. /* constant expressions */
  14. #define IS_CONSTANT 11
  15. #define IS_CONSTANT_AST 12
  16. /* fake types */
  17. #define _IS_BOOL 13
  18. #define IS_CALLABLE 14
  19. #define IS_ITERABLE 19
  20. #define IS_VOID 18
  21. /* internal types */
  22. #define IS_INDIRECT 15
  23. #define IS_PTR 17
  24. #define _IS_ERROR 20

测试

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

  1. PHP_FUNCTION(hello_typeof)
  2. {
  3. zval *userval = NULL;
  4. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &userval) == FAILURE) {
  5. RETURN_NULL();
  6. }
  7. switch (Z_TYPE_P(userval)) {
  8. case IS_NULL:
  9. RETVAL_STRING("NULL");
  10. break;
  11. case IS_TRUE:
  12. RETVAL_STRING("true");
  13. break;
  14. case IS_FALSE:
  15. RETVAL_STRING("false");
  16. break;
  17. case IS_LONG:
  18. RETVAL_STRING("integer");
  19. break;
  20. case IS_DOUBLE:
  21. RETVAL_STRING("double");
  22. break;
  23. case IS_STRING:
  24. RETVAL_STRING("string");
  25. break;
  26. case IS_ARRAY:
  27. RETVAL_STRING("array");
  28. break;
  29. case IS_OBJECT:
  30. RETVAL_STRING("object");
  31. break;
  32. case IS_RESOURCE:
  33. RETVAL_STRING("resource");
  34. break;
  35. default:
  36. RETVAL_STRING("unknown type");
  37. }
  38. }

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

  1. #define RETVAL_STRING(s) ZVAL_STRING(return_value, s)
  2. #define RETVAL_STRINGL(s, l) ZVAL_STRINGL(return_value, s, l)
  3. #define RETURN_STRING(s) { RETVAL_STRING(s); return; }
  4. #define RETURN_STRINGL(s, l) { RETVAL_STRINGL(s, l); return; }

创建zval

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

  1. {
  2. zval temp;
  3. ZVAL_LONG(&temp, 1234);
  4. }

数组

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

154420_BzEY_1450051.png

做一个测试:

  1. PHP_FUNCTION(hello_get_arr)
  2. {
  3. array_init(return_value);
  4. add_next_index_null(return_value);
  5. add_next_index_long(return_value, 42);
  6. add_next_index_bool(return_value, 1);
  7. add_next_index_double(return_value, 3.14);
  8. add_next_index_string(return_value, "foo");
  9. add_assoc_string(return_value, "mno", "baz");
  10. add_assoc_bool(return_value, "ghi", 1);
  11. }

154420_BzEY_1450051.png

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

数组遍历

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

  1. <?php
  2. function hello_array_strings($arr) {
  3. if (!is_array($arr)) {
  4. return NULL;
  5. }
  6. printf("The array passed contains %d elements\n", count($arr));
  7. foreach ($arr as $data) {
  8. if (is_string($data))
  9. echo $data.'\n';
  10. }
  11. }

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)

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

  1. PHP_FUNCTION(hello_array_strings)
  2. {
  3. ulong num_key;
  4. zend_string *key;
  5. zval *val, *arr;
  6. HashTable *arr_hash;
  7. int array_count;
  8. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &arr) == FAILURE) {
  9. RETURN_NULL();
  10. }
  11. arr_hash = Z_ARRVAL_P(arr);
  12. array_count = zend_hash_num_elements(arr_hash);
  13. php_printf("The array passed contains %d elements\n", array_count);
  14. ZEND_HASH_FOREACH_KEY_VAL(arr_hash, num_key, key, val) {
  15. //if (key) { //HASH_KEY_IS_STRING
  16. //}
  17. PHPWRITE(Z_STRVAL_P(val), Z_STRLEN_P(val));
  18. php_printf("\n");
  19. }ZEND_HASH_FOREACH_END();
  20. }

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

人气教程排行