エクステンションでforeachを簡単に書くには

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.