Commit a2114a3b505 for php.net

commit a2114a3b50524b889817d3611e3146b5da157147
Author: Jorg Adam Sowa <jorg.sowa@gmail.com>
Date:   Sun Jun 14 11:11:24 2026 +0200

    Zend: fix canonical case of magic-method name macros (#22275)

    Fixed the canonical case of the magic method `__debugInfo()` + organized all macros of magic methods with the separation of canonical and lowercase versions.

diff --git a/Zend/tests/debug_info/debug_info-error-0.0.phpt b/Zend/tests/debug_info/debug_info-error-0.0.phpt
index ab41b440fc8..4df25c4c95a 100644
--- a/Zend/tests/debug_info/debug_info-error-0.0.phpt
+++ b/Zend/tests/debug_info/debug_info-error-0.0.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-0.phpt b/Zend/tests/debug_info/debug_info-error-0.phpt
index 37289c6468f..c0cedf3b88e 100644
--- a/Zend/tests/debug_info/debug_info-error-0.phpt
+++ b/Zend/tests/debug_info/debug_info-error-0.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-1.0.phpt b/Zend/tests/debug_info/debug_info-error-1.0.phpt
index 9b168621b5e..6af945cb0e3 100644
--- a/Zend/tests/debug_info/debug_info-error-1.0.phpt
+++ b/Zend/tests/debug_info/debug_info-error-1.0.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-1.phpt b/Zend/tests/debug_info/debug_info-error-1.phpt
index ae01a6055c0..7cadd485118 100644
--- a/Zend/tests/debug_info/debug_info-error-1.phpt
+++ b/Zend/tests/debug_info/debug_info-error-1.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt b/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt
new file mode 100644
index 00000000000..c2a6f4357a5
--- /dev/null
+++ b/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Testing __debugInfo() magic method declared with non-canonical case
+--FILE--
+<?php
+
+class C {
+  public $val;
+  public function __DEBUGINFO() {
+    return $this->val;
+  }
+  public function __construct($val) {
+    $this->val = $val;
+  }
+}
+
+$c = new C(1);
+var_dump($c);
+?>
+--EXPECTF--
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-empty_str.phpt b/Zend/tests/debug_info/debug_info-error-empty_str.phpt
index bbab78cd820..21611cc9bde 100644
--- a/Zend/tests/debug_info/debug_info-error-empty_str.phpt
+++ b/Zend/tests/debug_info/debug_info-error-empty_str.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-false.phpt b/Zend/tests/debug_info/debug_info-error-false.phpt
index 3e48372c420..fb5a35c2d1e 100644
--- a/Zend/tests/debug_info/debug_info-error-false.phpt
+++ b/Zend/tests/debug_info/debug_info-error-false.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-object.phpt b/Zend/tests/debug_info/debug_info-error-object.phpt
index 42e07399990..5cdc6b289e6 100644
--- a/Zend/tests/debug_info/debug_info-error-object.phpt
+++ b/Zend/tests/debug_info/debug_info-error-object.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-resource.phpt b/Zend/tests/debug_info/debug_info-error-resource.phpt
index ccacce7a74b..1fc84fe83d8 100644
--- a/Zend/tests/debug_info/debug_info-error-resource.phpt
+++ b/Zend/tests/debug_info/debug_info-error-resource.phpt
@@ -19,4 +19,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-str.phpt b/Zend/tests/debug_info/debug_info-error-str.phpt
index 85d3f63b97e..fe4201de1c9 100644
--- a/Zend/tests/debug_info/debug_info-error-str.phpt
+++ b/Zend/tests/debug_info/debug_info-error-str.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-true.phpt b/Zend/tests/debug_info/debug_info-error-true.phpt
index 3843c6a7a14..12bb7329a85 100644
--- a/Zend/tests/debug_info/debug_info-error-true.phpt
+++ b/Zend/tests/debug_info/debug_info-error-true.phpt
@@ -17,4 +17,4 @@ public function __construct($val) {
 var_dump($c);
 ?>
 --EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt b/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt
new file mode 100644
index 00000000000..7f456479445
--- /dev/null
+++ b/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Stringable is automatically implemented for __toString() declared with non-canonical case
+--FILE--
+<?php
+
+class Test {
+    public function __TOSTRING() {
+        return "foo";
+    }
+}
+
+var_dump(new Test instanceof Stringable);
+var_dump((new ReflectionClass(Test::class))->getInterfaceNames());
+var_dump((string) new Test);
+
+?>
+--EXPECT--
+bool(true)
+array(1) {
+  [0]=>
+  string(10) "Stringable"
+}
+string(3) "foo"
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index 789b3ac2642..46b62dcc7c4 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -2809,18 +2809,18 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce,
 		zend_check_magic_method_public(ce, fptr);
 		zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_STRING);
 		zend_check_magic_method_arg_type(1, ce, fptr, error_type, MAY_BE_ARRAY);
-	} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) {
+	} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_LCNAME)) {
 		zend_check_magic_method_args(2, ce, fptr, error_type);
 		zend_check_magic_method_static(ce, fptr, error_type);
 		zend_check_magic_method_public(ce, fptr);
 		zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_STRING);
 		zend_check_magic_method_arg_type(1, ce, fptr, error_type, MAY_BE_ARRAY);
-	} else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) {
+	} else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)) {
 		zend_check_magic_method_args(0, ce, fptr, error_type);
 		zend_check_magic_method_non_static(ce, fptr, error_type);
 		zend_check_magic_method_public(ce, fptr);
 		zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_STRING);
-	} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
+	} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_LCNAME)) {
 		zend_check_magic_method_args(0, ce, fptr, error_type);
 		zend_check_magic_method_non_static(ce, fptr, error_type);
 		zend_check_magic_method_public(ce, fptr);
@@ -2829,18 +2829,18 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce,
 			zend_error(E_DEPRECATED, "Returning null from %s::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead",
 				ZSTR_VAL(ce->name));
 		}
-	} else if (zend_string_equals_literal(lcname, "__serialize")) {
+	} else if (zend_string_equals_literal(lcname, ZEND_SERIALIZE_FUNC_NAME)) {
 		zend_check_magic_method_args(0, ce, fptr, error_type);
 		zend_check_magic_method_non_static(ce, fptr, error_type);
 		zend_check_magic_method_public(ce, fptr);
 		zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_ARRAY);
-	} else if (zend_string_equals_literal(lcname, "__unserialize")) {
+	} else if (zend_string_equals_literal(lcname, ZEND_UNSERIALIZE_FUNC_NAME)) {
 		zend_check_magic_method_args(1, ce, fptr, error_type);
 		zend_check_magic_method_non_static(ce, fptr, error_type);
 		zend_check_magic_method_public(ce, fptr);
 		zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_ARRAY);
 		zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_VOID);
-	} else if (zend_string_equals_literal(lcname, "__set_state")) {
+	} else if (zend_string_equals_literal(lcname, ZEND_SET_STATE_FUNC_NAME)) {
 		zend_check_magic_method_args(1, ce, fptr, error_type);
 		zend_check_magic_method_static(ce, fptr, error_type);
 		zend_check_magic_method_public(ce, fptr);
@@ -2888,16 +2888,16 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, c
 	} else if (zend_string_equals_literal(lcname, ZEND_ISSET_FUNC_NAME)) {
 		ce->__isset = fptr;
 		ce->ce_flags |= ZEND_ACC_USE_GUARDS;
-	} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) {
+	} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_LCNAME)) {
 		ce->__callstatic = fptr;
-	} else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) {
+	} else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)) {
 		ce->__tostring = fptr;
-	} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
+	} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_LCNAME)) {
 		ce->__debugInfo = fptr;
 		ce->ce_flags |= ZEND_ACC_USE_GUARDS;
-	} else if (zend_string_equals_literal(lcname, "__serialize")) {
+	} else if (zend_string_equals_literal(lcname, ZEND_SERIALIZE_FUNC_NAME)) {
 		ce->__serialize = fptr;
-	} else if (zend_string_equals_literal(lcname, "__unserialize")) {
+	} else if (zend_string_equals_literal(lcname, ZEND_UNSERIALIZE_FUNC_NAME)) {
 		ce->__unserialize = fptr;
 	}
 }
@@ -3111,7 +3111,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend

 		/* If not specified, add __toString() return type for compatibility with Stringable
 		 * interface. */
-		if (scope && zend_string_equals_literal_ci(internal_function->function_name, "__tostring") &&
+		if (scope && zend_string_equals_literal_ci(internal_function->function_name, ZEND_TOSTRING_FUNC_LCNAME) &&
 				!(internal_function->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
 			zend_error(E_CORE_WARNING, "%s::__toString() implemented without string return type",
 				ZSTR_VAL(scope->name));
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 105f99d2417..7e9f7ceac8d 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -8610,7 +8610,7 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string
 	}

 	zend_add_magic_method(ce, (zend_function *) op_array, lcname);
-	if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)
+	if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)
 			&& !(ce->ce_flags & ZEND_ACC_TRAIT)) {
 		add_stringable_interface(ce);
 	}
@@ -8841,7 +8841,7 @@ static zend_op_array *zend_compile_func_decl_ex(
 	}

 	zend_compile_params(params_ast, return_type_ast,
-		is_method && zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME) ? IS_STRING : 0);
+		is_method && zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME) ? IS_STRING : 0);
 	if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) {
 		zend_mark_function_as_generator();
 		zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL);
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 0e31332c97f..2351882a560 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -368,7 +368,7 @@ typedef struct _zend_oparray_context {
 #define ZEND_ACC_USES_THIS               (1 << 17) /*     |  X  |     |     */
 /*                                                        |     |     |     */
 /* call through user function trampoline. e.g.            |     |     |     */
-/* __call, __callstatic                                   |     |     |     */
+/* __call, __callStatic                                   |     |     |     */
 #define ZEND_ACC_CALL_VIA_TRAMPOLINE     (1 << 18) /*     |  X  |     |     */
 /*                                                        |     |     |     */
 /* disable inline caching                                 |     |     |     */
@@ -1249,10 +1249,18 @@ END_EXTERN_C()
 #define ZEND_UNSET_FUNC_NAME        "__unset"
 #define ZEND_ISSET_FUNC_NAME        "__isset"
 #define ZEND_CALL_FUNC_NAME         "__call"
-#define ZEND_CALLSTATIC_FUNC_NAME   "__callstatic"
-#define ZEND_TOSTRING_FUNC_NAME     "__tostring"
+#define ZEND_CALLSTATIC_FUNC_NAME   "__callStatic"
+#define ZEND_CALLSTATIC_FUNC_LCNAME "__callstatic"
+#define ZEND_TOSTRING_FUNC_NAME     "__toString"
+#define ZEND_TOSTRING_FUNC_LCNAME   "__tostring"
 #define ZEND_INVOKE_FUNC_NAME       "__invoke"
-#define ZEND_DEBUGINFO_FUNC_NAME    "__debuginfo"
+#define ZEND_DEBUGINFO_FUNC_NAME    "__debugInfo"
+#define ZEND_DEBUGINFO_FUNC_LCNAME  "__debuginfo"
+#define ZEND_SLEEP_FUNC_NAME        "__sleep"
+#define ZEND_WAKEUP_FUNC_NAME       "__wakeup"
+#define ZEND_SERIALIZE_FUNC_NAME    "__serialize"
+#define ZEND_UNSERIALIZE_FUNC_NAME  "__unserialize"
+#define ZEND_SET_STATE_FUNC_NAME    "__set_state"

 /* The following constants may be combined in CG(compiler_options)
  * to change the default compiler behavior */
diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c
index a5091f6c1b6..7f0b5857538 100644
--- a/Zend/zend_enum.c
+++ b/Zend/zend_enum.c
@@ -92,21 +92,21 @@ static void zend_verify_enum_magic_methods(const zend_class_entry *ce)
 {
 	// Only __get, __call, __debugInfo and __invoke are allowed

-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(constructor, "__construct");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(destructor, "__destruct");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(clone, "__clone");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__get, "__get");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, "__set");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, "__unset");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, "__isset");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, "__serialize");
-	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, "__unserialize");
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(constructor, ZEND_CONSTRUCTOR_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(destructor, ZEND_DESTRUCTOR_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(clone, ZEND_CLONE_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__get, ZEND_GET_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, ZEND_SET_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, ZEND_UNSET_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, ZEND_ISSET_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, ZEND_TOSTRING_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, ZEND_SERIALIZE_FUNC_NAME);
+	ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, ZEND_UNSERIALIZE_FUNC_NAME);

 	static const char *const forbidden_methods[] = {
-		"__sleep",
-		"__wakeup",
-		"__set_state",
+		ZEND_SLEEP_FUNC_NAME,
+		ZEND_WAKEUP_FUNC_NAME,
+		ZEND_SET_STATE_FUNC_NAME,
 	};

 	uint32_t forbidden_methods_length = sizeof(forbidden_methods) / sizeof(forbidden_methods[0]);