はじめに
これで、簡単な関数を宣言し、静的または動的値を返すことができます。 INI オプションを定義し、内部値またはグローバル値を宣言します。この章では、呼び出し元のスクリプト(phpファイル)から渡されるパラメータの値を受け取る方法と、PHPカーネルとZendエンジンが内部変数を操作する方法を紹介します。
パラメータを受け取る
ユーザー制御コードとは異なり、内部関数のパラメータは実際には関数ヘッダーで宣言されません。関数宣言は PHP_FUNCTION(func_name) の形式であり、パラメータ宣言は含まれません。パラメータはパラメータ リストのアドレスを通じて渡され、パラメータの有無に関係なくすべての関数に渡されます。
関数 hello_str() を定義して見てみましょう。この関数はパラメーターを受け取り、挨拶テキストとともに出力します。
1 2 3 4 5 6 7 8 9 10 | 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」を指定すると、この関数は 1 つのパラメーターのみが渡されることを予期し、パラメーターは文字列データ型に変換され、アドレスが char * 変数に渡されます。
さらに、アドレスによって zend_parse_parameters() に渡される int 変数があります。これにより、Zend エンジンが文字列のバイト長を提供できるようになり、バイナリ セーフ関数が文字列の長さを決定するために strlen(name) に依存しなくなります。 name には文字列の末尾の前に NULL 文字が含まれる可能性があるため、実際に strlen(name) を使用しても正しい結果さえ得られないからです。
php7 では、パラメータ解析のパフォーマンスを向上させるために、パラメータを取得する別の方法 FAST_ZPP が提供されています。
1 2 3 | #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
|
ログイン後にコピー
パラメータ型テーブル

最後の 4 つの型はすべて zvals * です。これは、実際の PHP の使用では、zval データ型にすべてのユーザー空間変数が格納されるためです。 3 つの「複雑な」データ型: リソース、配列、オブジェクト。それらのデータ型コードが zend_parse_parameters() で使用される場合、Zend エンジンは型チェックを実行しますが、C にはそれらに対応するデータ型がないため、型変換は実行されません。
Zval
一般的に、zvalとphpのユーザー空間変数は非常に面倒で、概念を理解するのが難しいです。 PHP7 では、その構造は Zend/zend_types.h で定義されています:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | struct _zval_struct {
zend_value value;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type,
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved)
} v;
uint32_t type_info;
} u1;
union {
uint32_t next;
uint32_t cache_slot;
uint32_t lineno;
uint32_t num_args;
uint32_t fe_pos;
uint32_t fe_iter_idx;
uint32_t access_flags;
uint32_t property_guard;
} u2;
};
|
ログイン後にコピー
変数は _zval_struct 構造を通じて格納されており、変数の値は zend_value 型であることがわかります:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | typedef union _zend_value {
zend_long lval;
double dval;
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;
|
ログイン後にコピー
構造は次のように見えますが、非常に大きいですが、よく見ると、これは実際には共用体であり、u1 は type_info であり、u2 はその他のさまざまな補助フィールドです。
zvalタイプ
变量存储的数据是有数据类型的,php7中总体有以下类型,Zend/zend_types.h中有定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #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
#define IS_CONSTANT 11
#define IS_CONSTANT_AST 12
#define _IS_BOOL 13
#define IS_CALLABLE 14
#define IS_ITERABLE 19
#define IS_VOID 18
#define IS_INDIRECT 15
#define IS_PTR 17
#define _IS_ERROR 20
|
ログイン後にコピー
测试
书写一个类似gettype()来取得变量的类型的hello_typeof():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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 中有定义:
1 2 3 4 5 | #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,可以参考如下代码:
1 2 3 4 | {
zval temp;
ZVAL_LONG(&temp, 1234);
}
|
ログイン後にコピー
数组
数组作为运载其他变量的变量。内部实现上使用了众所周知的 HashTable .要创建将被返回PPHP的数组,最简单的方法:

做一个测试:
1 2 3 4 5 6 7 8 9 10 11 | 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()函数参数从四个改为了三个。
数组遍历
假设我们需要一个取代以下功能的拓展:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?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)
因此它的对应函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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) {
PHPWRITE(Z_STRVAL_P(val), Z_STRLEN_P(val));
php_printf( "\n" );
}ZEND_HASH_FOREACH_END();
}
|
ログイン後にコピー
因为这是新的遍历方法,而我看的还是php5的处理方式,调试出上面的代码花了不少功夫,总的来说,用宏的方式遍历大大减少了编码体积。哈希表是php中很重要的一个内容,有时间再好好研究一下。