php in_array的坑以及其实现

楚天乐 3697 0 条

先来看一段代码

<?php
$array = ["a", "b", "c"];
var_dump(in_array(0, $array));

这东西的输出是true, 虽然数组里并没有0

$array = ["a", "b", "c"];
var_dump(in_array(0, $array, true));

这东西的输出是false!!!

文档

http://php.net/manual/en/function.in-array.php
很简单没啥可说的

in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) : bool

源码

https://github.com/php/php-src/blob/master/ext/standard/array.c

/* {{{ proto bool in_array(mixed needle, array haystack [, bool strict])
   Checks if the given value exists in the array */
PHP_FUNCTION(in_array)
{
    php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */

/* {{{ proto mixed array_search(mixed needle, array haystack [, bool strict])
   Searches the array for a given value and returns the corresponding key if successful */
PHP_FUNCTION(array_search)
{
    php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */

没错,in_array和array_search只是最后一个参数不一样而已。in_array需要自己制定最后一个参数,array_search最后一个参数是true。

再来看看php_search_array如何实现的

/* void php_search_array(INTERNAL_FUNCTION_PARAMETERS, int behavior)
 * 0 = return boolean
 * 1 = return key
 */
static inline void php_search_array(INTERNAL_FUNCTION_PARAMETERS, int behavior) /* {{{ */
{
    zval *value,                /* value to check for */
         *array,                /* array to check in */
         *entry;                /* pointer to array entry */
    zend_ulong num_idx;
    zend_string *str_idx;
    zend_bool strict = 0;       /* strict comparison or not */

    ZEND_PARSE_PARAMETERS_START(2, 3)
        Z_PARAM_ZVAL(value)
        Z_PARAM_ARRAY(array)
        Z_PARAM_OPTIONAL
        Z_PARAM_BOOL(strict)
    ZEND_PARSE_PARAMETERS_END();

    if (strict) {
        ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
            ZVAL_DEREF(entry);
            if (fast_is_identical_function(value, entry)) {
                if (behavior == 0) {
                    RETURN_TRUE;
                } else {
                    if (str_idx) {
                        RETVAL_STR_COPY(str_idx);
                    } else {
                        RETVAL_LONG(num_idx);
                    }
                    return;
                }
            }
        } ZEND_HASH_FOREACH_END();
    } else {
        if (Z_TYPE_P(value) == IS_LONG) {
            ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
                if (fast_equal_check_long(value, entry)) {
                    if (behavior == 0) {
                        RETURN_TRUE;
                    } else {
                        if (str_idx) {
                            RETVAL_STR_COPY(str_idx);
                        } else {
                            RETVAL_LONG(num_idx);
                        }
                        return;
                    }
                }
            } ZEND_HASH_FOREACH_END();
        } else if (Z_TYPE_P(value) == IS_STRING) {
            ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
                if (fast_equal_check_string(value, entry)) {
                    if (behavior == 0) {
                        RETURN_TRUE;
                    } else {
                        if (str_idx) {
                            RETVAL_STR_COPY(str_idx);
                        } else {
                            RETVAL_LONG(num_idx);
                        }
                        return;
                    }
                }
            } ZEND_HASH_FOREACH_END();
        } else {
            ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
                if (fast_equal_check_function(value, entry)) {
                    if (behavior == 0) {
                        RETURN_TRUE;
                    } else {
                        if (str_idx) {
                            RETVAL_STR_COPY(str_idx);
                        } else {
                            RETVAL_LONG(num_idx);
                        }
                        return;
                    }
                }
            } ZEND_HASH_FOREACH_END();
        }
    }

    RETURN_FALSE;
}
/* }}} */

重点

if (Z_TYPE_P(value) == IS_LONG) {
    // ZEND_HASH_FOREACH_KEY_VAL(hashtable, 数值索引, 字符串索引, 值)
    ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_idx, str_idx, entry) {
        if (fast_equal_check_long(value, entry)) {
            if (behavior == 0) {
                RETURN_TRUE;
            } else {
                if (str_idx) {
                    RETVAL_STR_COPY(str_idx);
                } else {
                    RETVAL_LONG(num_idx);
                }
                return;
            }
        }
    } ZEND_HASH_FOREACH_END();
}

相关函数实现

/* 
op1: php输入的查找项,
op2: array遍历当前项
*/
static zend_always_inline int fast_equal_check_long(zval *op1, zval *op2)
{
    zval result;

    // 明显op2是字符串,不是long,跳过这里
    if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) {
        return Z_LVAL_P(op1) == Z_LVAL_P(op2);
    }

    // 重点!!
    // 参数1:执行结果
    // 参数2: php输入的查找项: 0
    // 参数3: php数组当前项: "a" 或者 "b" 或者 "c"
    compare_function(&result, op1, op2);
    return Z_LVAL(result) == 0;
}

来继续看源码
https://github.com/php/php-src/blob/db0079023421b8048f090ee04adb992e09132553/Zend/zend_operators.c

// 参数1:执行结果
// 参数2: php输入的查找项: 0
// 参数3: php数组当前项: "a" 或者 "b" 或者 "c"
ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) /* {{{ */
{
    int ret;
    int converted = 0;
    zval op1_copy, op2_copy;
    zval *op_free, tmp_free;

    while (1) {
        switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) {
            case TYPE_PAIR(IS_LONG, IS_LONG):
                ZVAL_LONG(result, Z_LVAL_P(op1)>Z_LVAL_P(op2)?1:(Z_LVAL_P(op1)<Z_LVAL_P(op2)?-1:0));
                return SUCCESS;

            case TYPE_PAIR(IS_DOUBLE, IS_LONG):
                Z_DVAL_P(result) = Z_DVAL_P(op1) - (double)Z_LVAL_P(op2);
                ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
                return SUCCESS;

            case TYPE_PAIR(IS_LONG, IS_DOUBLE):
                Z_DVAL_P(result) = (double)Z_LVAL_P(op1) - Z_DVAL_P(op2);
                ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
                return SUCCESS;

            case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE):
                if (Z_DVAL_P(op1) == Z_DVAL_P(op2)) {
                    ZVAL_LONG(result, 0);
                } else {
                    Z_DVAL_P(result) = Z_DVAL_P(op1) - Z_DVAL_P(op2);
                    ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
                }
                return SUCCESS;

            case TYPE_PAIR(IS_ARRAY, IS_ARRAY):
                ZVAL_LONG(result, zend_compare_arrays(op1, op2));
                return SUCCESS;

            case TYPE_PAIR(IS_NULL, IS_NULL):
            case TYPE_PAIR(IS_NULL, IS_FALSE):
            case TYPE_PAIR(IS_FALSE, IS_NULL):
            case TYPE_PAIR(IS_FALSE, IS_FALSE):
            case TYPE_PAIR(IS_TRUE, IS_TRUE):
                ZVAL_LONG(result, 0);
                return SUCCESS;

            case TYPE_PAIR(IS_NULL, IS_TRUE):
                ZVAL_LONG(result, -1);
                return SUCCESS;

            case TYPE_PAIR(IS_TRUE, IS_NULL):
                ZVAL_LONG(result, 1);
                return SUCCESS;

            case TYPE_PAIR(IS_STRING, IS_STRING):
                if (Z_STR_P(op1) == Z_STR_P(op2)) {
                    ZVAL_LONG(result, 0);
                    return SUCCESS;
                }
                ZVAL_LONG(result, zendi_smart_strcmp(Z_STR_P(op1), Z_STR_P(op2)));
                return SUCCESS;

            case TYPE_PAIR(IS_NULL, IS_STRING):
                ZVAL_LONG(result, Z_STRLEN_P(op2) == 0 ? 0 : -1);
                return SUCCESS;

            case TYPE_PAIR(IS_STRING, IS_NULL):
                ZVAL_LONG(result, Z_STRLEN_P(op1) == 0 ? 0 : 1);
                return SUCCESS;

            case TYPE_PAIR(IS_OBJECT, IS_NULL):
                ZVAL_LONG(result, 1);
                return SUCCESS;

            case TYPE_PAIR(IS_NULL, IS_OBJECT):
                ZVAL_LONG(result, -1);
                return SUCCESS;

            default: // 重点:IS_LONG, IS_STRING处理

                if (Z_ISREF_P(op1)) {           // 解引用
                    op1 = Z_REFVAL_P(op1);
                    continue;
                } else if (Z_ISREF_P(op2)) {    // 解引用
                    op2 = Z_REFVAL_P(op2);
                    continue;
                }

                if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJ_HANDLER_P(op1, compare)) {
                    ret = Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2);
                    if (UNEXPECTED(Z_TYPE_P(result) != IS_LONG)) {
                        convert_compare_result_to_long(result);
                    }
                    return ret;
                } else if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJ_HANDLER_P(op2, compare)) {
                    ret = Z_OBJ_HANDLER_P(op2, compare)(result, op1, op2);
                    if (UNEXPECTED(Z_TYPE_P(result) != IS_LONG)) {
                        convert_compare_result_to_long(result);
                    }
                    return ret;
                }

                if (Z_TYPE_P(op1) == IS_OBJECT && Z_TYPE_P(op2) == IS_OBJECT) {
                    if (Z_OBJ_P(op1) == Z_OBJ_P(op2)) {
                        /* object handles are identical, apparently this is the same object */
                        ZVAL_LONG(result, 0);
                        return SUCCESS;
                    }
                    if (Z_OBJ_HANDLER_P(op1, compare_objects) == Z_OBJ_HANDLER_P(op2, compare_objects)) {
                        ZVAL_LONG(result, Z_OBJ_HANDLER_P(op1, compare_objects)(op1, op2));
                        return SUCCESS;
                    }
                }
                if (Z_TYPE_P(op1) == IS_OBJECT) {
                    if (Z_OBJ_HT_P(op1)->get) {
                        zval rv;
                        op_free = Z_OBJ_HT_P(op1)->get(Z_OBJ_P(op1), &rv);
                        ret = compare_function(result, op_free, op2);
                        zend_free_obj_get_result(op_free);
                        return ret;
                    } else if (Z_TYPE_P(op2) != IS_OBJECT && Z_OBJ_HT_P(op1)->cast_object) {
                        ZVAL_UNDEF(&tmp_free);
                        if (Z_OBJ_HT_P(op1)->cast_object(Z_OBJ_P(op1), &tmp_free, ((Z_TYPE_P(op2) == IS_FALSE || Z_TYPE_P(op2) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(op2))) == FAILURE) {
                            ZVAL_LONG(result, 1);
                            zend_free_obj_get_result(&tmp_free);
                            return SUCCESS;
                        }
                        ret = compare_function(result, &tmp_free, op2);
                        zend_free_obj_get_result(&tmp_free);
                        return ret;
                    }
                }
                if (Z_TYPE_P(op2) == IS_OBJECT) {
                    if (Z_OBJ_HT_P(op2)->get) {
                        zval rv;
                        op_free = Z_OBJ_HT_P(op2)->get(Z_OBJ_P(op2), &rv);
                        ret = compare_function(result, op1, op_free);
                        zend_free_obj_get_result(op_free);
                        return ret;
                    } else if (Z_TYPE_P(op1) != IS_OBJECT && Z_OBJ_HT_P(op2)->cast_object) {
                        ZVAL_UNDEF(&tmp_free);
                        if (Z_OBJ_HT_P(op2)->cast_object(Z_OBJ_P(op2), &tmp_free, ((Z_TYPE_P(op1) == IS_FALSE || Z_TYPE_P(op1) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(op1))) == FAILURE) {
                            ZVAL_LONG(result, -1);
                            zend_free_obj_get_result(&tmp_free);
                            return SUCCESS;
                        }
                        ret = compare_function(result, op1, &tmp_free);
                        zend_free_obj_get_result(&tmp_free);
                        return ret;
                    } else if (Z_TYPE_P(op1) == IS_OBJECT) {
                        ZVAL_LONG(result, 1);
                        return SUCCESS;
                    }
                }

                /*
                继续追宏定义
                Zend/zend_types.h源码定义了
                    #define IS_UNDEF                    0
                    #define IS_NULL                     1
                    #define IS_FALSE                    2
                    #define IS_TRUE                     3
                    #define IS_LONG                     4           // op1
                    #define IS_DOUBLE                   5
                    #define IS_STRING                   6           // op2
                    #define IS_ARRAY                    7
                    #define IS_OBJECT                   8
                    #define IS_RESOURCE                 9
                    #define IS_REFERENCE                10
                */
                if (!converted) {
                    if (Z_TYPE_P(op1) < IS_TRUE) { // no
                        ZVAL_LONG(result, zval_is_true(op2) ? -1 : 0);
                        return SUCCESS;
                    } else if (Z_TYPE_P(op1) == IS_TRUE) {
                        ZVAL_LONG(result, zval_is_true(op2) ? 0 : 1);
                        return SUCCESS;
                    } else if (Z_TYPE_P(op2) < IS_TRUE) {
                        ZVAL_LONG(result, zval_is_true(op1) ? 1 : 0);
                        return SUCCESS;
                    } else if (Z_TYPE_P(op2) == IS_TRUE) {
                        ZVAL_LONG(result, zval_is_true(op1) ? 0 : -1);
                        return SUCCESS;
                    } else {

                        // 这里!!!!!
                        op1 = zendi_convert_scalar_to_number(op1, &op1_copy, result, 1);
                        op2 = zendi_convert_scalar_to_number(op2, &op2_copy, result, 1);
                        if (EG(exception)) {
                            if (result != op1) {
                                ZVAL_UNDEF(result);
                            }
                            return FAILURE;
                        }
                        converted = 1;
                    }
                } else if (Z_TYPE_P(op1)==IS_ARRAY) {
                    ZVAL_LONG(result, 1);
                    return SUCCESS;
                } else if (Z_TYPE_P(op2)==IS_ARRAY) {
                    ZVAL_LONG(result, -1);
                    return SUCCESS;
                } else {
                    ZEND_ASSERT(0);
                    zend_throw_error(NULL, "Unsupported operand types");
                    if (result != op1) {
                        ZVAL_UNDEF(result);
                    }
                    return FAILURE;
                }
        }
    }
}
/* }}} */

最后的最后
https://github.com/php/php-src/blob/db0079023421b8048f090ee04adb992e09132553/Zend/zend_operators.c

#define zendi_convert_scalar_to_number(op, holder, result, silent) \
    ((Z_TYPE_P(op) == IS_LONG || Z_TYPE_P(op) == IS_DOUBLE) ? (op) : \
        (((op) == result) ? (_convert_scalar_to_number((op), silent, 1), (op)) : \
            (silent ? _zendi_convert_scalar_to_number((op), holder) : \
                _zendi_convert_scalar_to_number_noisy((op), holder))))


发表我的评论
昵称 (必填)
邮箱 (必填)
网址
执行时间: 1710845975115.8 毫秒