本篇文章主要介绍 php 内部是如何实现 count 这个函数的。
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