起步
到這已經能宣告簡單函數,回傳靜態或動態值了。定義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_ZPPZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(type) Z_PARAM_OPTIONAL Z_PARAM_ZVAL_EX(value, 0, 1) ZEND_PARSE_PARAMETERS_END();#endif
參數類型表
最後四個型別都是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_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()函数参数从四个改为了三个。
数组遍历
假设我们需要一个取代以下功能的拓展:
<?php function hello_array_strings($arr) { if (!is_array($arr)) { return NULL; } printf("The array passed contains %d elements\n", count($arr)); foreach ($arr as $data) { if (is_string($data)) echo $data.'\n'; } }
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\n", 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("\n"); }ZEND_HASH_FOREACH_END(); }
因为这是新的遍历方法,而我看的还是php5的处理方式,调试出上面的代码花了不少功夫,总的来说,用宏的方式遍历大大减少了编码体积。哈希表是php中很重要的一个内容,有时间再好好研究一下。