php源码阅读(一):count 函数的实现

本篇文章主要介绍 php 内部是如何实现 count 这个函数的。

  1. count 函数介绍
  2. count 函数的源码实现
  3. PHP_FUNCTION
  4. 获取参数
  5. 函数逻辑
  6. 返回值
  7. IS_NULL
  8. IS_ARRAY
  9. IS_OBJECT
  10. 其它情况
  11. 测试
count 函数介绍

php 中的 count 函数用来计算数组中的单元数目或对象(实现了 countable 接口)中的属性个数。

php count 函数的使用方式:

count ( mixed $array_or_countable [, int $mode = COUNT_NORMAL ] ) : int
count 函数的源码实现

php源码版本:7.1.0
代码位置:/php-7.1.0/ext/standard/array.c 第 778 行。

/* {{{ proto int count(mixed var [, int mode])
   Count the number of elements in a variable (usually an array) */
PHP_FUNCTION(count)
{
	zval *array;
	zend_long mode = COUNT_NORMAL;
	zend_long cnt;
	zval *element;

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_ZVAL(array)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(mode)
	ZEND_PARSE_PARAMETERS_END();

	switch (Z_TYPE_P(array)) {
		case IS_NULL:
			RETURN_LONG(0);
			break;
		case IS_ARRAY:
			cnt = zend_array_count(Z_ARRVAL_P(array));
			if (mode == COUNT_RECURSIVE) {
				ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), element) {
					ZVAL_DEREF(element);
					cnt += php_count_recursive(element, COUNT_RECURSIVE);
				} ZEND_HASH_FOREACH_END();
			}
			RETURN_LONG(cnt);
			break;
		case IS_OBJECT: {
			zval retval;
			/* first, we check if the handler is defined */
			if (Z_OBJ_HT_P(array)->count_elements) {
				RETVAL_LONG(1);
				if (SUCCESS == Z_OBJ_HT(*array)->count_elements(array, &Z_LVAL_P(return_value))) {
					return;
				}
			}
			/* if not and the object implements Countable we call its count() method */
			if (instanceof_function(Z_OBJCE_P(array), spl_ce_Countable)) {
				zend_call_method_with_0_params(array, NULL, NULL, "count", &retval);
				if (Z_TYPE(retval) != IS_UNDEF) {
					RETVAL_LONG(zval_get_long(&retval));
					zval_ptr_dtor(&retval);
				}
				return;
			}
		}
		default:
			RETURN_LONG(1);
			break;
	}
}
/* }}} */
PHP_FUNCTION

通过 PHP_FUNCTION 宏编写函数。

PHP 为函数名加了 “zif_” 作为前缀。

展开后:

void zif_count(zend_execute_data *execute_data, zval * return_value){
	....
}
获取参数
	zval *array;
	zend_long mode = COUNT_NORMAL;
	zend_long cnt;
	zval *element;

	ZEND_PARSE_PARAMETERS_START(1, 2)  // 至少1个参数,最多2个
		Z_PARAM_ZVAL(array)  // 为 array 进行赋值,为 zval 的指针类型
		Z_PARAM_OPTIONAL   //表明下面的是可选参数
		Z_PARAM_LONG(mode)
	ZEND_PARSE_PARAMETERS_END();

ZEND_PARSE_PARAMETERS_START(min, max)
ZEND_PARSE_PARAMETERS_END();

这两个宏成对出现,用来获取函数的传参。

  • min 表示最少的传参个数,即必传参数;
  • max 表示最多可传参个数。
函数逻辑
	switch (Z_TYPE_P(array)) {
		case IS_NULL:
			RETURN_LONG(0);
			break;
		case IS_ARRAY:
			....
			break;
		case IS_OBJECT: {
			....
			break;
		}
		default:
			RETURN_LONG(1);
			break;
	}

array 是一个 zval 的结构体。

zend_types.h 中定义了类型标识:
在这里插入图片描述

Z_TYPE_P 宏用来从 zval 结构体指针中获取具体类型。

zend_types.h 文件

static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
	return pz->u1.v.type;
}
#define Z_TYPE(zval)				zval_get_type(&(zval))
#define Z_TYPE_P(zval_p)			Z_TYPE(*(zval_p))
函数返回值

函数的返回值地址在调用内部函数时作为参数传入,即上面的 return_value。如果函数有返回值,直接设置此指针即可。

虽然可以直接设置 return_value,但实际使用时并不建议这么做,
zend_API.h 中,PHP 提供了很多设置返回值的宏。

比如在上面用到的 RETURN_LONG(),返回整型。
在这里插入图片描述

IS_NULL
		case IS_NULL:
			RETURN_LONG(0);
			break;

直接返回 0

IS_ARRAY
		case IS_ARRAY:
			cnt = zend_array_count(Z_ARRVAL_P(array));
			if (mode == COUNT_RECURSIVE) {  // 查看是否是要递归调用
				ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), element) {
					ZVAL_DEREF(element);
					cnt += php_count_recursive(element, COUNT_RECURSIVE);
				} ZEND_HASH_FOREACH_END();
			}
			RETURN_LONG(cnt);  // 返回数值
			break;

先通过 zend_array_count 函数获取元素个数,再检查 mode,看看是否需要递归计算。

IS_OBJECT
		case IS_OBJECT: {
			zval retval;
			/* first, we check if the handler is defined */
			if (Z_OBJ_HT_P(array)->count_elements) {
				RETVAL_LONG(1);
				if (SUCCESS == Z_OBJ_HT(*array)->count_elements(array, &Z_LVAL_P(return_value))) {
					return;
				}
			}
			/* if not and the object implements Countable we call its count() method */
			if (instanceof_function(Z_OBJCE_P(array), spl_ce_Countable)) {
				zend_call_method_with_0_params(array, NULL, NULL, "count", &retval);
				if (Z_TYPE(retval) != IS_UNDEF) {
					RETVAL_LONG(zval_get_long(&retval));
					zval_ptr_dtor(&retval);
				}
				return;
			}
		}
其它情况
		default:
			RETURN_LONG(1);
			break;

返回值为 1

测试
<?php

$a = count(1);
echo 'count(1) = ' . $a . "\n";

// 空值
$a = count(null);
echo 'count(null) = ' . $a . "\n";

// 数组
$a = count([1,2,3]);
echo 'count([1,2,3]) = ' . $a . "\n";

// 多维数组
$a = count([1,2,3, [1, 2]], false);
echo 'count([1,2,3, [1, 2]], false) = ' . $a . "\n";
$a = count([1,2,3, [1, 2]], true);
echo 'count([1,2,3, [1, 2]], true) = ' . $a . "\n";

// 回调函数
$a = count(function(){ echo "1";});
echo 'count(function(){ echo "1";}) = ' . $a . "\n";

结果:

count(1) = 1
count(null) = 0
count([1,2,3]) = 3
count([1,2,3, [1, 2]], false) = 4
count([1,2,3, [1, 2]], true) = 6
count(function(){ echo "1";}) = 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值