From 264d60ac68729a86dfe630fb94780222e6cd80d4 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Mon, 31 Mar 2025 22:02:28 +0200 Subject: [PATCH] [RFC] Implement array_first() and array_last() --- ext/standard/array.c | 26 +++++++++ ext/standard/basic_functions.stub.php | 10 ++++ ext/standard/basic_functions_arginfo.h | 12 ++++- .../tests/array/array_first_last.phpt | 53 +++++++++++++++++++ .../tests/array/array_first_last_errors.phpt | 20 +++++++ 5 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/array/array_first_last.phpt create mode 100644 ext/standard/tests/array/array_first_last_errors.phpt diff --git a/ext/standard/array.c b/ext/standard/array.c index adb2a20d60388..2ab13f3aa8330 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -4511,6 +4511,32 @@ PHP_FUNCTION(array_key_last) } /* }}} */ +PHP_FUNCTION(array_first) +{ + HashTable *array; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(array) + ZEND_PARSE_PARAMETERS_END(); + + ZEND_HASH_FOREACH_VAL(array, zval *zv) { + RETURN_COPY_DEREF(zv); + } ZEND_HASH_FOREACH_END(); +} + +PHP_FUNCTION(array_last) +{ + HashTable *array; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(array) + ZEND_PARSE_PARAMETERS_END(); + + ZEND_HASH_REVERSE_FOREACH_VAL(array, zval *zv) { + RETURN_COPY_DEREF(zv); + } ZEND_HASH_FOREACH_END(); +} + /* {{{ Return just the values from the input array */ PHP_FUNCTION(array_values) { diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index e7f4ff8844714..4bec9d07348c0 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -1712,6 +1712,16 @@ function array_key_first(array $array): int|string|null {} */ function array_key_last(array $array): int|string|null {} +/** + * @compile-time-eval + */ +function array_first(array $array): mixed {} + +/** + * @compile-time-eval + */ +function array_last(array $array): mixed {} + /** * @return array * @compile-time-eval diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 3d92288643159..c39ddafc827ec 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 85677dc3476d25b7820fd3a26fe39f2e9378b6e7 */ + * Stub hash: f1fdd58097ccd7562c63aee8e9cc1ca88f5bdf31 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -235,6 +235,12 @@ ZEND_END_ARG_INFO() #define arginfo_array_key_last arginfo_array_key_first +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_first, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +#define arginfo_array_last arginfo_array_first + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array_values, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, array, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -2350,6 +2356,8 @@ ZEND_FUNCTION(array_replace_recursive); ZEND_FUNCTION(array_keys); ZEND_FUNCTION(array_key_first); ZEND_FUNCTION(array_key_last); +ZEND_FUNCTION(array_first); +ZEND_FUNCTION(array_last); ZEND_FUNCTION(array_values); ZEND_FUNCTION(array_count_values); ZEND_FUNCTION(array_column); @@ -2943,6 +2951,8 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY("array_keys", zif_array_keys, arginfo_array_keys, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("array_key_first", zif_array_key_first, arginfo_array_key_first, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("array_key_last", zif_array_key_last, arginfo_array_key_last, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("array_first", zif_array_first, arginfo_array_first, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) + ZEND_RAW_FENTRY("array_last", zif_array_last, arginfo_array_last, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("array_values", zif_array_values, arginfo_array_values, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("array_count_values", zif_array_count_values, arginfo_array_count_values, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("array_column", zif_array_column, arginfo_array_column, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) diff --git a/ext/standard/tests/array/array_first_last.phpt b/ext/standard/tests/array/array_first_last.phpt new file mode 100644 index 0000000000000..f8f7a6afc3624 --- /dev/null +++ b/ext/standard/tests/array/array_first_last.phpt @@ -0,0 +1,53 @@ +--TEST-- +array_first()/array_last() +--FILE-- + 1, 0 => 0, 3 => 3, 2 => 2], + [100 => []], + [new stdClass, false], + [true, new stdClass], +]; + +foreach ($test_cases as $test_case) { + // Output the checked values + echo "--- Testing: ", json_encode($test_case), " ---\n"; + echo "First: ", json_encode(array_first($test_case)), "\n"; + echo "Last: ", json_encode(array_last($test_case)), "\n"; + + // Sanity check consistency with array_key_first()/array_key_last() + if (array_first($test_case) !== $test_case[array_key_first($test_case)]) { + throw new Error("Key first and value first inconsistency"); + } + if (array_last($test_case) !== $test_case[array_key_last($test_case)]) { + throw new Error("Key last and value last inconsistency"); + } +} +?> +--EXPECT-- +--- Testing: ["single element"] --- +First: "single element" +Last: "single element" +--- Testing: ["hello world",1] --- +First: "hello world" +Last: 1 +--- Testing: [1,"hello world"] --- +First: 1 +Last: "hello world" +--- Testing: {"1":1,"0":0,"3":3,"2":2} --- +First: 1 +Last: 2 +--- Testing: {"100":[]} --- +First: [] +Last: [] +--- Testing: [{},false] --- +First: {} +Last: false +--- Testing: [true,{}] --- +First: true +Last: {} diff --git a/ext/standard/tests/array/array_first_last_errors.phpt b/ext/standard/tests/array/array_first_last_errors.phpt new file mode 100644 index 0000000000000..4225bf8289198 --- /dev/null +++ b/ext/standard/tests/array/array_first_last_errors.phpt @@ -0,0 +1,20 @@ +--TEST-- +array_first()/array_last() error cases +--FILE-- + +--EXPECT-- +NULL +NULL +NULL +NULL