[php-src] master: Deprecate returning values from `__construct()` and `__destruct()` (#21982)

Tim Düsterhus via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Tim Düsterhus (TimWolla)
Committer: GitHub (web-flow)
Pusher: TimWolla
Date: 2026-06-30T10:03:55+02:00

Commit: https://github.com/php/php-src/commit/18d783719d58420fa7f333175830fcf40c916bea
Raw diff: https://github.com/php/php-src/commit/18d783719d58420fa7f333175830fcf40c916bea.diff

Deprecate returning values from `__construct()` and `__destruct()` (#21982)

* Deprecate returning values from `__construct()` and `__destruct()`

* Deprecate making `__construct()` and `__destruct()` a `Generator`

* NEWS / UPGRADING

Changed paths:
  A  Zend/tests/magic_methods/constructor_destructor_return.phpt
  M  NEWS
  M  UPGRADING
  M  Zend/tests/prop_const_expr/non_enums_catchable.phpt
  M  Zend/tests/traits/bug60536_001.phpt
  M  Zend/zend_compile.c
  M  ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
  M  ext/reflection/tests/bug36434.phpt
  M  ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt
  M  ext/zend_test/tests/zend_object_init_with_constructor.phpt


Diff:

diff --git a/NEWS b/NEWS
index 9d326431321c..667a0bc591e3 100644
--- a/NEWS
+++ b/NEWS
@@ -37,6 +37,7 @@ PHP                                                                        NEWS
   . Fixed bug GH-22257 (type confusion in Exception::getTraceAsString()).
     (David Carlier)
   . TSRM: make CG, EG, SCNG and AG compile-time offsets. (henderkes)
+  . Deprecate returning values from __construct() and __destruct(). (timwolla)
 
 - BCMath:
   . Added NUL-byte validation to BCMath functions. (jorgsowa)
diff --git a/UPGRADING b/UPGRADING
index 02aa821acecf..fa9a816f7aed 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -283,6 +283,10 @@ PHP 8.6 UPGRADE NOTES
 - Core:
   . Specifying a return type of array|null / ?array for __debugInfo() is now
     deprecated. Specify array instead.
+  . Returning values from __construct() and __destruct() is now deprecated.
+    RFC: https://wiki.php.net/rfc/deprecate-return-value-from-construct
+  . Making __construct() and __destruct() a Generator is now deprecated.
+    RFC: https://wiki.php.net/rfc/deprecate-return-value-from-construct
 
 - GMP
   . The shift (<<, >>) and exponentiation (**) operators on GMP objects now
diff --git a/Zend/tests/magic_methods/constructor_destructor_return.phpt b/Zend/tests/magic_methods/constructor_destructor_return.phpt
new file mode 100644
index 000000000000..635548bb6f47
--- /dev/null
+++ b/Zend/tests/magic_methods/constructor_destructor_return.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Returning values from constructors and destructors is deprecated
+--FILE--
+<?php
+
+class A {
+    public function __construct() { return ''; }
+    public function __destruct() { return ''; }
+}
+
+class B {
+    public function __construct() { return $this->voidMethod(); }
+    public function __destruct() { return $this->voidMethod(); }
+
+    public function voidMethod(): void { }
+}
+
+class Gen {
+    public function __construct() { yield ''; }
+    public function __destruct() { yield ''; }
+}
+
+?>
+--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a destructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a destructor is deprecated in %s on line %d
+
+Deprecated: Making a constructor a Generator is deprecated in %s on line %d
+
+Deprecated: Making a destructor a Generator is deprecated in %s on line %d
diff --git a/Zend/tests/prop_const_expr/non_enums_catchable.phpt b/Zend/tests/prop_const_expr/non_enums_catchable.phpt
index 6f410ac7acbe..702bc024122d 100644
--- a/Zend/tests/prop_const_expr/non_enums_catchable.phpt
+++ b/Zend/tests/prop_const_expr/non_enums_catchable.phpt
@@ -18,6 +18,7 @@ const A_prop = (new A)->{new Printer ? 'printer' : null};
 
 ?>
 --EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
 Printer
 
 Fatal error: Uncaught Error: Fetching properties on non-enums in constant expressions is not allowed in %s:%d
diff --git a/Zend/tests/traits/bug60536_001.phpt b/Zend/tests/traits/bug60536_001.phpt
index a58098ca3112..712600a4b1d1 100644
--- a/Zend/tests/traits/bug60536_001.phpt
+++ b/Zend/tests/traits/bug60536_001.phpt
@@ -23,5 +23,9 @@ $a->__construct();
 echo "DONE";
 ?>
 --EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
 Warning: Undefined property: Z::$x in %s on line %d
 DONE
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 9f8ebad8ab29..4c1375215888 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -530,6 +530,12 @@ ZEND_API bool zend_is_compiling(void) /* {{{ */
 }
 /* }}} */
 
+static bool zend_is_constructor(const zend_string *name) /* {{{ */
+{
+	return zend_string_equals_literal_ci(name, ZEND_CONSTRUCTOR_FUNC_NAME);
+}
+/* }}} */
+
 static zend_always_inline uint32_t get_temporary_variable(void) /* {{{ */
 {
 	return (uint32_t)CG(active_op_array)->T++;
@@ -5549,12 +5555,6 @@ static void zend_compile_method_call(znode *result, zend_ast *ast, uint32_t type
 }
 /* }}} */
 
-static bool zend_is_constructor(const zend_string *name) /* {{{ */
-{
-	return zend_string_equals_literal_ci(name, ZEND_CONSTRUCTOR_FUNC_NAME);
-}
-/* }}} */
-
 static bool is_func_accessible(const zend_function *fbc)
 {
 	if ((fbc->common.fn_flags & ZEND_ACC_PUBLIC) || fbc->common.scope == CG(active_class_entry)) {
@@ -5993,6 +5993,16 @@ static void zend_compile_return(const zend_ast *ast) /* {{{ */
 		zend_compile_expr(&expr_node, expr_ast);
 	}
 
+	if (expr_ast) {
+		if (CG(active_class_entry) != NULL) {
+			if (zend_is_constructor(CG(active_op_array)->function_name)) {
+				zend_error(E_DEPRECATED, "Returning a value from a constructor is deprecated");
+			} else if (zend_string_equals_literal_ci(CG(active_op_array)->function_name, ZEND_DESTRUCTOR_FUNC_NAME)) {
+				zend_error(E_DEPRECATED, "Returning a value from a destructor is deprecated");
+			}
+		}
+	}
+
 	if ((CG(active_op_array)->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)
 	 && (expr_node.op_type == IS_CV || (by_ref && expr_node.op_type == IS_VAR))
 	 && zend_has_finally()) {
@@ -8843,6 +8853,14 @@ 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_LCNAME) ? IS_STRING : 0);
 	if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) {
+		if (CG(active_class_entry) != NULL) {
+			if (zend_is_constructor(CG(active_op_array)->function_name)) {
+				zend_error(E_DEPRECATED, "Making a constructor a Generator is deprecated");
+			} else if (zend_string_equals_literal_ci(CG(active_op_array)->function_name, ZEND_DESTRUCTOR_FUNC_NAME)) {
+				zend_error(E_DEPRECATED, "Making a destructor a Generator is deprecated");
+			}
+		}
+
 		zend_mark_function_as_generator();
 		zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL);
 	}
diff --git a/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt b/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
index d74e348fb99a..be7eb74b5b35 100644
--- a/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
+++ b/ext/pdo_mysql/tests/pdo_mysql_subclass.phpt
@@ -76,6 +76,7 @@ $db = MySQLPDOTest::factory();
 $db->exec('DROP TABLE IF EXISTS test_subclass');
 ?>
 --EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
 __construct('%S', '%S', %s)
 
 Deprecated: Callables of the form ["MyPDO", "parent::__construct"] are deprecated in %s on line %d
diff --git a/ext/reflection/tests/bug36434.phpt b/ext/reflection/tests/bug36434.phpt
index 3f4cc70c7554..0dfa5d117b2c 100644
--- a/ext/reflection/tests/bug36434.phpt
+++ b/ext/reflection/tests/bug36434.phpt
@@ -26,6 +26,9 @@ foreach ($r->GetProperties() as $p)
 }
 
 ?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
 bar foo
 ancestor ancestor
diff --git a/ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt b/ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt
index 4a82d5893157..dcfa8f2e759c 100644
--- a/ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt
+++ b/ext/standard/tests/array/gh16649/array_splice_uaf_original_case.phpt
@@ -25,5 +25,6 @@ try {
     echo "Exception caught: " . $e->getMessage() . "\n";
 }
 ?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning a value from a destructor is deprecated in %s on line %d
 Exception caught: Array was modified during array_splice operation
diff --git a/ext/zend_test/tests/zend_object_init_with_constructor.phpt b/ext/zend_test/tests/zend_object_init_with_constructor.phpt
index 65b111447f0b..aa3ad7ee845a 100644
--- a/ext/zend_test/tests/zend_object_init_with_constructor.phpt
+++ b/ext/zend_test/tests/zend_object_init_with_constructor.phpt
@@ -139,7 +139,14 @@ $o = zend_object_init_with_constructor("TestUserWithConstructorNoParams");
 var_dump($o);
 unset($o);
 ?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
 Testing impossible initializations
 Error: Cannot instantiate interface _ZendTestInterface
 Error: Cannot instantiate trait _ZendTestTrait
@@ -152,14 +159,14 @@ Testing param passing
 ArgumentCountError: Too few arguments to function TestUserWithConstructorArgs::__construct(), 0 passed and exactly 2 expected
 TypeError: TestUserWithConstructorArgs::__construct(): Argument #1 ($int_param) must be of type int, string given
 Error: Unknown named parameter $unused_param
-object(TestUserWithConstructorArgs)#1 (0) {
+object(TestUserWithConstructorArgs)#%d (0) {
 }
 Destructor for TestUserWithConstructorArgs
 Passing too many args to constructor
-object(TestUserWithConstructorArgs)#1 (0) {
+object(TestUserWithConstructorArgs)#%d (0) {
 }
 Destructor for TestUserWithConstructorArgs
 Testing class with defined constructor and no params
-object(TestUserWithConstructorNoParams)#1 (0) {
+object(TestUserWithConstructorNoParams)#%d (0) {
 }
 Destructor for TestUserWithConstructorNoParams
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.