php源码之array_key_exists

本文详细解析了PHP函数array_key_exists的源码实现过程,包括参数解析、字符串和整数类型的键值检测流程,以及 zend_hash_index_exists 和 zend_hash_exists_ind 函数的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先定一个flag,以后争取每天理解一个php函数源码,熟悉php内核。

函数array_key_exists等数组相关的函数都定义在源码ext/standard/array.c文件中。

array_key_exists函数的作用是检测给定数组array中是否有键为key的值。源码如下:

PHP_FUNCTION(array_key_exists)
{
	zval *key;					/* key to check for */
	HashTable *array;			/* array to check in */

	ZEND_PARSE_PARAMETERS_START(2, 2)
		Z_PARAM_ZVAL(key)
		Z_PARAM_ARRAY_OR_OBJECT_HT(array)
	ZEND_PARSE_PARAMETERS_END();

	switch (Z_TYPE_P(key)) {
		case IS_STRING:
			if (zend_symtable_exists_ind(array, Z_STR_P(key))) {
				RETURN_TRUE;
			}
			RETURN_FALSE;
		case IS_LONG:
			if (zend_hash_index_exists(array, Z_LVAL_P(key))) {
				RETURN_TRUE;
			}
			RETURN_FALSE;
		case IS_NULL:
			if (zend_hash_exists_ind(array, ZSTR_EMPTY_ALLOC())) {
				RETURN_TRUE;
			}
			RETURN_FALSE;

		default:
			php_error_docref(NULL, E_WARNING, "The first argument should be either a string or an integer");
			RETURN_FALSE;
	}
}

如上,php7中采用zend_parse_parameters_start来代替zend_parse_parameters函数获取参数值,具体实现我还没有弄明白,后面补上,反正作用和zend_parse_parameters一样,之前也有写关于它的博客。

指针变量key指向一个zval结构体,通过判断这个key是字符串类型还是整型分别处理。

如果key指向的zval变量是string类型:

调用函数zend_symtabl_exists_ind函数,传入的第二个参数Z_STR_P(key)展开就是*(key).value.str,不熟悉zval结构的可以自行google一下。函数zend_symtabl_exists_ind定义在zend_hash.h中,代码如下:

static zend_always_inline int zend_symtable_exists_ind(HashTable *ht, zend_string *key)
{
	zend_ulong idx;

	if (ZEND_HANDLE_NUMERIC(key, idx)) {
		return zend_hash_index_exists(ht, idx);
	} else {
		return zend_hash_exists_ind(ht, key);
	}
}

这里面再次先通过函数zend_handle_numeric对key做判断,看这个字符串是不是数字类型的,比如说‘1234’,‘23’这种字符串。判断代码如下:

static zend_always_inline int _zend_handle_numeric_str(const char *key, size_t length, zend_ulong *idx)
{
	const char *tmp = key;

	if (*tmp > '9') {
		return 0;
	} else if (*tmp < '0') {
		if (*tmp != '-') {
			return 0;
		}
		tmp++;
		if (*tmp > '9' || *tmp < '0') {
			return 0;
		}
	}
	return _zend_handle_numeric_str_ex(key, length, idx);
}

这里比较*tmp > '9'其实是比较的ascii码。如果key确实是字符串数字,那么返回函数zend_handle_numeric_str_ex的结果:

ZEND_API int ZEND_FASTCALL _zend_handle_numeric_str_ex(const char *key, size_t length, zend_ulong *idx)
{
	register const char *tmp = key;

	const char *end = key + length;

	if (*tmp == '-') {
		tmp++;
	}

	if ((*tmp == '0' && length > 1) /* numbers with leading zeros */
	 || (end - tmp > MAX_LENGTH_OF_LONG - 1) /* number too long */
	 || (SIZEOF_ZEND_LONG == 4 &&
	     end - tmp == MAX_LENGTH_OF_LONG - 1 &&
	     *tmp > '2')) { /* overflow */
		return 0;
	}
	*idx = (*tmp - '0');
	while (1) {
		++tmp;
		if (tmp == end) {
			if (*key == '-') {
				if (*idx-1 > ZEND_LONG_MAX) { /* overflow */
					return 0;
				}
				*idx = 0 - *idx;
			} else if (*idx > ZEND_LONG_MAX) { /* overflow */
				return 0;
			}
			return 1;
		}
		if (*tmp <= '9' && *tmp >= '0') {
			*idx = (*idx * 10) + (*tmp - '0');
		} else {
			return 0;
		}
	}
}

这个函数的作用就是将字符串数字,通过逐个的获取然后计算出它的整型值,如‘23’=》2*10+3*1(当然代码中没这么简单,我这里是打个比方说这个函数的作用),最后赋值给idx变量。

然后到了最关键的一步,检测idx这个key是否存在于数组ht中了。zend_hash_index_exists(ht,idx);

ZEND_API zend_bool ZEND_FASTCALL zend_hash_index_exists(const HashTable *ht, zend_ulong h)
{
	Bucket *p;

	IS_CONSISTENT(ht);

	if (ht->u.flags & HASH_FLAG_PACKED) {
		if (h < ht->nNumUsed) {
			if (Z_TYPE(ht->arData[h].val) != IS_UNDEF) {
				return 1;
			}
		}
		return 0;
	}

	p = zend_hash_index_find_bucket(ht, h);
	return p ? 1 : 0;
}

这里有一个宏HASH_FLAG_PACKED,为真就代表当前数组的key都是系统生成的,也就是说是按从0到1,2,3等等按序排列的,所以判读键为key的是否存在,直接检查arData数组中第idx个元素是否有定义就行了,这里不涉及什么hash查找,冲突解决等一系列问题。但如果HASH_FLAG_PACKED为假,那么肯定就需要先计算idx的hash值,找到key为idx的数据应该在arData的第几位才行。这就要通过函数zend_hash_index_find_bucket了。

static zend_always_inline Bucket *zend_hash_index_find_bucket(const HashTable *ht, zend_ulong h)
{
	uint32_t nIndex;
	uint32_t idx;
	Bucket *p, *arData;

	arData = ht->arData;
	nIndex = h | ht->nTableMask;
	idx = HT_HASH_EX(arData, nIndex);
	while (idx != HT_INVALID_IDX) {
		ZEND_ASSERT(idx < HT_IDX_TO_HASH(ht->nTableSize));
		p = HT_HASH_TO_BUCKET_EX(arData, idx);
		if (p->h == h && !p->key) {
			return p;
		}
		idx = Z_NEXT(p->val);
	}
	return NULL;
}

这里需要明白一点,数字的哈希值就等于他本身,所以才有不计算h的哈希值,就执行h | ht->nTableMask。

然后处理一下冲突,最后得出key为idx的数据是否存在于数组中。

如果idx确确实实是字符串,那么思路更简单一点,最后通过zen_hash_find_bucket来判断是否存在,与上面zend_hash_index_find_bucket不同的是,函数中要先计算字符串key的哈希值,然后再执行h | ht->nTableMask。

因为篇幅有限,就写到这里了,如果有不正确的地方烦请指正!












### PHP购物系统源码下载与实现 寻找PHP实现的购物系统源码可以通过多种途径获取。以下是一些常见的方法和注意事项: #### 1. 开源平台获取 开源社区是获取PHP购物系统源码的主要渠道之一。GitHub、GitLab等平台上存在大量开源的PHP购物系统项目[^1]。这些项目通常包含完整的功能模块,例如用户管理、商品展示、购物车、订单处理等。 #### 2. 示例代码片段 以下是一个简单的PHP购物车功能实现示例,用于说明如何将商品添加到购物车中: ```php <?php session_start(); // 假设商品ID为 product_id if (isset($_POST['product_id'])) { $product_id = $_POST['product_id']; // 检查购物车是否已存在该商品 if (!isset($_SESSION['cart'])) { $_SESSION['cart'] = []; } // 添加商品到购物车 if (array_key_exists($product_id, $_SESSION['cart'])) { $_SESSION['cart'][$product_id]++; } else { $_SESSION['cart'][$product_id] = 1; } } // 显示购物车内容 if (isset($_SESSION['cart']) && !empty($_SESSION['cart'])) { echo "购物车中的商品:"; foreach ($_SESSION['cart'] as $id => $quantity) { echo "商品ID: $id, 数量: $quantity<br>"; } } else { echo "购物车为空。"; } ?> ``` 此代码展示了如何使用PHP会话(`$_SESSION`)来管理购物车中的商品[^2]。 #### 3. 注意事项 在下载和使用PHP购物系统源码时,请注意以下几点: - **安全性**:确保源码经过安全测试,避免SQL注入、XSS攻击等问题[^3]。 - **兼容性**:检查源码是否兼容当前使用的PHP版本和Web服务器环境。 - **许可证**:确认源码的开源许可证是否允许商业用途或二次分发。 #### 4. 下载资源推荐 可以访问以下网站查找PHP购物系统的源码: - [GitHub](https://github.com/) - [SourceForge](https://sourceforge.net/) - [CodeCanyon](https://codecanyon.net/) 这些平台提供了丰富的PHP购物系统源码,部分可能需要付费购买[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值