PHPCon2007でも少し話したことですが、PHPエクステンションを書くとき、PHPのforeachに相当することをしようと思うとちょっと面倒です。
そこで、IoのLIST_FOREACH/PHASH_FOREACHを真似してforeachを簡単に書けるマクロを作ってみました。
aにハッシュテーブルのポインタ、kにキー(zval *)が代入される変数名、vに値(zval *)が代入される変数名、codeに処理内容を記述します。
#define HASH_FOREACH(a, k, v, code) \ { \ zval **_entry = NULL; \ \ zend_hash_internal_pointer_reset(a); \ \ while (zend_hash_get_current_data(a, (void **)&_entry) == SUCCESS) { \ char *_kstr = NULL; \ uint _klen = 0U; \ ulong _knum = 0UL; \ zval _key, *k, *v = *_entry; \ \ INIT_ZVAL(_key); \ k = &_key; \ \ switch (zend_hash_get_current_key_ex(a, &_kstr, &_klen, &_knum, 0, NULL)) { \ case HASH_KEY_IS_STRING: \ ZVAL_STRINGL(k, _kstr, _klen - 1, 0); \ break; \ case HASH_KEY_IS_LONG: \ ZVAL_LONG(k, (long)_knum); \ break; \ default: \ ZVAL_NULL(k); \ } \ \ if (Z_TYPE(_key) != IS_NULL) { \ code; \ } \ \ zend_hash_move_forward(a); \ } \ }
実際にはこんな風にして使います。
<?xml version="1.0"?> <extension name="sample" version="1.0.0"> <code position="top"><![CDATA[ // 上記マクロのコードをここにコピペ ]]></code> <function name="sample"> <proto>void sample(array a)</proto> <code><![CDATA[ HASH_FOREACH(a_hash, key, value, if (Z_TYPE_P(key) == IS_STRING) { php_printf("key is string(%d) \"%s\".\n", Z_STRLEN_P(key), Z_STRVAL_P(key)); } else { php_printf("key is integer %ld.\n", Z_LVAL_P(key)); } php_printf("value is %s.\n\n", zend_zval_type_name(value)); ); ]]></code> </function> </extension>
CodeGen_PECLを使ってこのXMLからエクステンションを作成してテスト。
pecl-gen sample.xml cd sample phpize ./configure make sudo make install php -d extension=sample.so -r 'sample(array(1, "2" => "3", "four" => "five", -1 => array()));'
出力:
key is integer 0. value is integer. key is integer 2. value is string. key is string(4) "four". value is string. key is integer -1. value is array.