diff --git a/ext/opcache/Optimizer/zend_func_info.c b/ext/opcache/Optimizer/zend_func_info.c index a684a16e73e9a..b4db3321577ff 100644 --- a/ext/opcache/Optimizer/zend_func_info.c +++ b/ext/opcache/Optimizer/zend_func_info.c @@ -825,6 +825,7 @@ static const func_info_t func_infos[] = { F1("array_pad", MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), F1("array_flip", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_STRING), F1("array_change_key_case", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), + F1("array_change_keys", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), F1("array_rand", UNKNOWN_INFO), F1("array_unique", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), F1("array_intersect", MAY_BE_NULL | MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_OF_ANY), diff --git a/ext/standard/array.c b/ext/standard/array.c index 7ee505601ea85..a803428da4d8e 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -3776,6 +3776,65 @@ PHP_FUNCTION(array_change_key_case) } /* }}} */ +/** {{{ proto array array_change_keys(array input, mixed callback) + Retuns an array with all keys modified by a callback */ +PHP_FUNCTION(array_change_keys) +{ + zval *array, *value, *params; + zval result; + zend_fcall_info fci; + zend_fcall_info_cache fci_cache = empty_fcall_info_cache; + zend_ulong num_key; + zend_string *str_key; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "af", &array, &fci, &fci_cache) == FAILURE) { + return; + } + + array_init_size(return_value, zend_hash_num_elements(Z_ARRVAL_P(array))); + params = (zval *)safe_emalloc(2, sizeof(zval), 0); + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(array), num_key, str_key, value) { + fci.retval = &result; + fci.param_count = 2; + fci.params = params; + fci.no_separation = 0; + + if (str_key) { + ZVAL_STR_COPY(¶ms[0], str_key); + } else { + ZVAL_LONG(¶ms[0], num_key); + } + ZVAL_COPY(¶ms[1], value); + + if (zend_call_function(&fci, &fci_cache) != SUCCESS || Z_TYPE(result) == IS_UNDEF) { + zval_dtor(return_value); + zval_dtor(¶ms[0]); + zval_dtor(¶ms[1]); + efree(params); + RETURN_NULL(); + } + + zval_dtor(¶ms[0]); + zval_dtor(¶ms[1]); + + if (Z_TYPE(result) == IS_STRING) { + value = zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR(result), value); + zval_add_ref(value); + } else if (Z_TYPE(result) == IS_LONG) { + value = zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL(result), value); + zval_add_ref(value); + } else { + php_error_docref(NULL, E_WARNING, "New key should be either a string or an integer"); + } + + zval_ptr_dtor(&result); + } ZEND_HASH_FOREACH_END(); + + efree(params); +} +/* }}} */ + struct bucketindex { Bucket b; unsigned int i; diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index f887903f268e9..02c8807290ddc 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -452,6 +452,11 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_array_change_key_case, 0, 0, 1) ZEND_ARG_INFO(0, case) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_array_change_keys, 0, 0, 1) + ZEND_ARG_INFO(0, input) /* ARRAY_INFO(0, arg, 0) */ + ZEND_ARG_INFO(0, callback) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_array_unique, 0, 0, 1) ZEND_ARG_INFO(0, arg) /* ARRAY_INFO(0, arg, 0) */ ZEND_ARG_INFO(0, flags) @@ -3312,6 +3317,7 @@ const zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(array_pad, arginfo_array_pad) PHP_FE(array_flip, arginfo_array_flip) PHP_FE(array_change_key_case, arginfo_array_change_key_case) + PHP_FE(array_change_keys, arginfo_array_change_keys) PHP_FE(array_rand, arginfo_array_rand) PHP_FE(array_unique, arginfo_array_unique) PHP_FE(array_intersect, arginfo_array_intersect) diff --git a/ext/standard/php_array.h b/ext/standard/php_array.h index 302f0ddc6731c..cd01a09322803 100644 --- a/ext/standard/php_array.h +++ b/ext/standard/php_array.h @@ -77,6 +77,7 @@ PHP_FUNCTION(array_reduce); PHP_FUNCTION(array_pad); PHP_FUNCTION(array_flip); PHP_FUNCTION(array_change_key_case); +PHP_FUNCTION(array_change_keys); PHP_FUNCTION(array_rand); PHP_FUNCTION(array_unique); PHP_FUNCTION(array_intersect); diff --git a/ext/standard/tests/array/array_change_keys.phpt b/ext/standard/tests/array/array_change_keys.phpt new file mode 100644 index 0000000000000..16d4d9e0de14a --- /dev/null +++ b/ext/standard/tests/array/array_change_keys.phpt @@ -0,0 +1,132 @@ +--TEST-- +Test array_change_keys() function +--FILE-- + 42, 'bar' => 3]; +$arrayWithMixedKeys = ['foo' => 42, 'bar']; + +echo "---Testing an empty array---\n"; +var_dump(array_change_keys([], function(){})); + +echo "---Numeric keys to numeric keys---\n"; +var_dump(array_change_keys($arrayWithIntKeys, function($k, $v) { + return $k * 10; +})); + +echo "---Numeric keys to string keys---\n"; +var_dump(array_change_keys($arrayWithIntKeys, function($k, $v) { + return 'test:' . $k; +})); + +echo "---String keys to numeric keys---\n"; +$i = 0; +var_dump(array_change_keys($arrayWithIntKeys, function($k, $v) use (&$i) { + return $i++; +})); + +echo "---String keys to string keys---\n"; +var_dump(array_change_keys($arrayWithStringKeys, function($k, $v) { + return 'test:' . $k; +})); + +echo "---Mixed keys to numeric keys---\n"; +$i = 10; +var_dump(array_change_keys($arrayWithMixedKeys, function($k, $v) use (&$i) { + return $i++; +})); + +echo "---Mixed keys to string keys---\n"; +var_dump(array_change_keys($arrayWithMixedKeys, function($k, $v) { + return 'test:' . $k; +})); + +echo "---Mixed keys to mixed keys---\n"; +var_dump(array_change_keys($arrayWithMixedKeys, function($k, $v) { + return is_int($k) ? 'baz' : 3; +})); + +echo "---Test using a string as the callable---\n"; +// We can't reference 'md5' directly because it would get a truthy value in its second param, +// This resulting in some raw bytes instead of a nice human-readable string. +function use_md5_hash_as_key($k, $v) { + return md5($k); +} +var_dump(array_change_keys($arrayWithMixedKeys, 'use_md5_hash_as_key')); + +echo "---Test using the [\$obj, 'method'] callback syntax\n"; +$keyMaker = new class { + function getNewKey($k, $v) { + return md5($v); + } +}; +var_dump(array_change_keys($arrayWithMixedKeys, [$keyMaker, 'getNewKey'])); + +?> +--EXPECTF-- +---Testing an empty array--- +array(0) { +} +---Numeric keys to numeric keys--- +array(2) { + [0]=> + string(3) "foo" + [10]=> + string(3) "bar" +} +---Numeric keys to string keys--- +array(2) { + ["test:0"]=> + string(3) "foo" + ["test:1"]=> + string(3) "bar" +} +---String keys to numeric keys--- +array(2) { + [0]=> + string(3) "foo" + [1]=> + string(3) "bar" +} +---String keys to string keys--- +array(2) { + ["test:foo"]=> + int(42) + ["test:bar"]=> + int(3) +} +---Mixed keys to numeric keys--- +array(2) { + [10]=> + int(42) + [11]=> + string(3) "bar" +} +---Mixed keys to string keys--- +array(2) { + ["test:foo"]=> + int(42) + ["test:0"]=> + string(3) "bar" +} +---Mixed keys to mixed keys--- +array(2) { + [3]=> + int(42) + ["baz"]=> + string(3) "bar" +} +---Test using a string as the callable--- +array(2) { + ["acbd18db4cc2f85cedef654fccc4a4d8"]=> + int(42) + ["cfcd208495d565ef66e7dff9f98764da"]=> + string(3) "bar" +} +---Test using the [$obj, 'method'] callback syntax +array(2) { + ["a1d0c6e83f027327d8461063f4ac58a6"]=> + int(42) + ["37b51d194a7513e45b56f6524f2d51f2"]=> + string(3) "bar" +} \ No newline at end of file diff --git a/ext/standard/tests/array/array_change_keys_variation1.phpt b/ext/standard/tests/array/array_change_keys_variation1.phpt new file mode 100644 index 0000000000000..c190173c7ee7b --- /dev/null +++ b/ext/standard/tests/array/array_change_keys_variation1.phpt @@ -0,0 +1,27 @@ +--TEST-- +Test array_change_keys() function with duplicate keys +--FILE-- + +--EXPECTF-- +array(1) { + ["foo"]=> + int(3) +} +array(1) { + [99]=> + int(3) +} +array(2) { + [99]=> + int(9) + ["foo"]=> + int(10) +} \ No newline at end of file diff --git a/ext/standard/tests/array/array_change_keys_variation2.phpt b/ext/standard/tests/array/array_change_keys_variation2.phpt new file mode 100644 index 0000000000000..2a755b6a8d2c5 --- /dev/null +++ b/ext/standard/tests/array/array_change_keys_variation2.phpt @@ -0,0 +1,147 @@ +--TEST-- +Test array_change_keys() function with invalid callback results +--FILE-- +str = $str; + } + + public function __toString() + { + return $this->str; + } +} + +var_dump(array_change_keys(range(1, 5), function($k, $v) { + return new FancyString($v); +})); + +echo "--- Test with invalid callable references ---\n"; +var_dump(array_change_keys($oldArray, null)); +var_dump(array_change_keys($oldArray, 'some_function_that_doesnt_exist')); +var_dump(array_change_keys($oldArray, ['foo', 'bar'])); + +?> +--EXPECTF-- +--- Test with function that doesn't return --- + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d +array(0) { +} +--- Test with function that always returns null --- + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d +array(0) { +} +--- Test with function that doesn't always return --- + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d +array(2) { + [1]=> + int(2) + [3]=> + int(4) +} +--- Test with function that returns streams --- + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d +array(0) { +} +--- Test with function that returns objects that don't implement __toString() --- + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d +array(0) { +} +--- Test with function that returns objects that do implement __toString(), which is unsupported --- + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d + +Warning: array_change_keys(): New key should be either a string or an integer in %s on line %d +array(0) { +} +--- Test with invalid callable references --- + +Warning: array_change_keys() expects parameter 2 to be a valid callback, no array or string given in %s on line %d +NULL + +Warning: array_change_keys() expects parameter 2 to be a valid callback, function 'some_function_that_doesnt_exist' not found or invalid function name in %s on line %d +NULL + +Warning: array_change_keys() expects parameter 2 to be a valid callback, class 'foo' not found in %s on line %d +NULL \ No newline at end of file