From 3c89753b49828c4a3c17fba45beb60655a5f0785 Mon Sep 17 00:00:00 2001 From: b84330808 Date: Wed, 4 Nov 2026 16:13:48 +0000 Subject: [PATCH] Fix implode($array, null) throwing on the frameless call path PHP_FUNCTION(implode) accepts an array as the first argument when the second one is null, which is the same as implode($array). The frameless variant used for direct two-argument calls parsed argument 1 as a string unconditionally, so the same call threw depending on how it was made: implode(['a', 'b'], null); // TypeError call_user_func('implode', ['a', 'b'], null); // "ab" It also blamed argument 1 for implode($array, 'x'), while the regular function reports that argument 2 must be of type ?array. Handle an array first argument the way the regular function does. --- ext/standard/string.c | 11 +++++ .../strings/implode_array_null_frameless.phpt | 41 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 ext/standard/tests/strings/implode_array_null_frameless.phpt diff --git a/ext/standard/string.c b/ext/standard/string.c index 8b0f8ec572bb..7895aaabf308 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -1152,6 +1152,17 @@ ZEND_FRAMELESS_FUNCTION(implode, 2) zend_string *str; zval *pieces; + /* Like PHP_FUNCTION(implode): implode($array, null) is the same as implode($array) */ + if (Z_TYPE_P(arg1) == IS_ARRAY) { + Z_FLF_PARAM_ARRAY_OR_NULL(2, pieces); + if (pieces) { + zend_argument_type_error(1, "must be of type string, array given"); + } else { + php_implode(ZSTR_EMPTY_ALLOC(), Z_ARR_P(arg1), return_value); + } + return; + } + Z_FLF_PARAM_STR(1, str, str_tmp); Z_FLF_PARAM_ARRAY_OR_NULL(2, pieces); diff --git a/ext/standard/tests/strings/implode_array_null_frameless.phpt b/ext/standard/tests/strings/implode_array_null_frameless.phpt new file mode 100644 index 000000000000..bc53ee0beff2 --- /dev/null +++ b/ext/standard/tests/strings/implode_array_null_frameless.phpt @@ -0,0 +1,41 @@ +--TEST-- +implode() with an array first argument behaves the same in direct and dynamic calls +--FILE-- + $direct, 'dynamic' => $dynamic] as $kind => $fn) { + try { + $result = var_export($fn(), true); + } catch (TypeError $e) { + $result = $e->getMessage(); + } + echo "$label ($kind): $result\n"; + } +} + +test('array, null', fn() => implode($array, $null), fn() => call_user_func('implode', $array, $null)); +test('array, null literal', fn() => implode($array, null), fn() => call_user_func('implode', $array, null)); +test('empty array, null', fn() => implode([], $null), fn() => call_user_func('implode', [], $null)); +test('array, string', fn() => implode($array, ','), fn() => call_user_func('implode', $array, ',')); +test('array, array', fn() => implode($array, $array), fn() => call_user_func('implode', $array, $array)); +test('string, array', fn() => implode(',', $array), fn() => call_user_func('implode', ',', $array)); +test('join', fn() => join($array, $null), fn() => call_user_func('join', $array, $null)); +?> +--EXPECT-- +array, null (direct): 'ab' +array, null (dynamic): 'ab' +array, null literal (direct): 'ab' +array, null literal (dynamic): 'ab' +empty array, null (direct): '' +empty array, null (dynamic): '' +array, string (direct): implode(): Argument #2 ($array) must be of type ?array, string given +array, string (dynamic): implode(): Argument #2 ($array) must be of type ?array, string given +array, array (direct): implode(): Argument #1 ($separator) must be of type string, array given +array, array (dynamic): implode(): Argument #1 ($separator) must be of type string, array given +string, array (direct): 'a,b' +string, array (dynamic): 'a,b' +join (direct): 'ab' +join (dynamic): 'ab'