わかりません＞＜教えてください＞＜

シャミロスでつらい。

2期が来るまで現実を忘れるために[key](https://www.php.net/manual/ja/function.key.php)関数のソースを掘ってみようと思ったのだ。

これがkey関数の[ソース](https://github.com/php/php-src/blob/php-7.3.9/ext/standard/array.c#L1253)です。

```c
PHP_FUNCTION(key)
{
	HashTable *array;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ARRAY_OR_OBJECT_HT(array)
	ZEND_PARSE_PARAMETERS_END();

	zend_hash_get_current_key_zval(array, return_value);
}
```

なんだ、凄く短いですね。
これなら楽勝じゃないですか。

しかし幾つかマクロが使われているので、ちょっと中身を調べてみましょう。

ZEND_PARSE_PARAMETERS_STARTの[実装](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_API.h#L755)。

```c
#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args) \
	ZEND_PARSE_PARAMETERS_START_EX(0, min_num_args, max_num_args)
```

1行のたらい回し。
ZEND_PARSE_PARAMETERS_START_EXの[実装](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_API.h#L718)はこんな。

```c
#define ZEND_PARSE_PARAMETERS_START_EX(flags, min_num_args, max_num_args) do { \
		const int _flags = (flags); \
		int _min_num_args = (min_num_args); \
		int _max_num_args = (max_num_args); \
		int _num_args = EX_NUM_ARGS(); \
		int _i; \
		zval *_real_arg, *_arg = NULL; \
		zend_expected_type _expected_type = Z_EXPECTED_LONG; \
		char *_error = NULL; \
		zend_bool _dummy; \
		zend_bool _optional = 0; \
		int error_code = ZPP_ERROR_OK; \
		((void)_i); \
		((void)_real_arg); \
		((void)_arg); \
		((void)_expected_type); \
		((void)_error); \
		((void)_dummy); \
		((void)_optional); \
		\
		do { \
			if (UNEXPECTED(_num_args < _min_num_args) || \
			    (UNEXPECTED(_num_args > _max_num_args) && \
			     EXPECTED(_max_num_args >= 0))) { \
				if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \
					if (_flags & ZEND_PARSE_PARAMS_THROW) { \
						zend_wrong_parameters_count_exception(_min_num_args, _max_num_args); \
					} else { \
						zend_wrong_parameters_count_error(_min_num_args, _max_num_args); \
					} \
				} \
				error_code = ZPP_ERROR_FAILURE; \
				break; \
			} \
			_i = 0; \
			_real_arg = ZEND_CALL_ARG(execute_data, 0);
```

なんだこれ。

尤も、このあたりは定型文なので、毎回わざわざ掘り返す必要はありません。
ZEND_PARSE_PARAMETERS_STARTとZEND_PARSE_PARAMETERS_ENDで挟まれたところに[Z_PARAM_XXX](https://wiki.php.net/rfc/fast_zpp)と書いておけば引数を受け取れるというイメージでよいです。

```c
	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_ARRAY_OR_OBJECT_HT(array)
	ZEND_PARSE_PARAMETERS_END();
```

key関数は配列もしくはオブジェクト型の引数をひとつ受け取ります。
はい、[マニュアル](https://www.php.net/manual/ja/function.key.php)には引数は配列って書いてあるのですが、実際は何の問題もなく[オブジェクトを渡せます](https://3v4l.org/BhY0Q)。
まあ今回はそこが焦点ではないのでどうでもいいとして、要するにこの3行は、単に引数を受け取る処理です。

ということでこの関数は、実質`zend_hash_get_current_key_zval(array, return_value);`の1行だけということになります。

zend_hash_get_current_key_zvalは小文字だからわかりにくいですが、これも実はマクロです。
zend_hash_get_current_key_zvalの[実装](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_hash.h#L229)。

```c
#define zend_hash_get_current_key_zval(ht, key) \
	zend_hash_get_current_key_zval_ex(ht, key, &(ht)->nInternalPointer)
```

PHPには1行だけのマクロが大量にあるのですが、これは何の意味があるのだろう。
`zend_hash_get_current_key_zval`はkey関数しか使ってないんだけど、これ直接`zend_hash_get_current_key_zval_ex`を呼ぶようにしたらいけないんですかね？(わかってない)

zend_hash_get_current_key_zval_exでようやく[処理部分に到達](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_hash.c#L2289)しました。

```c
ZEND_API void ZEND_FASTCALL zend_hash_get_current_key_zval_ex(const HashTable *ht, zval *key, HashPosition *pos)
{
	uint32_t idx;
	Bucket *p;

	IS_CONSISTENT(ht);
	idx = _zend_hash_get_valid_pos(ht, *pos);
	if (idx >= ht->nNumUsed) {
		ZVAL_NULL(key);
	} else {
		p = ht->arData + idx;
		if (p->key) {
			ZVAL_STR_COPY(key, p->key);
		} else {
			ZVAL_LONG(key, p->h);
		}
	}
}
```

ここまで来れば詳細を読まなくても、`_zend_hash_get_valid_pos`で現在地を取ってきて、範囲外ならNULLを返す、キーが文字列ならstring値を、数値ならlong値を返す、ってだいたいわかりますね。

しかし脈絡なく出てくるIS_CONSISTENTってなんだ？

これはZEND_DEBUGであれば[`_zend_is_inconsistent(a, __FILE__, __LINE__);`](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_hash.c#L69)で、そうでなければ[空っぽ](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_hash.c#L74)になるようです。
ZEND_DEBUGはコンパイルオプション[`--enable-debug`](https://www.php.net/manual/ja/configure.about.php#configure.options.misc)を指定してコンパイルしたときに有効になる値です。
つまり完全にデバッグ用途みたいなので今回はスルーしましょう。


さて見て見ぬふりをしましたが、[ZEND_FASTCALL](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_portability.h#L247)やら[ZVAL_STR_COPY](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_types.h#L779)やら[ZVAL_LONG](https://github.com/php/php-src/blob/php-7.3.9/Zend/zend_types.h#L743)やらも全部マクロです。
その中でもさらにマクロが使われていてさらにその中でも…の無限連鎖で、どこまで追っても終わりません。

関数ひとつ見るだけで力尽きました。
こんなのをさくさく[書いてる人たち](https://github.com/php/php-src/graphs/contributors)はいったいどうやってPHPを理解してるのですか？
わかりません＞＜教えてください＞＜