Commit 7cc143235d2 for php.net

commit 7cc143235d2be7b989348d37f465860835833a25
Author: Gina Peter Banyard <girgias@php.net>
Date:   Wed Aug 5 16:23:31 2026 +0100

    mbstring: deprecate passing objects as vars to mb_convert_variables()

    RFC: https://wiki.php.net/rfc/deprecations_php_8_6#passing_objects_for_vars_parameter_of_mb_convert_variables

diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c
index b475b83e02f..4059d8d35fd 100644
--- a/ext/mbstring/mbstring.c
+++ b/ext/mbstring/mbstring.c
@@ -3820,7 +3820,7 @@ static bool mb_recursive_find_strings(zval *var, const unsigned char **val_list,
 	return false;
 }

-static bool mb_recursive_convert_variable(zval *var, const mbfl_encoding* from_encoding, const mbfl_encoding* to_encoding)
+static bool mb_recursive_convert_variable(uint32_t arg_num, zval *var, const mbfl_encoding* from_encoding, const mbfl_encoding* to_encoding)
 {
 	zval *entry, *orig_var;

@@ -3836,6 +3836,15 @@ static bool mb_recursive_convert_variable(zval *var, const mbfl_encoding* from_e
 		zval_ptr_dtor(orig_var);
 		ZVAL_STR(orig_var, ret);
 	} else if (Z_TYPE_P(var) == IS_ARRAY || Z_TYPE_P(var) == IS_OBJECT) {
+		if (Z_TYPE_P(var) == IS_OBJECT) {
+			php_error_docref(NULL, E_DEPRECATED,
+				"Passing an object for argument #%" PRIu32 " $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead",
+				arg_num
+			);
+			if (UNEXPECTED(EG(exception))) {
+				return true;
+			}
+		}
 		HashTable *ht = HASH_OF(var);
 		HashTable *orig_ht = ht;

@@ -3872,7 +3881,7 @@ static bool mb_recursive_convert_variable(zval *var, const mbfl_encoding* from_e
 					}
 				}

-				if (mb_recursive_convert_variable(entry, from_encoding, to_encoding)) {
+				if (mb_recursive_convert_variable(arg_num, entry, from_encoding, to_encoding)) {
 					if (ht && ht != orig_ht) {
 						GC_TRY_UNPROTECT_RECURSION(ht);
 					}
@@ -3890,6 +3899,14 @@ static bool mb_recursive_convert_variable(zval *var, const mbfl_encoding* from_e
 		if (orig_ht) {
 			GC_TRY_UNPROTECT_RECURSION(orig_ht);
 		}
+	} else if (Z_TYPE_P(var) != IS_UNDEF) { /* Ignore unset properties */
+		php_error_docref(NULL, E_WARNING,
+			"Argument #%" PRIu32 " must be of type string|array|object or only contain entries of type string|array|object, %s given",
+			arg_num, zend_zval_type_name(var)
+		);
+		if (UNEXPECTED(EG(exception))) {
+			return true;
+		}
 	}

 	return false;
@@ -3985,7 +4002,7 @@ PHP_FUNCTION(mb_convert_variables)
 	for (size_t n = 0; n < argc; n++) {
 		zval *zv = &args[n];
 		ZVAL_DEREF(zv);
-		if (mb_recursive_convert_variable(zv, from_encoding, to_encoding)) {
+		if (mb_recursive_convert_variable(n + 3, zv, from_encoding, to_encoding)) {
 			if (!EG(exception)) {
 				php_error_docref(NULL, E_WARNING, "Cannot handle recursive references");
 			}
diff --git a/ext/mbstring/tests/gh16261.phpt b/ext/mbstring/tests/gh16261.phpt
index 3573bd191c6..1fbbc5c208c 100644
--- a/ext/mbstring/tests/gh16261.phpt
+++ b/ext/mbstring/tests/gh16261.phpt
@@ -26,7 +26,8 @@ public function __construct(public string $x) {}

 var_dump($test, $test2);
 ?>
---EXPECT--
+--EXPECTF--
+Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
 object(Test)#1 (2) {
   ["x"]=>
   string(5) "hello"
diff --git a/ext/mbstring/tests/mb_convert_variables.phpt b/ext/mbstring/tests/mb_convert_variables.phpt
index 2eb45946b5e..7558bded048 100644
--- a/ext/mbstring/tests/mb_convert_variables.phpt
+++ b/ext/mbstring/tests/mb_convert_variables.phpt
@@ -173,7 +173,7 @@ function __construct($value)
 var_dump($bad_utf7imap);

 ?>
---EXPECT--
+--EXPECTF--
 == SCALAR TEST ==
 SJIS
 c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3
@@ -191,21 +191,37 @@ function __construct($value)
 EUC-JP
 c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3
 == OBJECT TEST ==
+
+Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
 EUC-JP
 c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3
+
+Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
 EUC-JP
 c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3
 == SCALAR, ARRAY AND OBJECT TEST ==
+
+Deprecated: mb_convert_variables(): Passing an object for argument #7 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
 EUC-JP
 c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3
 c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3
 c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3c6fccbdcb8eca5c6a5ada5b9a5c8a4c7a4b9a1a33031323334a3b5a3b6a3b7a3b8a3b9a1a3
 == DEEPLY NESTED OBJECT/ARRAY TEST ==
+
+Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
+
+Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
+
+Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
 UTF-8
 42004c0041004800
 == INVALID STRING ENCODING TEST ==
+
+Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
 2500
 # of illegal characters detected: 1
+
+Deprecated: mb_convert_variables(): Passing an object for argument #3 $vars to mb_convert_variables() is deprecated, call get_object_vars() first instead in %s on line %d
 2600
 # of illegal characters detected: 1
 == ENCODING AUTO-DETECTION TEST ==
diff --git a/ext/mbstring/tests/mb_convert_variables_invalid_types.phpt b/ext/mbstring/tests/mb_convert_variables_invalid_types.phpt
new file mode 100644
index 00000000000..70972f04213
--- /dev/null
+++ b/ext/mbstring/tests/mb_convert_variables_invalid_types.phpt
@@ -0,0 +1,30 @@
+--TEST--
+mb_convert_variables(): $vars which are not string|array|object
+--EXTENSIONS--
+mbstring
+--FILE--
+<?php
+
+$i = 5;
+$a = [$i];
+
+try {
+    var_dump( mb_convert_variables('UTF-8', 'EUC-JP', $i, $a));
+} catch (\ValueError $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+try {
+    var_dump( mb_convert_variables('UTF-8', 'EUC-JP', $a));
+} catch (\ValueError $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECTF--
+Warning: mb_convert_variables(): Argument #3 must be of type string|array|object or only contain entries of type string|array|object, int given in %s on line %d
+
+Warning: mb_convert_variables(): Argument #4 must be of type string|array|object or only contain entries of type string|array|object, int given in %s on line %d
+string(6) "EUC-JP"
+
+Warning: mb_convert_variables(): Argument #3 must be of type string|array|object or only contain entries of type string|array|object, int given in %s on line %d
+string(6) "EUC-JP"